From 4b6cc8ba0f7cfb35a8d7200eb22ba15e73392ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Thu, 8 Oct 2026 04:59:35 +0900 Subject: [PATCH] Add MuQSS kernel based on v7.2.8-5 with latest MuQSS from CK tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof WilczyƄski --- .../linux-omarchy-muqss/.omarchy/package.json | 4 + .../0010-archlinux-base.patch | 55 + .../0010-archlinux-base.patch.sig | Bin 0 -> 594 bytes ...1-kbuild-optimize-for-performance-o3.patch | 31 + ...uild-optimize-for-performance-o3.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0110-muqss-0.31.patch | 17203 +++++++ .../0110-muqss-0.31.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0120-tlbpull.patch | 582 + .../0120-tlbpull.patch.sig | Bin 0 -> 594 bytes .../0121-smp-preempt.patch | 514 + .../0121-smp-preempt.patch.sig | Bin 0 -> 594 bytes .../0130-sched-detach-tasks.patch | 21 + .../0130-sched-detach-tasks.patch.sig | Bin 0 -> 594 bytes .../0140-sched-always-inline.patch | 367 + .../0140-sched-always-inline.patch.sig | Bin 0 -> 594 bytes .../0141-sched-urgent-fixes.patch | 97 + .../0141-sched-urgent-fixes.patch.sig | Bin 0 -> 594 bytes .../0143-sched-hybrid-cluster-balancing.patch | 88 + ...3-sched-hybrid-cluster-balancing.patch.sig | Bin 0 -> 594 bytes .../0144-sched-nohz-idle-core.patch | 77 + .../0144-sched-nohz-idle-core.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0210-pm-updates.patch | 2365 + .../0210-pm-updates.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0211-pm-fixes-1.patch | 104 + .../0211-pm-fixes-1.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0212-pm-fixes-2.patch | 54 + .../0212-pm-fixes-2.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0213-pm-fixes-3.patch | 61 + .../0213-pm-fixes-3.patch.sig | Bin 0 -> 594 bytes .../0220-x86-amd-zen5-tlb-sizes.patch | 95 + .../0220-x86-amd-zen5-tlb-sizes.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0250-zsmalloc.patch | 396 + .../0250-zsmalloc.patch.sig | Bin 0 -> 594 bytes .../0260-mglru-exec-protect.patch | 313 + .../0260-mglru-exec-protect.patch.sig | Bin 0 -> 594 bytes .../0270-ksm-rmap-walk.patch | 161 + .../0270-ksm-rmap-walk.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0280-mm-updates.patch | 1162 + .../0280-mm-updates.patch.sig | Bin 0 -> 594 bytes ...erred-kernel-page-table-free-at-boot.patch | 35 + ...d-kernel-page-table-free-at-boot.patch.sig | Bin 0 -> 594 bytes .../0290-zstd-bmi2-fallback-aliases.patch | 97 + .../0290-zstd-bmi2-fallback-aliases.patch.sig | Bin 0 -> 594 bytes .../0291-zstd-bmi2-cpu-feature-dispatch.patch | 413 + ...1-zstd-bmi2-cpu-feature-dispatch.patch.sig | Bin 0 -> 594 bytes .../0292-crypto-zstd-defer-cstream-init.patch | 44 + ...2-crypto-zstd-defer-cstream-init.patch.sig | Bin 0 -> 594 bytes .../0293-crypto-zstd-defer-dstream-init.patch | 44 + ...3-crypto-zstd-defer-dstream-init.patch.sig | Bin 0 -> 594 bytes .../0295-af-alg-restrict.patch | 363 + .../0295-af-alg-restrict.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0300-btrfs.patch | 4042 ++ .../linux-omarchy-muqss/0300-btrfs.patch.sig | Bin 0 -> 594 bytes .../0301-btrfs-fixes-1.patch | 92 + .../0301-btrfs-fixes-1.patch.sig | Bin 0 -> 594 bytes ...btrfs-zstd-decompress-direct-to-page.patch | 109 + ...s-zstd-decompress-direct-to-page.patch.sig | Bin 0 -> 594 bytes .../0303-btrfs-fixes-2.patch | 97 + .../0303-btrfs-fixes-2.patch.sig | Bin 0 -> 594 bytes .../0304-btrfs-fixes-3.patch | 77 + .../0304-btrfs-fixes-3.patch.sig | Bin 0 -> 594 bytes .../0310-fuse-eof-zeroing.patch | 42 + .../0310-fuse-eof-zeroing.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0311-fuse-perf.patch | 104 + .../0311-fuse-perf.patch.sig | Bin 0 -> 594 bytes .../0312-fuse-writethrough-uptodate.patch | 38 + .../0312-fuse-writethrough-uptodate.patch.sig | Bin 0 -> 594 bytes .../0313-fuse-background-wakeup.patch | 13 + .../0313-fuse-background-wakeup.patch.sig | Bin 0 -> 594 bytes ...0320-vfs-open-consume-path-reference.patch | 119 + ...-vfs-open-consume-path-reference.patch.sig | Bin 0 -> 594 bytes ...r-range-from-displayid-adaptive-sync.patch | 123 + ...nge-from-displayid-adaptive-sync.patch.sig | Bin 0 -> 594 bytes .../0360-gpu-mem-cgroup.patch | 601 + .../0360-gpu-mem-cgroup.patch.sig | Bin 0 -> 594 bytes ...5-alpm-limit-pr-alpm-to-panel-replay.patch | 13 + ...pm-limit-pr-alpm-to-panel-replay.patch.sig | Bin 0 -> 594 bytes .../0401-psr2-early-transport-panels.patch | 20 + ...0401-psr2-early-transport-panels.patch.sig | Bin 0 -> 594 bytes ...915-quirk-panel-replay-exit-on-flush.patch | 172 + ...quirk-panel-replay-exit-on-flush.patch.sig | Bin 0 -> 594 bytes ...xps14-ptl-panel-replay-exit-on-flush.patch | 12 + ...4-ptl-panel-replay-exit-on-flush.patch.sig | Bin 0 -> 594 bytes ...xps16-ptl-panel-replay-exit-on-flush.patch | 12 + ...6-ptl-panel-replay-exit-on-flush.patch.sig | Bin 0 -> 594 bytes ...irk-dell-xps13-dx13260-ptl-psr1-only.patch | 28 + ...dell-xps13-dx13260-ptl-psr1-only.patch.sig | Bin 0 -> 594 bytes ...13260-wcl-panel-replay-exit-on-flush.patch | 19 + ...0-wcl-panel-replay-exit-on-flush.patch.sig | Bin 0 -> 594 bytes ...rm-xe-display-no-stolen-framebuffers.patch | 43 + ...e-display-no-stolen-framebuffers.patch.sig | Bin 0 -> 594 bytes .../0420-safe-window.patch | 23 + .../0420-safe-window.patch.sig | Bin 0 -> 594 bytes pkgbuilds/linux-omarchy-muqss/0430-fbc.patch | 112 + .../linux-omarchy-muqss/0430-fbc.patch.sig | Bin 0 -> 594 bytes .../0440-xe3-peak-bandwidth.patch | 67 + .../0440-xe3-peak-bandwidth.patch.sig | Bin 0 -> 594 bytes ...-i915-psr-clear-stale-sel-fetch-bits.patch | 95 + ...5-psr-clear-stale-sel-fetch-bits.patch.sig | Bin 0 -> 594 bytes ...15-dsb-delayed-vblank-scanline-shift.patch | 22 + ...sb-delayed-vblank-scanline-shift.patch.sig | Bin 0 -> 594 bytes ...m-i915-vrr-dc-balance-off-by-default.patch | 42 + ...15-vrr-dc-balance-off-by-default.patch.sig | Bin 0 -> 594 bytes .../0450-amd-hdmi-vrr-allm.patch | 456 + .../0450-amd-hdmi-vrr-allm.patch.sig | Bin 0 -> 594 bytes .../0451-amd-vtem-tmds-links.patch | 15 + .../0451-amd-vtem-tmds-links.patch.sig | Bin 0 -> 594 bytes .../0452-amd-hdmi-frl-default.patch | 28 + .../0452-amd-hdmi-frl-default.patch.sig | Bin 0 -> 594 bytes .../0453-amd-vtem-null-sink.patch | 14 + .../0453-amd-vtem-null-sink.patch.sig | Bin 0 -> 594 bytes .../0454-amd-hdmi-frl-status-polling.patch | 12 + ...0454-amd-hdmi-frl-status-polling.patch.sig | Bin 0 -> 594 bytes ...dmi-frl-cap-on-nondestructive-verify.patch | 12 + ...frl-cap-on-nondestructive-verify.patch.sig | Bin 0 -> 594 bytes ...456-amd-dml-frl-compressed-cap-check.patch | 26 + ...amd-dml-frl-compressed-cap-check.patch.sig | Bin 0 -> 594 bytes .../0460-vesa-displayid-dsc-bpp.patch | 335 + .../0460-vesa-displayid-dsc-bpp.patch.sig | Bin 0 -> 594 bytes ...461-vesa-dsc-passthru-mode-match-fix.patch | 51 + ...vesa-dsc-passthru-mode-match-fix.patch.sig | Bin 0 -> 593 bytes .../0472-amdgpu-userq-post-reset-error.patch | 26 + ...72-amdgpu-userq-post-reset-error.patch.sig | Bin 0 -> 594 bytes ...0474-amd-display-oled-vesa-backlight.patch | 18 + ...-amd-display-oled-vesa-backlight.patch.sig | Bin 0 -> 594 bytes ...-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch | 16 + ...ert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig | Bin 0 -> 594 bytes ...0476-amd-display-ycbcr420-only-modes.patch | 25 + ...-amd-display-ycbcr420-only-modes.patch.sig | Bin 0 -> 594 bytes .../0477-amd-display-irq-atomic-rmw.patch | 303 + .../0477-amd-display-irq-atomic-rmw.patch.sig | Bin 0 -> 594 bytes ...md-freesync-vcp-code-check-hdmi-pcon.patch | 16 + ...reesync-vcp-code-check-hdmi-pcon.patch.sig | Bin 0 -> 594 bytes ...-hdmi-allm-hf-vsif-in-vsp-infopacket.patch | 40 + ...i-allm-hf-vsif-in-vsp-infopacket.patch.sig | Bin 0 -> 594 bytes .../0510-sound-updates.patch | 12141 +++++ .../0510-sound-updates.patch.sig | Bin 0 -> 594 bytes .../0511-asoc-updates.patch | 37891 ++++++++++++++++ .../0511-asoc-updates.patch.sig | Bin 0 -> 594 bytes .../0512-sound-fixes-1.patch | 661 + .../0512-sound-fixes-1.patch.sig | Bin 0 -> 594 bytes .../0513-asoc-fixes-1.patch | 871 + .../0513-asoc-fixes-1.patch.sig | Bin 0 -> 594 bytes .../0514-sound-fixes-2.patch | 362 + .../0514-sound-fixes-2.patch.sig | Bin 0 -> 594 bytes .../0515-xps13-sof-quirk.patch | 19 + .../0515-xps13-sof-quirk.patch.sig | Bin 0 -> 594 bytes .../0516-rt766-stream-config-type.patch | 30 + .../0516-rt766-stream-config-type.patch.sig | Bin 0 -> 594 bytes ...da-realtek-rog-strix-g733zw-speakers.patch | 39 + ...ealtek-rog-strix-g733zw-speakers.patch.sig | Bin 0 -> 594 bytes ...518-asoc-amd-yc-acer-aspire-a314-23p.patch | 17 + ...asoc-amd-yc-acer-aspire-a314-23p.patch.sig | Bin 0 -> 594 bytes .../0522-sound-fixes-3.patch | 432 + .../0522-sound-fixes-3.patch.sig | Bin 0 -> 594 bytes .../0523-asoc-fixes-2.patch | 1040 + .../0523-asoc-fixes-2.patch.sig | Bin 0 -> 594 bytes .../0524-sound-fixes-4.patch | 135 + .../0524-sound-fixes-4.patch.sig | Bin 0 -> 594 bytes .../0525-asoc-fixes-3.patch | 549 + .../0525-asoc-fixes-3.patch.sig | Bin 0 -> 594 bytes ...f-amd-acp7x-suspend-unknown-revision.patch | 13 + ...d-acp7x-suspend-unknown-revision.patch.sig | Bin 0 -> 594 bytes .../0527-sound-fixes-5.patch | 513 + .../0527-sound-fixes-5.patch.sig | Bin 0 -> 593 bytes .../0528-asoc-fixes-4.patch | 523 + .../0528-asoc-fixes-4.patch.sig | Bin 0 -> 594 bytes .../0541-cvs-nova-lake-acpi-id.patch | 33 + .../0541-cvs-nova-lake-acpi-id.patch.sig | Bin 0 -> 594 bytes ...a-cvs-wake-irq-without-claiming-gpio.patch | 33 + ...s-wake-irq-without-claiming-gpio.patch.sig | Bin 0 -> 594 bytes .../0543-media-laptop-cameras.patch | 3293 ++ .../0543-media-laptop-cameras.patch.sig | Bin 0 -> 594 bytes ...544-ipu-bridge-lenovo-x9-upside-down.patch | 49 + ...ipu-bridge-lenovo-x9-upside-down.patch.sig | Bin 0 -> 594 bytes ...-dell-16-premium-da16250-upside-down.patch | 17 + ...l-16-premium-da16250-upside-down.patch.sig | Bin 0 -> 594 bytes ...ung-galaxy-book5-pro-360-upside-down.patch | 18 + ...galaxy-book5-pro-360-upside-down.patch.sig | Bin 0 -> 594 bytes ...547-int3472-imx471-vana-power-enable.patch | 28 + ...int3472-imx471-vana-power-enable.patch.sig | Bin 0 -> 594 bytes ...ia-cvs-sensor-node-in-bound-callback.patch | 37 + ...vs-sensor-node-in-bound-callback.patch.sig | Bin 0 -> 594 bytes ...549-media-imx471-analog-gain-max-960.patch | 12 + ...media-imx471-analog-gain-max-960.patch.sig | Bin 0 -> 594 bytes .../0560-input-updates.patch | 3869 ++ .../0560-input-updates.patch.sig | Bin 0 -> 594 bytes .../0561-input-fixes.patch | 22 + .../0561-input-fixes.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0562-hid-fixes.patch | 483 + .../0562-hid-fixes.patch.sig | Bin 0 -> 594 bytes .../0563-input-fixes-2.patch | 88 + .../0563-input-fixes-2.patch.sig | Bin 0 -> 594 bytes .../0565-i2c-asue140d-touchpad-100khz.patch | 11 + ...565-i2c-asue140d-touchpad-100khz.patch.sig | Bin 0 -> 594 bytes ...o-keyboard-init-reports-to-touchpads.patch | 24 + ...yboard-init-reports-to-touchpads.patch.sig | Bin 0 -> 594 bytes .../0601-thunderbolt-updates.patch | 1131 + .../0601-thunderbolt-updates.patch.sig | Bin 0 -> 594 bytes .../0602-thunderbolt-fixes.patch | 557 + .../0602-thunderbolt-fixes.patch.sig | Bin 0 -> 594 bytes .../0603-usb-updates.patch | 5629 +++ .../0603-usb-updates.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0604-xhci-fixes.patch | 35 + .../0604-xhci-fixes.patch.sig | Bin 0 -> 594 bytes ...thunderbolt-pcie-rescan-after-tunnel.patch | 186 + ...derbolt-pcie-rescan-after-tunnel.patch.sig | Bin 0 -> 594 bytes .../0606-thunderbolt-fixes-2.patch | 239 + .../0606-thunderbolt-fixes-2.patch.sig | Bin 0 -> 594 bytes .../linux-omarchy-muqss/0607-usb-fixes.patch | 1673 + .../0607-usb-fixes.patch.sig | Bin 0 -> 594 bytes .../0620-usb-string-sanitize.patch | 29 + .../0620-usb-string-sanitize.patch.sig | Bin 0 -> 594 bytes .../0650-wireguard-tstamp-type.patch | 18 + .../0650-wireguard-tstamp-type.patch.sig | Bin 0 -> 594 bytes ...0660-btusb-mediatek-mt7922-13d3-3625.patch | 12 + ...-btusb-mediatek-mt7922-13d3-3625.patch.sig | Bin 0 -> 594 bytes ...lwifi-mld-skip-tx-when-firmware-dead.patch | 27 + ...i-mld-skip-tx-when-firmware-dead.patch.sig | Bin 0 -> 594 bytes ...63-btintel-pcie-serialize-reset-type.patch | 145 + ...tintel-pcie-serialize-reset-type.patch.sig | Bin 0 -> 594 bytes ...pcie-stale-cache-in-dxstate-fallback.patch | 30 + ...-stale-cache-in-dxstate-fallback.patch.sig | Bin 0 -> 594 bytes ...0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch | 145 + ...-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig | Bin 0 -> 594 bytes ...btintel-pcie-verify-serialize-dstate.patch | 113 + ...tel-pcie-verify-serialize-dstate.patch.sig | Bin 0 -> 594 bytes ...667-btintel-pcie-gp0-cause-w1c-write.patch | 20 + ...btintel-pcie-gp0-cause-w1c-write.patch.sig | Bin 0 -> 594 bytes .../0668-btintel-pcie-dstate-races.patch | 86 + .../0668-btintel-pcie-dstate-races.patch.sig | Bin 0 -> 594 bytes ...669-btintel-version-tlv-parse-errors.patch | 24 + ...btintel-version-tlv-parse-errors.patch.sig | Bin 0 -> 594 bytes .../0670-bluetooth-fixes.patch | 485 + .../0670-bluetooth-fixes.patch.sig | Bin 0 -> 594 bytes .../0700-pci-target-speed-quirk.patch | 16 + .../0700-pci-target-speed-quirk.patch.sig | Bin 0 -> 594 bytes .../0701-pci-amd-enhanced-atomics.patch | 109 + .../0701-pci-amd-enhanced-atomics.patch.sig | Bin 0 -> 594 bytes .../0702-ahci-jmb585-32bit-dma-revert.patch | 42 + ...702-ahci-jmb585-32bit-dma-revert.patch.sig | Bin 0 -> 594 bytes ...pci-vmd-scan-bios-enumerated-devices.patch | 297 + ...vmd-scan-bios-enumerated-devices.patch.sig | Bin 0 -> 594 bytes ...704-pci-vmd-meteor-lake-mtl016-flush.patch | 120 + ...pci-vmd-meteor-lake-mtl016-flush.patch.sig | Bin 0 -> 594 bytes ...pci-toshiba-ahci-ssd-dma-alias-quirk.patch | 13 + ...toshiba-ahci-ssd-dma-alias-quirk.patch.sig | Bin 0 -> 594 bytes .../0711-iommu-amd-perfopt-support.patch | 413 + .../0711-iommu-amd-perfopt-support.patch.sig | Bin 0 -> 594 bytes ...rm-amdgpu-perfopt-in-identity-domain.patch | 128 + ...mdgpu-perfopt-in-identity-domain.patch.sig | Bin 0 -> 594 bytes .../0740-hwmon-updates.patch | 7419 +++ .../0740-hwmon-updates.patch.sig | Bin 0 -> 594 bytes .../0741-hwmon-fixes-1.patch | 179 + .../0741-hwmon-fixes-1.patch.sig | Bin 0 -> 594 bytes .../0742-hwmon-fixes-2.patch | 12 + .../0742-hwmon-fixes-2.patch.sig | Bin 0 -> 594 bytes ...acer-swift3-sf314-56g-power-resource.patch | 23 + ...-swift3-sf314-56g-power-resource.patch.sig | Bin 0 -> 594 bytes .../0800-platform-updates.patch | 8290 ++++ .../0800-platform-updates.patch.sig | Bin 0 -> 594 bytes ...1-amd-pmf-util-unbind-use-after-free.patch | 34 + ...d-pmf-util-unbind-use-after-free.patch.sig | Bin 0 -> 594 bytes .../0802-platform-fixes.patch | 240 + .../0802-platform-fixes.patch.sig | Bin 0 -> 594 bytes ...03-acpi-battery-hook-api-acpi-prefix.patch | 412 + ...cpi-battery-hook-api-acpi-prefix.patch.sig | Bin 0 -> 594 bytes ...-acpi-battery-unified-hooks-acpi-sbs.patch | 472 + ...i-battery-unified-hooks-acpi-sbs.patch.sig | Bin 0 -> 594 bytes ...pplesmc-charge-control-end-threshold.patch | 209 + ...smc-charge-control-end-threshold.patch.sig | Bin 0 -> 594 bytes .../0850-futex-wait-multiple.patch | 142 + .../0850-futex-wait-multiple.patch.sig | Bin 0 -> 594 bytes .../0851-futex-wait-multiple-fixes.patch | 35 + .../0851-futex-wait-multiple-fixes.patch.sig | Bin 0 -> 594 bytes .../0852-futex-wait-multiple-abi-fixes.patch | 88 + ...52-futex-wait-multiple-abi-fixes.patch.sig | Bin 0 -> 594 bytes ...ule-add-module-kallsyms-on-each-addr.patch | 95 + ...add-module-kallsyms-on-each-addr.patch.sig | Bin 0 -> 594 bytes ...p-weak-function-locations-of-modules.patch | 113 + ...ak-function-locations-of-modules.patch.sig | Bin 0 -> 594 bytes .../8204-mm-opportunistic-compaction.patch | 381 + ...8204-mm-opportunistic-compaction.patch.sig | Bin 0 -> 594 bytes .../8205-mm-hint-uses-allocation-order.patch | 236 + ...05-mm-hint-uses-allocation-order.patch.sig | Bin 0 -> 594 bytes ...-mm-carry-order-and-hint-in-one-word.patch | 232 + ...carry-order-and-hint-in-one-word.patch.sig | Bin 0 -> 594 bytes ...fy-huge-page-allocations-as-failable.patch | 28 + ...uge-page-allocations-as-failable.patch.sig | Bin 0 -> 594 bytes ...9-xe-shrinker-use-opportunistic-hint.patch | 39 + ...-shrinker-use-opportunistic-hint.patch.sig | Bin 0 -> 594 bytes ...0-xe-shrinker-single-backup-decision.patch | 75 + ...-shrinker-single-backup-decision.patch.sig | Bin 0 -> 594 bytes .../8211-xe-shrinker-skip-active-vm.patch | 116 + .../8211-xe-shrinker-skip-active-vm.patch.sig | Bin 0 -> 594 bytes ...12-xe-shrinker-backup-in-kswapd-only.patch | 95 + ...e-shrinker-backup-in-kswapd-only.patch.sig | Bin 0 -> 594 bytes .../8213-xe-hibernation-writeback-gated.patch | 112 + ...3-xe-hibernation-writeback-gated.patch.sig | Bin 0 -> 594 bytes pkgbuilds/linux-omarchy-muqss/PKGBUILD | 1009 + pkgbuilds/linux-omarchy-muqss/config.x86_64 | 12784 ++++++ ...D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7.asc | 1615 + ...7F28654894E3BD457199BE38DBBDC86092693E.asc | 78 + ...AF11C65A2970B130ABE3C479BE3E4300411886.asc | 38 + 304 files changed, 141852 insertions(+) create mode 100644 pkgbuilds/linux-omarchy-muqss/.omarchy/package.json create mode 100644 pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0011-kbuild-optimize-for-performance-o3.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0011-kbuild-optimize-for-performance-o3.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0121-smp-preempt.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0121-smp-preempt.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0144-sched-nohz-idle-core.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0144-sched-nohz-idle-core.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0212-pm-fixes-2.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0212-pm-fixes-2.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0250-zsmalloc.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0250-zsmalloc.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0290-zstd-bmi2-fallback-aliases.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0290-zstd-bmi2-fallback-aliases.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0292-crypto-zstd-defer-cstream-init.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0292-crypto-zstd-defer-cstream-init.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0293-crypto-zstd-defer-dstream-init.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0293-crypto-zstd-defer-dstream-init.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0295-af-alg-restrict.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0295-af-alg-restrict.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0360-gpu-mem-cgroup.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0360-gpu-mem-cgroup.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0401-psr2-early-transport-panels.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0401-psr2-early-transport-panels.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0420-safe-window.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0420-safe-window.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0430-fbc.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0430-fbc.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0451-amd-vtem-tmds-links.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0451-amd-vtem-tmds-links.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0452-amd-hdmi-frl-default.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0452-amd-hdmi-frl-default.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0472-amdgpu-userq-post-reset-error.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0472-amdgpu-userq-post-reset-error.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0474-amd-display-oled-vesa-backlight.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0474-amd-display-oled-vesa-backlight.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0516-rt766-stream-config-type.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0516-rt766-stream-config-type.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0541-cvs-nova-lake-acpi-id.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0541-cvs-nova-lake-acpi-id.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0544-ipu-bridge-lenovo-x9-upside-down.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0650-wireguard-tstamp-type.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0650-wireguard-tstamp-type.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8209-xe-shrinker-use-opportunistic-hint.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8209-xe-shrinker-use-opportunistic-hint.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8210-xe-shrinker-single-backup-decision.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8210-xe-shrinker-single-backup-decision.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch create mode 100644 pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch.sig create mode 100644 pkgbuilds/linux-omarchy-muqss/PKGBUILD create mode 100644 pkgbuilds/linux-omarchy-muqss/config.x86_64 create mode 100644 pkgbuilds/linux-omarchy-muqss/keys/pgp/12D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7.asc create mode 100644 pkgbuilds/linux-omarchy-muqss/keys/pgp/647F28654894E3BD457199BE38DBBDC86092693E.asc create mode 100644 pkgbuilds/linux-omarchy-muqss/keys/pgp/ABAF11C65A2970B130ABE3C479BE3E4300411886.asc diff --git a/pkgbuilds/linux-omarchy-muqss/.omarchy/package.json b/pkgbuilds/linux-omarchy-muqss/.omarchy/package.json new file mode 100644 index 0000000..c3ed389 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "skip_build": true +} diff --git a/pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch b/pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch new file mode 100644 index 0000000..497422b --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch @@ -0,0 +1,55 @@ +diff --git a/kernel/fork.c b/kernel/fork.c +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -127,6 +127,12 @@ + + #include + ++#ifdef CONFIG_USER_NS ++static int unprivileged_userns_clone = 1; ++#else ++#define unprivileged_userns_clone 1 ++#endif ++ + /* + * Minimum number of threads to boot the kernel + */ +@@ -2092,6 +2098,11 @@ __latent_entropy struct task_struct *copy_process( + return ERR_PTR(-EPERM); + } + ++ if ((clone_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ if (!capable(CAP_SYS_ADMIN)) ++ return ERR_PTR(-EPERM); ++ } ++ + /* + * Force any signals received before this point to be delivered + * before the fork happens. Collect up signals sent to multiple +@@ -3165,6 +3176,10 @@ static int check_unshare_flags(unsigned long unshare_flags) + if (!current_is_single_threaded()) + return -EINVAL; + } ++ if ((unshare_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ } + + return 0; + } +@@ -3400,6 +3415,15 @@ static const struct ctl_table fork_sysctl_table[] = { + .mode = 0644, + .proc_handler = sysctl_max_threads, + }, ++#ifdef CONFIG_USER_NS ++ { ++ .procname = "unprivileged_userns_clone", ++ .data = &unprivileged_userns_clone, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec, ++ }, ++#endif + }; + + static int __init init_fork_sysctl(void) diff --git a/pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch.sig b/pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..a82892436f1b7324dd2d93315814f48caa5c45d7 GIT binary patch literal 594 zcmV-Y0s=j00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=M)SO0H?)iWIm+0a|^U97%KN< z4mw$O*vDTN!OX&c=(F5$Us@D3J~m!$j-)hLX;jKsc#BDp>w)qm$EMARG)QNP$Ky3q zXBE0v@zeFPCS9k5J0Rj9e%`=K-+d#AVG-QbnANUsAkPR?2;Q`TA|ARrFwDNRQQUYc z-;%qq4Sa641fy#JC*CD^+eAKdt_vhv(_=P1vH+RM~Uf zTd$;Dm>eBliCnl{zK65R+_WoQ`$;*|8exvdMaC~f>A6XHt=qbU>Z_|#erFlt`&>lwXm|BCsbTSvudir##83%MqUVLedn>|tGxt?KUB)>o*EsOwb#Yr=~#iu}RG zw-hb?n-qA^5&K_gTXsvbpRK5SzVpvTUyn3N9>YXmQXamn{)oO*?AoB;*uk7tj(dgH z?}hRFfz%QK`{Pwqw33@~cQLxLRtVOHWOH$n3VdW!h5XS860fy<5f*PHoPgfo(aB32 zEX{50hIEZ5DCyUJxbmt+ g=vz0PRzhA4eUt7f5<(pkPU$vMqhdq*Dn?7-Mk#x&26gTWff&@xFV**qQYbnqh8H_v=T!UIj|aZmk&)IqKqw`-%aLj-ApWJkGM{rSBl(2O3 zJx=sQv+m$F9a|pq1>08#P5a6*8YtK?@hBkAwY3+bw@HjMg2|ON8@gV99dt`2#DsWU gn%;jfa-pCU8IdKB;87E;zlk6X#*c3VHCd|K6}X5Qo&W#< literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch b/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch new file mode 100644 index 0000000..16f35c4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch @@ -0,0 +1,17203 @@ +diff --git a/arch/m68k/coldfire/pci.c b/arch/m68k/coldfire/pci.c +--- a/arch/m68k/coldfire/pci.c ++++ b/arch/m68k/coldfire/pci.c +@@ -226,7 +226,7 @@ static int __init mcf_pci_init(void) + /* Turn of PCI reset, and wait for devices to settle */ + mcf_write32(0, PCIGSCR); + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(200)); ++ schedule_msec_hrtimeout(200); + + + pci_add_resource(&bridge->windows, &ioport_resource); +diff --git a/arch/powerpc/platforms/powernv/vas-window.c b/arch/powerpc/platforms/powernv/vas-window.c +--- a/arch/powerpc/platforms/powernv/vas-window.c ++++ b/arch/powerpc/platforms/powernv/vas-window.c +@@ -1185,7 +1185,7 @@ static void poll_window_credits(struct pnv_vas_window *window) + if (creds < window->vas_win.wcreds_max) { + val = 0; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout(10); + count++; + /* + * Process can not close send window until all credits are +@@ -1218,7 +1218,7 @@ static void poll_window_busy_state(struct pnv_vas_window *window) + if (busy) { + val = 0; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout(10); + count++; + /* + * Takes around few milliseconds to process all pending +diff --git a/block/Makefile b/block/Makefile +--- a/block/Makefile ++++ b/block/Makefile +@@ -14,6 +14,7 @@ obj-y := bdev.o fops.o bio.o elevator.o blk-core.o blk-sysfs.o \ + disk-events.o blk-ia-ranges.o early-lookup.o + + obj-$(CONFIG_BLK_ERROR_INJECTION) += error-injection.o ++obj-$(CONFIG_MUQSS_IOTIME) += blk-iotime.o + obj-$(CONFIG_BLK_DEV_BSG_COMMON) += bsg.o + obj-$(CONFIG_BLK_DEV_BSGLIB) += bsg-lib.o + obj-$(CONFIG_BLK_CGROUP) += blk-cgroup.o +diff --git a/block/bio.c b/block/bio.c +--- a/block/bio.c ++++ b/block/bio.c +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + + #include + #include "blk.h" +@@ -185,6 +186,8 @@ void bio_uninit(struct bio *bio) + bio->bi_blkg = NULL; + } + #endif ++ muqss_iotime_put_owner(bio); ++ + if (bio_integrity(bio)) + bio_integrity_free(bio); + +@@ -234,13 +237,18 @@ void bio_init(struct bio *bio, struct block_device *bdev, struct bio_vec *table, + bio->bi_private = NULL; + #ifdef CONFIG_BLK_CGROUP + bio->bi_blkg = NULL; +- bio->issue_time_ns = 0; + if (bdev) + bio_associate_blkg(bio); + #ifdef CONFIG_BLK_CGROUP_IOCOST + bio->bi_iocost_cost = 0; + #endif + #endif ++#if defined(CONFIG_BLK_CGROUP) || defined(CONFIG_MUQSS_IOTIME) ++ bio->issue_time_ns = 0; ++#endif ++#ifdef CONFIG_MUQSS_IOTIME ++ bio->bi_muqss_owner = NULL; ++#endif + #ifdef CONFIG_BLK_INLINE_ENCRYPTION + bio->bi_crypt_context = NULL; + #endif +@@ -868,6 +876,7 @@ static int __bio_clone(struct bio *bio, struct bio *bio_src, gfp_t gfp) + bio_set_flag(bio, BIO_REMAPPED); + bio_clone_blkg_association(bio, bio_src); + } ++ muqss_iotime_clone_owner(bio, bio_src); + + if (bio_crypt_clone(bio, bio_src, gfp) < 0) + return -ENOMEM; +@@ -1865,6 +1874,7 @@ void bio_endio(struct bio *bio) + bio->bi_blkg = NULL; + } + #endif ++ muqss_iotime_put_owner(bio); + + if (bio->bi_end_io) + bio->bi_end_io(bio); +diff --git a/block/blk-iotime.c b/block/blk-iotime.c +new file mode 100644 +--- /dev/null ++++ b/block/blk-iotime.c +@@ -0,0 +1,753 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * I/O aware scheduling support for MuQSS. ++ * ++ * The CPU scheduler can see how much CPU a task uses but nothing of what it ++ * costs the storage device. This attaches an rq_qos policy that attributes ++ * block device time back to the task that caused the I/O, so that cost is ++ * visible to the scheduler. ++ * ++ * Two different quantities are recorded and deliberately kept apart: ++ * ++ * Latency is what a waiting task experiences, measured per bio from ++ * bio->issue_time_ns, which is stamped in blk_mq_submit_bio() before merging ++ * and request allocation. It therefore includes plug, merge and queue wait as ++ * well as service. ++ * ++ * Occupancy is what the task cost everybody else, measured per request from ++ * rq->io_start_time_ns at dispatch to rq->muqss_done_ns at completion. This is ++ * the quantity the scheduler charges for. ++ * ++ * Attribution has two halves. Reads and synchronous writes are submitted by ++ * the originating task, so the owner is simply current. Writeback is not: the ++ * dirtier is recorded on the folio when it is dirtied and recovered when the ++ * flusher thread submits the I/O. See the owner table below. ++ * ++ * The owner table earns its keep a second time over. A kworker running a work ++ * item declares itself to be acting for whoever queued that work, which both ++ * attributes the I/O the work item issues and lets the CPU time it costs be ++ * charged back as kern_debt_ns. See muqss_kerntime_begin(). ++ * ++ * The occupancy is charged one for one against the deadline, with nothing to ++ * tune it by: the accounting below and the scheduling it feeds are one ++ * feature, and only CONFIG_MUQSS_IOTIME=n removes either. See ++ * consume_iotime_penalty() in kernel/sched/MuQSS.c and ++ * MuQSS-iotime-design.md. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "blk.h" ++#include "blk-mq.h" ++#include "blk-mq-debugfs.h" ++#include "blk-rq-qos.h" ++#include "blk-stat.h" ++ ++struct blk_iotime { ++ struct rq_qos rqos; ++ ++ /* Unattributed device busy time, see comment above. */ ++ atomic64_t occupancy_ns; ++ atomic64_t requests; ++ ++ /* ++ * Bios that completed with no owner recorded. Counted so the size of ++ * the attribution gap stays visible rather than silently skewing the ++ * per task numbers. ++ */ ++ atomic64_t unattributed; ++ ++ /* Requests disowned because a merge brought in another task's bio. */ ++ atomic64_t mixed; ++ ++ /* ++ * Requests that reached completion with no captured timestamp, and so ++ * could not be measured at all. See muqss_iotime_done(). ++ */ ++ atomic64_t unstamped; ++ ++ /* Bios that completed with an owner but no submit timestamp. */ ++ atomic64_t nostamp_bio; ++}; ++ ++static inline struct blk_iotime *BLK_IOTIME(struct rq_qos *rqos) ++{ ++ return container_of(rqos, struct blk_iotime, rqos); ++} ++ ++/* ++ * Owner table. ++ * ++ * Reads are attributable at submit time because the submitting task is the ++ * originating one. Writeback is not: the folio is dirtied by one task and ++ * written out much later by a flusher thread. The dirtier is therefore ++ * recorded on the folio itself, in spare page->flags bits, as an index into ++ * this table. ++ * ++ * Only MUQSS_IOWNER_WIDTH bits are available, so the table is small and the ++ * index carries no generation. Slot 0 is reserved to mean "no owner". A slot ++ * is held from the task's first dirty until it exits, and the table holds a ++ * reference for that whole period, so a resolved pointer is always a live ++ * task_struct. ++ * ++ * Slots are released on exit and never stolen from a live task, so a resolved ++ * index is never charged to the wrong task. The cost of that choice is that ++ * beyond IOWNER_SLOTS concurrent dirtiers, further tasks get no slot and their ++ * writeback goes unattributed; iowner_exhausted counts it. ++ * ++ * Writeback still in flight for a task that has exited resolves to nothing, ++ * which is correct: there is no longer anybody to charge. ++ */ ++#define IOWNER_SLOTS (1U << MUQSS_IOWNER_SHIFT) ++ ++static struct task_struct *iowner_table[IOWNER_SLOTS]; ++/* Work can be queued with a raw runqueue lock held (for example by PSI). */ ++static DEFINE_RAW_SPINLOCK(iowner_lock); ++ ++static atomic64_t iowner_exhausted; ++ ++static atomic64_t iowner_proxied; ++ ++/* Work items charged back to a task, and the CPU time they cost. */ ++static atomic64_t kernwork_charged; ++static atomic64_t kernwork_ns; ++ ++/* ++ * Slot for @tsk, allocating one on first use. ++ * ++ * @tsk is not necessarily current: an io-wq worker allocates on behalf of the ++ * task that queued the request it is running, so that pages it dirties are ++ * tagged with the real owner. The caller holds a reference to @tsk. ++ */ ++static unsigned int iotime_task_slot(struct task_struct *tsk) ++{ ++ unsigned int slot, scan; ++ unsigned long flags; ++ ++ if (!MUQSS_IOWNER_WIDTH) ++ return 0; ++ ++ slot = READ_ONCE(tsk->io_owner_slot); ++ if (slot) ++ return slot; ++ ++ if (tsk->flags & (PF_KTHREAD | PF_EXITING)) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iowner_lock, flags); ++ ++ /* ++ * Recheck PF_EXITING under the lock. It is set well before ++ * muqss_iotime_release_slot() runs, and that also takes this lock, so ++ * a task seen here as not exiting either has not reached exit or will ++ * release the slot we are about to hand it. Without the recheck a slot ++ * claimed for a remote task racing through exit would never be freed. ++ */ ++ if (tsk->flags & PF_EXITING) ++ goto out; ++ ++ /* Raced with another dirty on the same task. */ ++ if (tsk->io_owner_slot) { ++ slot = tsk->io_owner_slot; ++ goto out; ++ } ++ ++ /* Prefer a free slot. */ ++ for (scan = 1; scan < IOWNER_SLOTS; scan++) { ++ if (!iowner_table[scan]) { ++ slot = scan; ++ goto claim; ++ } ++ } ++ ++ /* ++ * Table full of live dirtiers. Leave this task unattributed rather ++ * than evicting somebody still using their slot: a stolen slot would ++ * silently charge one task's writeback to another, which is worse ++ * than not charging it at all. ++ */ ++ atomic64_inc(&iowner_exhausted); ++ slot = 0; ++ goto out; ++ ++claim: ++ iowner_table[slot] = get_task_struct(tsk); ++ WRITE_ONCE(tsk->io_owner_slot, slot); ++out: ++ raw_spin_unlock_irqrestore(&iowner_lock, flags); ++ return slot; ++} ++ ++unsigned int muqss_iotime_owner_slot(void) ++{ ++ struct task_struct *tsk = current; ++ struct task_struct *owner; ++ ++ /* ++ * A thread running inside a proxy window dirties pages on behalf of ++ * the task that window belongs to, so tag them with that task. Doing ++ * otherwise would both misattribute the writeback and, for io-wq ++ * workers, let a churn of short lived workers consume the whole slot ++ * table. ++ * ++ * An io-wq worker outside a window has nobody to speak for and must ++ * not claim a slot of its own. A kworker outside a window needs no ++ * such test: iotime_task_slot() refuses PF_KTHREAD outright. ++ */ ++ owner = READ_ONCE(tsk->io_owner_override); ++ if (owner) ++ tsk = owner; ++ else if (tsk->flags & PF_IO_WORKER) ++ return 0; ++ ++ return iotime_task_slot(tsk); ++} ++ ++struct task_struct *muqss_iotime_proxy_begin(struct task_struct *owner) ++{ ++ struct task_struct *prev = current->io_owner_override; ++ ++ WRITE_ONCE(current->io_owner_override, owner); ++ return prev; ++} ++ ++void muqss_iotime_proxy_end(struct task_struct *prev) ++{ ++ WRITE_ONCE(current->io_owner_override, prev); ++} ++ ++void muqss_work_set_owner(struct work_struct *work) ++{ ++ /* Interrupt work does not belong to the task it interrupted. */ ++ work->muqss_owner_slot = in_task() ? muqss_iotime_owner_slot() : 0; ++} ++ ++void muqss_kerntime_begin(struct muqss_kern_window *w, unsigned int slot) ++{ ++ w->prev = NULL; ++ w->start = 0; ++ ++ /* ++ * Resolving the slot is what costs here, so it gates everything else. ++ * Work queued by kernel threads on their own account carries slot 0 ++ * and leaves this function having done one branch, which matters: ++ * timers, readahead, RCU and any amount of driver housekeeping run ++ * through work items that belong to nobody. ++ */ ++ w->owner = muqss_iotime_owner_task(slot); ++ if (!w->owner) ++ return; ++ ++ w->prev = muqss_iotime_proxy_begin(w->owner); ++ w->start = muqss_task_runtime_live(); ++} ++ ++void muqss_kerntime_end(struct muqss_kern_window *w) ++{ ++ s64 ns; ++ ++ if (!w->owner) ++ return; ++ ++ ns = muqss_task_runtime_live() - w->start; ++ muqss_iotime_proxy_end(w->prev); ++ ++ /* ++ * Runtime rather than elapsed time, so a worker preempted for a whole ++ * scheduling round does not hand the bill for it to the task it is ++ * working for. Negative or absurd deltas are possible if the two ends ++ * of the window landed either side of a clock warp, and are dropped ++ * rather than clamped: there is no sensible value to substitute. ++ */ ++ if (ns > 0) { ++ atomic64_add(ns, &w->owner->kern_time_ns); ++ atomic64_add(ns, &w->owner->kern_debt_ns); ++ atomic64_inc(&kernwork_charged); ++ atomic64_add(ns, &kernwork_ns); ++ } ++ ++ put_task_struct(w->owner); ++ w->owner = NULL; ++} ++ ++struct task_struct *muqss_iotime_owner_task(unsigned int slot) ++{ ++ struct task_struct *tsk = NULL; ++ unsigned long flags; ++ ++ if (!slot || slot >= IOWNER_SLOTS) ++ return NULL; ++ ++ raw_spin_lock_irqsave(&iowner_lock, flags); ++ if (iowner_table[slot]) ++ tsk = get_task_struct(iowner_table[slot]); ++ raw_spin_unlock_irqrestore(&iowner_lock, flags); ++ ++ return tsk; ++} ++ ++void muqss_iotime_release_slot(struct task_struct *p) ++{ ++ unsigned int slot = READ_ONCE(p->io_owner_slot); ++ unsigned long flags; ++ ++ if (!slot) ++ return; ++ ++ raw_spin_lock_irqsave(&iowner_lock, flags); ++ if (iowner_table[slot] == p) { ++ iowner_table[slot] = NULL; ++ WRITE_ONCE(p->io_owner_slot, 0); ++ raw_spin_unlock_irqrestore(&iowner_lock, flags); ++ put_task_struct(p); ++ return; ++ } ++ WRITE_ONCE(p->io_owner_slot, 0); ++ raw_spin_unlock_irqrestore(&iowner_lock, flags); ++} ++ ++void muqss_iotime_dirty_folio(struct folio *folio) ++{ ++ unsigned int slot = muqss_iotime_owner_slot(); ++ ++ folio_set_io_owner(folio, slot); ++} ++ ++void muqss_iotime_task_init(struct task_struct *p) ++{ ++ atomic64_set(&p->io_latency_ns, 0); ++ atomic64_set(&p->io_count, 0); ++ atomic64_set(&p->io_occupancy_ns, 0); ++ atomic64_set(&p->io_debt_ns, 0); ++ atomic64_set(&p->kern_time_ns, 0); ++ atomic64_set(&p->kern_debt_ns, 0); ++ p->io_owner_slot = 0; ++ p->io_owner_override = NULL; ++} ++ ++void muqss_iotime_set_owner(struct bio *bio) ++{ ++ struct task_struct *tsk = current; ++ ++ if (bio->bi_muqss_owner) ++ return; ++ ++ /* ++ * Not in task context: current is whatever this interrupted, which ++ * has nothing to do with the I/O. Bios submitted from softirq or hard ++ * IRQ, such as a filesystem completing one write by starting another, ++ * would otherwise be charged to a bystander. ++ */ ++ if (!in_task()) ++ return; ++ ++ /* ++ * Passthrough requests are not issued on any task's behalf. Neither ++ * is the flush machinery's own empty bio, but REQ_PREFLUSH is also set ++ * on an ordinary write that wants a cache flush ahead of it, and that ++ * write is somebody's. Test for the payload rather than the flag. ++ */ ++ if (blk_op_is_passthrough(bio->bi_opf)) ++ return; ++ if ((bio->bi_opf & REQ_PREFLUSH) && !bio_has_data(bio)) ++ return; ++ ++ if (tsk->flags & (PF_KTHREAD | PF_IO_WORKER)) { ++ struct task_struct *owner; ++ struct folio *folio; ++ ++ /* ++ * io_uring punted this submission to an io-wq worker, which ++ * published the task that queued the request. Charge that ++ * task: the worker is an anonymous thread that exists only to ++ * run somebody else's I/O. ++ * ++ * Writeback that happens to be issued from inside the same ++ * window is charged here too, which is a small misattribution ++ * within one application rather than a loss. ++ */ ++ owner = READ_ONCE(tsk->io_owner_override); ++ if (owner) { ++ atomic64_inc(&iowner_proxied); ++ bio->bi_muqss_owner = get_task_struct(owner); ++ return; ++ } ++ ++ /* ++ * Writeback. current is a flusher thread rather than the task ++ * that dirtied the data, so recover the owner from the folio, ++ * where it was recorded at dirty time. ++ * ++ * The first page decides for the whole bio. A writeback bio ++ * normally covers one file's pages dirtied by one task, and ++ * reading every page to take a vote would put a loop on the ++ * submit path for a heuristic that does not need it. ++ * ++ * Cloned bios are skipped: their bi_io_vec belongs to the ++ * parent, and muqss_iotime_clone_owner() has already carried ++ * the owner across. ++ */ ++ if (op_is_write(bio->bi_opf) && bio->bi_vcnt && ++ !bio_flagged(bio, BIO_CLONED)) { ++ folio = page_folio(bio->bi_io_vec[0].bv_page); ++ bio->bi_muqss_owner = ++ muqss_iotime_owner_task(folio_io_owner(folio)); ++ return; ++ } ++ ++ /* ++ * An io_uring SQPOLL thread submits for whoever shares its ++ * ring, and the request does not record which task that was; ++ * with IORING_SETUP_ATTACH_WQ it need not be a single one. ++ * The thread is a long lived schedulable task in its own ++ * right though, so charge it directly rather than lose the ++ * time: the device cost is real, and the sq thread is the ++ * entity the scheduler can actually act on. ++ * ++ * Kernel threads proper get nothing. ++ */ ++ if (tsk->flags & PF_IO_WORKER) ++ bio->bi_muqss_owner = get_task_struct(tsk); ++ return; ++ } ++ ++ bio->bi_muqss_owner = get_task_struct(tsk); ++} ++ ++void muqss_iotime_put_owner(struct bio *bio) ++{ ++ if (bio->bi_muqss_owner) { ++ put_task_struct(bio->bi_muqss_owner); ++ bio->bi_muqss_owner = NULL; ++ } ++} ++ ++void muqss_iotime_clone_owner(struct bio *bio, struct bio *bio_src) ++{ ++ muqss_iotime_put_owner(bio); ++ ++ if (bio_src->bi_muqss_owner) ++ bio->bi_muqss_owner = get_task_struct(bio_src->bi_muqss_owner); ++} ++ ++/* ++ * Per bio completion. The owner reference is not dropped here: bio_endio() ++ * and bio_uninit() release it alongside the blkg reference, which keeps the ++ * lifetime rules identical to the ones the block layer already follows. ++ */ ++static void muqss_iotime_done_bio(struct rq_qos *rqos, struct bio *bio) ++{ ++ struct blk_iotime *bit = BLK_IOTIME(rqos); ++ struct task_struct *tsk = bio->bi_muqss_owner; ++ u64 now; ++ ++ if (!tsk) { ++ atomic64_inc(&bit->unattributed); ++ return; ++ } ++ if (!bio->issue_time_ns) { ++ atomic64_inc(&bit->nostamp_bio); ++ return; ++ } ++ ++ now = blk_time_get_ns(); ++ if (now <= bio->issue_time_ns) ++ return; ++ ++ atomic64_add(now - bio->issue_time_ns, &tsk->io_latency_ns); ++ atomic64_inc(&tsk->io_count); ++} ++ ++/* ++ * A request is being built from its first bio. Take our own reference for the ++ * request: the bio's reference is released by bio_endio() during ++ * blk_update_request(), which runs before rq_qos_done(), so borrowing it would ++ * leave a dangling pointer at completion. ++ */ ++static void muqss_iotime_track(struct rq_qos *rqos, struct request *rq, ++ struct bio *bio) ++{ ++ if (bio->bi_muqss_owner) ++ rq->muqss_owner = get_task_struct(bio->bi_muqss_owner); ++} ++ ++/* ++ * A further bio is merging into an existing request. Occupancy is measured ++ * per request and cannot be split between owners, so a request that ends up ++ * serving more than one task is disowned rather than charged to whichever ++ * task happened to be first. ++ */ ++static void muqss_iotime_merge(struct rq_qos *rqos, struct request *rq, ++ struct bio *bio) ++{ ++ if (!rq->muqss_owner || rq->muqss_owner == bio->bi_muqss_owner) ++ return; ++ ++ put_task_struct(rq->muqss_owner); ++ rq->muqss_owner = NULL; ++ atomic64_inc(&BLK_IOTIME(rqos)->mixed); ++} ++ ++/* ++ * Two whole requests are being merged: next's bios are about to be appended to ++ * rq and next freed. The rq_qos ->merge hook does not cover this, it only sees ++ * a bio being merged into a request, so without this the survivor keeps its own ++ * owner and is charged for both requests' device time. ++ * ++ * Called from attempt_merge() rather than being an rq_qos op because the ++ * framework has no request-to-request callback to hang it on. ++ */ ++void muqss_iotime_merge_requests(struct request *rq, struct request *next) ++{ ++ struct rq_qos *rqos; ++ ++ if (!rq->muqss_owner || rq->muqss_owner == next->muqss_owner) ++ return; ++ ++ put_task_struct(rq->muqss_owner); ++ rq->muqss_owner = NULL; ++ ++ rqos = rq_qos_id(rq->q, RQ_QOS_MUQSS_IOTIME); ++ if (rqos) ++ atomic64_inc(&BLK_IOTIME(rqos)->mixed); ++} ++ ++/* Per request completion. This is where device occupancy is attributed. */ ++static void muqss_iotime_done(struct rq_qos *rqos, struct request *rq) ++{ ++ struct blk_iotime *bit = BLK_IOTIME(rqos); ++ struct task_struct *tsk = rq->muqss_owner; ++ u64 now, occupancy; ++ ++ if (!(rq->rq_flags & RQF_STATS) || !rq->io_start_time_ns) ++ goto out; ++ ++ /* ++ * Use the timestamp captured when the request completed, not one ++ * taken here: rq_qos_done() runs after blk_update_request() has ++ * completed every bio, so measuring now would fold the completion ++ * path into what is supposed to be device service time. That bias is ++ * roughly constant per request, which would quietly turn part of the ++ * charge into a per-I/O-count charge and penalise small random I/O. ++ * ++ * Consuming the stamp also makes this idempotent. rq_qos_done() runs ++ * twice for a request with an end_io handler that frees it: once from ++ * __blk_mq_end_request() and again from blk_mq_free_request(). Every ++ * policy has to tolerate that. Clearing muqss_owner alone stopped the ++ * task being charged twice but left the device totals below counting ++ * the same request on both passes. ++ * ++ * A request that never went through blk_mq_end_request() has no stamp ++ * and is not measured. Timing it here instead would report the ++ * completion path as device time, which is the bias this exists to ++ * avoid; unstamped counts them so the loss stays visible. ++ */ ++ now = rq->muqss_done_ns; ++ if (!now) { ++ atomic64_inc(&bit->unstamped); ++ goto out; ++ } ++ rq->muqss_done_ns = 0; ++ ++ if (now <= rq->io_start_time_ns) ++ goto out; ++ ++ occupancy = now - rq->io_start_time_ns; ++ atomic64_add(occupancy, &bit->occupancy_ns); ++ atomic64_inc(&bit->requests); ++ ++ if (tsk) { ++ atomic64_add(occupancy, &tsk->io_occupancy_ns); ++ atomic64_add(occupancy, &tsk->io_debt_ns); ++ } ++out: ++ if (tsk) { ++ put_task_struct(tsk); ++ rq->muqss_owner = NULL; ++ } ++} ++ ++static void muqss_iotime_exit(struct rq_qos *rqos) ++{ ++ struct blk_iotime *bit = BLK_IOTIME(rqos); ++ ++ /* ++ * QUEUE_FLAG_BIO_ISSUE_TIME is deliberately left set. blk-iolatency ++ * uses the same flag, and the queue is being torn down here anyway. ++ */ ++ blk_stat_disable_accounting(rqos->disk->queue); ++ kfree(bit); ++} ++ ++#ifdef CONFIG_BLK_DEBUG_FS ++static int muqss_iotime_occupancy_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->occupancy_ns)); ++ return 0; ++} ++ ++static int muqss_iotime_requests_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->requests)); ++ return 0; ++} ++ ++static int muqss_iotime_unattributed_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->unattributed)); ++ return 0; ++} ++ ++static int muqss_iotime_mixed_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&BLK_IOTIME(rqos)->mixed)); ++ return 0; ++} ++ ++static int muqss_iotime_unstamped_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->unstamped)); ++ return 0; ++} ++ ++static int muqss_iotime_nostamp_bio_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->nostamp_bio)); ++ return 0; ++} ++ ++static int muqss_iotime_exhausted_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&iowner_exhausted)); ++ return 0; ++} ++ ++static int muqss_iotime_proxied_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&iowner_proxied)); ++ return 0; ++} ++ ++static int muqss_iotime_owner_width_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%u\n", MUQSS_IOWNER_WIDTH); ++ return 0; ++} ++ ++static int muqss_iotime_kernwork_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&kernwork_charged)); ++ return 0; ++} ++ ++static int muqss_iotime_kernwork_ns_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&kernwork_ns)); ++ return 0; ++} ++ ++static const struct blk_mq_debugfs_attr muqss_iotime_debugfs_attrs[] = { ++ {"occupancy_ns", 0400, muqss_iotime_occupancy_show}, ++ {"requests", 0400, muqss_iotime_requests_show}, ++ {"unattributed", 0400, muqss_iotime_unattributed_show}, ++ {"mixed", 0400, muqss_iotime_mixed_show}, ++ {"unstamped", 0400, muqss_iotime_unstamped_show}, ++ {"nostamp_bio", 0400, muqss_iotime_nostamp_bio_show}, ++ {"owner_slots_exhausted", 0400, muqss_iotime_exhausted_show}, ++ {"proxied", 0400, muqss_iotime_proxied_show}, ++ {"owner_tag_bits", 0400, muqss_iotime_owner_width_show}, ++ {"kernwork", 0400, muqss_iotime_kernwork_show}, ++ {"kernwork_ns", 0400, muqss_iotime_kernwork_ns_show}, ++ {}, ++}; ++#endif ++ ++static const struct rq_qos_ops muqss_iotime_ops = { ++ .track = muqss_iotime_track, ++ .merge = muqss_iotime_merge, ++ .done = muqss_iotime_done, ++ .done_bio = muqss_iotime_done_bio, ++ .exit = muqss_iotime_exit, ++#ifdef CONFIG_BLK_DEBUG_FS ++ .debugfs_attrs = muqss_iotime_debugfs_attrs, ++#endif ++}; ++ ++void muqss_iotime_enable(struct gendisk *disk) ++{ ++ struct request_queue *q = disk->queue; ++ unsigned int memflags; ++ struct blk_iotime *bit; ++ int ret; ++ ++ if (!queue_is_mq(q)) ++ return; ++ ++ /* ++ * Only attach to queues backed by real hardware. ++ * ++ * A stacking driver that uses blk-mq -- loop is the common one -- ++ * serves its requests by doing I/O to another block device, which runs ++ * this policy too. Accounting both charges the task twice for one ++ * trip to the disk, and with the deadline charge live that is a real ++ * distortion rather than a cosmetic one. ++ * ++ * Drivers for physical devices pass their parent through ++ * device_add_disk(): nvme passes ctrl->device, sd its scsi_device, ++ * virtio_blk its virtio device. Virtual ones call add_disk(), which ++ * is device_add_disk(NULL, ...) -- loop, brd, zram, nbd. So the ++ * absence of a parent is a serviceable test for "not a device whose ++ * busy time means anything". ++ * ++ * Bio based stacking drivers (dm, md) never reach here at all, having ++ * failed queue_is_mq() above. ++ */ ++ if (!disk_to_dev(disk)->parent) ++ return; ++ ++ bit = kzalloc_obj(*bit); ++ if (!bit) ++ return; ++ ++ mutex_lock(&q->rq_qos_mutex); ++ ret = rq_qos_add(&bit->rqos, disk, RQ_QOS_MUQSS_IOTIME, ++ &muqss_iotime_ops); ++ mutex_unlock(&q->rq_qos_mutex); ++ if (ret) { ++ kfree(bit); ++ return; ++ } ++ ++ /* Needed for rq->io_start_time_ns to be stamped at dispatch. */ ++ blk_stat_enable_accounting(q); ++ /* Needed for bio->issue_time_ns to be stamped at submit. */ ++ blk_queue_flag_set(QUEUE_FLAG_BIO_ISSUE_TIME, q); ++ ++ memflags = blk_debugfs_lock(q); ++ blk_mq_debugfs_register_rq_qos(q); ++ blk_debugfs_unlock(q, memflags); ++} +diff --git a/block/blk-merge.c b/block/blk-merge.c +--- a/block/blk-merge.c ++++ b/block/blk-merge.c +@@ -12,6 +12,8 @@ + + #include + ++#include ++ + #include "blk.h" + #include "blk-mq-sched.h" + #include "blk-rq-qos.h" +@@ -848,6 +850,7 @@ static struct request *attempt_merge(struct request_queue *q, + * 'next' is going away, so update stats accordingly + */ + blk_account_io_merge_request(next); ++ muqss_iotime_merge_requests(req, next); + + trace_block_rq_merge(next); + +diff --git a/block/blk-mq-debugfs.c b/block/blk-mq-debugfs.c +--- a/block/blk-mq-debugfs.c ++++ b/block/blk-mq-debugfs.c +@@ -757,6 +757,8 @@ static const char *rq_qos_id_to_name(enum rq_qos_id id) + return "latency"; + case RQ_QOS_COST: + return "cost"; ++ case RQ_QOS_MUQSS_IOTIME: ++ return "iotime"; + } + return "unknown"; + } +diff --git a/block/blk-mq.c b/block/blk-mq.c +--- a/block/blk-mq.c ++++ b/block/blk-mq.c +@@ -41,6 +41,7 @@ + #include "blk-stat.h" + #include "blk-mq-sched.h" + #include "blk-rq-qos.h" ++#include + + static DEFINE_PER_CPU(struct llist_head, blk_cpu_done); + static DEFINE_PER_CPU(call_single_data_t, blk_cpu_csd); +@@ -398,12 +399,48 @@ static inline void blk_mq_rq_time_init(struct request *rq, u64 alloc_time_ns) + #endif + } + ++/* ++ * Record the completion timestamp for MuQSS I/O accounting. ++ * ++ * rq_qos_done() is reached only after blk_update_request() has run bio_endio() ++ * on every bio of the request, so a timestamp taken inside the policy is later ++ * than the request actually finished, and lands after the per bio latency ++ * stamp. Occupancy then measures longer than the end to end latency it is ++ * supposed to be a subset of. ++ * ++ * Does not overwrite: the first caller to record a stamp is the earliest and ++ * therefore the most accurate one. ++ */ ++static inline void blk_mq_set_iotime_done(struct request *rq, u64 now) ++{ ++#ifdef CONFIG_MUQSS_IOTIME ++ if (!rq->muqss_done_ns) ++ rq->muqss_done_ns = now; ++#endif ++} ++ ++/* ++ * Take the completion stamp before bio completion. Costs an extra timestamp ++ * on the completion path, which cannot be avoided by reusing the one taken ++ * for blk-stat: that one is deliberately read after blk_update_request() and ++ * moving it would change accounting shared with everybody else. ++ */ ++static inline void blk_mq_capture_iotime_done(struct request *rq) ++{ ++#ifdef CONFIG_MUQSS_IOTIME ++ if (blk_mq_need_time_stamp(rq)) ++ rq->muqss_done_ns = blk_time_get_ns(); ++#endif ++} ++ + static inline void blk_mq_bio_issue_init(struct request_queue *q, + struct bio *bio) + { +-#ifdef CONFIG_BLK_CGROUP +- if (test_bit(QUEUE_FLAG_BIO_ISSUE_TIME, &q->queue_flags)) ++#if defined(CONFIG_BLK_CGROUP) || defined(CONFIG_MUQSS_IOTIME) ++ if (test_bit(QUEUE_FLAG_BIO_ISSUE_TIME, &q->queue_flags)) { + bio->issue_time_ns = blk_time_get_ns(); ++ muqss_iotime_set_owner(bio); ++ } + #endif + } + +@@ -435,6 +472,10 @@ static struct request *blk_mq_rq_ctx_init(struct blk_mq_alloc_data *data, + + rq->part = NULL; + rq->io_start_time_ns = 0; ++#ifdef CONFIG_MUQSS_IOTIME ++ rq->muqss_owner = NULL; ++ rq->muqss_done_ns = 0; ++#endif + rq->stats_sectors = 0; + rq->nr_phys_segments = 0; + rq->nr_integrity_segments = 0; +@@ -1127,8 +1168,12 @@ static inline void __blk_mq_end_request_acct(struct request *rq, u64 now) + + inline void __blk_mq_end_request(struct request *rq, blk_status_t error) + { +- if (blk_mq_need_time_stamp(rq)) +- __blk_mq_end_request_acct(rq, blk_time_get_ns()); ++ if (blk_mq_need_time_stamp(rq)) { ++ u64 now = blk_time_get_ns(); ++ ++ blk_mq_set_iotime_done(rq, now); ++ __blk_mq_end_request_acct(rq, now); ++ } + + blk_mq_finish_request(rq); + +@@ -1144,6 +1189,7 @@ EXPORT_SYMBOL(__blk_mq_end_request); + + void blk_mq_end_request(struct request *rq, blk_status_t error) + { ++ blk_mq_capture_iotime_done(rq); + if (blk_update_request(rq, error, blk_rq_bytes(rq))) + BUG(); + __blk_mq_end_request(rq, error); +@@ -1178,8 +1224,10 @@ void blk_mq_end_request_batch(struct io_comp_batch *iob) + prefetch(rq->rq_next); + + blk_complete_request(rq); +- if (iob->need_ts) ++ if (iob->need_ts) { ++ blk_mq_set_iotime_done(rq, now); + __blk_mq_end_request_acct(rq, now); ++ } + + blk_mq_finish_request(rq); + +diff --git a/block/blk-rq-qos.h b/block/blk-rq-qos.h +--- a/block/blk-rq-qos.h ++++ b/block/blk-rq-qos.h +@@ -17,6 +17,7 @@ enum rq_qos_id { + RQ_QOS_WBT, + RQ_QOS_LATENCY, + RQ_QOS_COST, ++ RQ_QOS_MUQSS_IOTIME, + }; + + struct rq_wait { +diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c +--- a/block/blk-sysfs.c ++++ b/block/blk-sysfs.c +@@ -20,6 +20,7 @@ + #include "blk-cgroup.h" + #include "blk-throttle.h" + #include "error-injection.h" ++#include + + struct queue_sysfs_entry { + struct attribute attr; +@@ -993,6 +994,7 @@ int blk_register_queue(struct gendisk *disk) + + blk_queue_flag_set(QUEUE_FLAG_REGISTERED, q); + wbt_init_enable_default(disk); ++ muqss_iotime_enable(disk); + + /* Now everything is ready and send out KOBJ_ADD uevent */ + kobject_uevent(&disk->queue_kobj, KOBJ_ADD); +diff --git a/drivers/accessibility/speakup/speakup_acntpc.c b/drivers/accessibility/speakup/speakup_acntpc.c +--- a/drivers/accessibility/speakup/speakup_acntpc.c ++++ b/drivers/accessibility/speakup/speakup_acntpc.c +@@ -207,7 +207,7 @@ static void do_catch_up(struct spk_synth *synth) + full_time_val = full_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth_full()) { +- schedule_timeout(msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -235,7 +235,7 @@ static void do_catch_up(struct spk_synth *synth) + jiffy_delta_val = jiffy_delta->u.n.value; + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_apollo.c b/drivers/accessibility/speakup/speakup_apollo.c +--- a/drivers/accessibility/speakup/speakup_apollo.c ++++ b/drivers/accessibility/speakup/speakup_apollo.c +@@ -178,7 +178,7 @@ static void do_catch_up(struct spk_synth *synth) + if (!synth->io_ops->synth_out(synth, ch)) { + synth->io_ops->tiocmset(synth, 0, UART_MCR_RTS); + synth->io_ops->tiocmset(synth, UART_MCR_RTS, 0); +- schedule_timeout(msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + continue; + } + if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) { +@@ -188,11 +188,9 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth->io_ops->synth_out(synth, synth->procspeech)) +- schedule_timeout(msecs_to_jiffies +- (delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + else +- schedule_timeout(msecs_to_jiffies +- (full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + set_current_state(TASK_RUNNING); +diff --git a/drivers/accessibility/speakup/speakup_decext.c b/drivers/accessibility/speakup/speakup_decext.c +--- a/drivers/accessibility/speakup/speakup_decext.c ++++ b/drivers/accessibility/speakup/speakup_decext.c +@@ -189,7 +189,7 @@ static void do_catch_up(struct spk_synth *synth) + if (ch == '\n') + ch = 0x0D; + if (synth_full() || !synth->io_ops->synth_out(synth, ch)) { +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -213,8 +213,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, + flags); +- schedule_timeout(msecs_to_jiffies +- (delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_decpc.c b/drivers/accessibility/speakup/speakup_decpc.c +--- a/drivers/accessibility/speakup/speakup_decpc.c ++++ b/drivers/accessibility/speakup/speakup_decpc.c +@@ -409,7 +409,7 @@ static void do_catch_up(struct spk_synth *synth) + if (ch == '\n') + ch = 0x0D; + if (dt_sendchar(ch)) { +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -432,8 +432,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, + flags); +- schedule_timeout(msecs_to_jiffies +- (delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_dectlk.c b/drivers/accessibility/speakup/speakup_dectlk.c +--- a/drivers/accessibility/speakup/speakup_dectlk.c ++++ b/drivers/accessibility/speakup/speakup_dectlk.c +@@ -265,7 +265,7 @@ static void do_catch_up(struct spk_synth *synth) + if (ch == '\n') + ch = 0x0D; + if (synth_full_val || !synth->io_ops->synth_out(synth, ch)) { +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -289,8 +289,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, + flags); +- schedule_timeout(msecs_to_jiffies +- (delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_dtlk.c b/drivers/accessibility/speakup/speakup_dtlk.c +--- a/drivers/accessibility/speakup/speakup_dtlk.c ++++ b/drivers/accessibility/speakup/speakup_dtlk.c +@@ -221,7 +221,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth_full()) { +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -237,7 +237,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + jiffy_delta_val = jiffy_delta->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_keypc.c b/drivers/accessibility/speakup/speakup_keypc.c +--- a/drivers/accessibility/speakup/speakup_keypc.c ++++ b/drivers/accessibility/speakup/speakup_keypc.c +@@ -208,7 +208,7 @@ static void do_catch_up(struct spk_synth *synth) + full_time_val = full_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth_full()) { +- schedule_timeout(msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -241,7 +241,7 @@ static void do_catch_up(struct spk_synth *synth) + jiffy_delta_val = jiffy_delta->u.n.value; + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c +--- a/drivers/accessibility/speakup/synth.c ++++ b/drivers/accessibility/speakup/synth.c +@@ -98,7 +98,7 @@ static void _spk_do_catch_up(struct spk_synth *synth, int unicode) + else + ret = synth->io_ops->synth_out(synth, ch); + if (!ret) { +- schedule_timeout(msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + continue; + } + if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) { +@@ -108,11 +108,9 @@ static void _spk_do_catch_up(struct spk_synth *synth, int unicode) + full_time_val = full_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth->io_ops->synth_out(synth, synth->procspeech)) +- schedule_timeout( +- msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + else +- schedule_timeout( +- msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + set_current_state(TASK_RUNNING); +diff --git a/drivers/auxdisplay/hd44780_common.c b/drivers/auxdisplay/hd44780_common.c +--- a/drivers/auxdisplay/hd44780_common.c ++++ b/drivers/auxdisplay/hd44780_common.c +@@ -34,7 +34,7 @@ + /* sleeps that many milliseconds with a reschedule */ + static void long_sleep(int ms) + { +- schedule_timeout_interruptible(msecs_to_jiffies(ms)); ++ schedule_msec_hrtimeout_interruptible(ms); + } + + int hd44780_common_print(struct charlcd *lcd, int c) +diff --git a/drivers/block/swim.c b/drivers/block/swim.c +--- a/drivers/block/swim.c ++++ b/drivers/block/swim.c +@@ -330,7 +330,7 @@ static inline void swim_motor(struct swim __iomem *base, + if (swim_readbit(base, MOTOR_ON)) + break; + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } + } else if (action == OFF) { + swim_action(base, MOTOR_OFF); +@@ -349,7 +349,7 @@ static inline void swim_eject(struct swim __iomem *base) + if (!swim_readbit(base, DISK_IN)) + break; + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } + swim_select(base, RELAX); + } +@@ -373,7 +373,7 @@ static inline int swim_step(struct swim __iomem *base) + for (wait = 0; wait < HZ; wait++) { + + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + + swim_select(base, RELAX); + if (!swim_readbit(base, STEP)) +diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c +--- a/drivers/char/ipmi/ipmi_msghandler.c ++++ b/drivers/char/ipmi/ipmi_msghandler.c +@@ -3804,7 +3804,7 @@ static void cleanup_smi_msgs(struct ipmi_smi *intf) + /* Current message first, to preserve order */ + while (intf->curr_msg && !list_empty(&intf->waiting_rcv_msgs)) { + /* Wait for the message to clear out. */ +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } + + /* No need for locks, the interface is down. */ +diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c +--- a/drivers/char/ipmi/ipmi_ssif.c ++++ b/drivers/char/ipmi/ipmi_ssif.c +@@ -1282,7 +1282,7 @@ static void shutdown_ssif(void *send_info) + + /* make sure the driver is not looking for flags any more. */ + while (ssif_info->ssif_state != SSIF_IDLE) +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + + ssif_info->stopping = true; + timer_delete_sync(&ssif_info->watch_timer); +diff --git a/drivers/comedi/drivers/ni_mio_common.c b/drivers/comedi/drivers/ni_mio_common.c +--- a/drivers/comedi/drivers/ni_mio_common.c ++++ b/drivers/comedi/drivers/ni_mio_common.c +@@ -4762,7 +4762,7 @@ static int cs5529_wait_for_idle(struct comedi_device *dev) + if ((status & NI67XX_CAL_STATUS_BUSY) == 0) + break; + set_current_state(TASK_INTERRUPTIBLE); +- if (schedule_timeout(1)) ++ if (schedule_min_hrtimeout()) + return -EIO; + } + if (i == timeout) { +diff --git a/drivers/crypto/ccp/tee-dev.c b/drivers/crypto/ccp/tee-dev.c +--- a/drivers/crypto/ccp/tee-dev.c ++++ b/drivers/crypto/ccp/tee-dev.c +@@ -270,7 +270,7 @@ static int tee_submit_cmd(struct psp_tee_device *tee, enum tee_cmd_id cmd_id, + + /* Wait if ring buffer is full or TEE is processing data */ + mutex_unlock(&tee->rb_mgr.mutex); +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + mutex_lock(&tee->rb_mgr.mutex); + + } while (--nloop); +diff --git a/drivers/gpib/nec7210/nec7210.c b/drivers/gpib/nec7210/nec7210.c +--- a/drivers/gpib/nec7210/nec7210.c ++++ b/drivers/gpib/nec7210/nec7210.c +@@ -317,7 +317,7 @@ int nec7210_go_to_standby(struct gpib_board *board, struct nec7210_priv *priv) + if (i == timeout) { + for (i = 0; i < HZ; i++) { + set_current_state(TASK_INTERRUPTIBLE); +- if (schedule_timeout(1)) ++ if (schedule_min_hrtimeout()) + return -ERESTARTSYS; + adsr_bits = read_byte(priv, ADSR); + if (adsr_bits & HR_NATN) +diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c +--- a/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c ++++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c +@@ -215,7 +215,7 @@ static int vmw_fifo_wait_noirq(struct vmw_private *dev_priv, + DRM_ERROR("SVGA device lockup.\n"); + break; + } +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + if (interruptible && signal_pending(current)) { + ret = -ERESTARTSYS; + break; +diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c b/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c +--- a/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c ++++ b/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c +@@ -200,7 +200,7 @@ int vmw_fallback_wait(struct vmw_private *dev_priv, + break; + } + if (lazy) +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + else if ((++count & 0x0F) == 0) { + /** + * FIXME: Use schedule_hr_timeout here for +diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c +--- a/drivers/hwmon/adt7470.c ++++ b/drivers/hwmon/adt7470.c +@@ -332,7 +332,7 @@ static int adt7470_update_thread(void *p) + if (kthread_should_stop()) + break; + +- schedule_timeout_interruptible(msecs_to_jiffies(data->auto_update_interval)); ++ schedule_msec_hrtimeout_interruptible(data->auto_update_interval); + } + + return 0; +diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c +--- a/drivers/hwmon/fam15h_power.c ++++ b/drivers/hwmon/fam15h_power.c +@@ -221,7 +221,7 @@ static ssize_t power1_average_show(struct device *dev, + prev_ptsc[cu] = data->cpu_sw_pwr_ptsc[cu]; + } + +- leftover = schedule_timeout_interruptible(msecs_to_jiffies(data->power_period)); ++ leftover = schedule_msec_hrtimeout_interruptible(data->power_period); + if (leftover) + return 0; + +diff --git a/drivers/hwmon/ibmaem.c b/drivers/hwmon/ibmaem.c +--- a/drivers/hwmon/ibmaem.c ++++ b/drivers/hwmon/ibmaem.c +@@ -827,9 +827,8 @@ static ssize_t aem_show_power(struct device *dev, + time = ktime_get_ns(); + before = data->energy[attr->index]; + +- leftover = schedule_timeout_interruptible( +- msecs_to_jiffies(data->power_period[attr->index]) +- ); ++ leftover = schedule_msec_hrtimeout_interruptible( ++ data->power_period[attr->index]); + if (leftover) { + mutex_unlock(&data->lock); + return 0; +diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c +--- a/drivers/iio/light/tsl2563.c ++++ b/drivers/iio/light/tsl2563.c +@@ -274,11 +274,7 @@ static void tsl2563_wait_adc(struct tsl2563_chip *chip) + default: + delay = 402; + } +- /* +- * TODO: Make sure that we wait at least required delay but why we +- * have to extend it one tick more? +- */ +- schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); ++ schedule_msec_hrtimeout_interruptible(delay + 1); + } + + static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +diff --git a/drivers/md/bcache/btree.c b/drivers/md/bcache/btree.c +--- a/drivers/md/bcache/btree.c ++++ b/drivers/md/bcache/btree.c +@@ -1846,8 +1846,7 @@ static void bch_btree_gc(struct cache_set *c) + cond_resched(); + + if (ret == -EAGAIN) +- schedule_timeout_interruptible( +- msecs_to_jiffies(btree_gc_sleep_ms(c))); ++ schedule_msec_hrtimeout_interruptible(btree_gc_sleep_ms(c)); + else if (ret) + pr_warn("gc failed!\n"); + } while (ret && !test_bit(CACHE_SET_IO_DISABLE, &c->flags)); +diff --git a/drivers/media/i2c/bt866.c b/drivers/media/i2c/bt866.c +--- a/drivers/media/i2c/bt866.c ++++ b/drivers/media/i2c/bt866.c +@@ -65,7 +65,7 @@ static int bt866_write(struct bt866 *encoder, u8 subaddr, u8 data) + err++; + v4l_warn(client, "error #%d writing to 0x%02x\n", + err, subaddr); +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + } + if (err == 3) { + v4l_warn(client, "giving up\n"); +diff --git a/drivers/media/i2c/ir-kbd-i2c.c b/drivers/media/i2c/ir-kbd-i2c.c +--- a/drivers/media/i2c/ir-kbd-i2c.c ++++ b/drivers/media/i2c/ir-kbd-i2c.c +@@ -698,7 +698,7 @@ static int zilog_tx(struct rc_dev *rcdev, unsigned int *txbuf, + */ + for (i = 0; i < 20; ++i) { + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(50)); ++ schedule_msec_hrtimeout(50); + ret = i2c_master_send(ir->tx_c, buf, 1); + if (ret == 1) + break; +diff --git a/drivers/media/i2c/msp3400-driver.c b/drivers/media/i2c/msp3400-driver.c +--- a/drivers/media/i2c/msp3400-driver.c ++++ b/drivers/media/i2c/msp3400-driver.c +@@ -170,7 +170,7 @@ static int msp_read(struct i2c_client *client, int dev, int addr) + break; + dev_warn(&client->dev, "I/O error #%d (read 0x%02x/0x%02x)\n", err, + dev, addr); +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + } + if (err == 3) { + dev_warn(&client->dev, "resetting chip, sound will go off.\n"); +@@ -211,7 +211,7 @@ static int msp_write(struct i2c_client *client, int dev, int addr, int val) + break; + dev_warn(&client->dev, "I/O error #%d (write 0x%02x/0x%02x)\n", err, + dev, addr); +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + } + if (err == 3) { + dev_warn(&client->dev, "resetting chip, sound will go off.\n"); +diff --git a/drivers/media/i2c/saa7110.c b/drivers/media/i2c/saa7110.c +--- a/drivers/media/i2c/saa7110.c ++++ b/drivers/media/i2c/saa7110.c +@@ -184,7 +184,7 @@ static v4l2_std_id determine_norm(struct v4l2_subdev *sd) + saa7110_write_block(sd, initseq, sizeof(initseq)); + saa7110_selmux(sd, decoder->input); + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(250)); ++ schedule_msec_hrtimeout(250); + finish_wait(&decoder->wq, &wait); + status = saa7110_read(sd); + if (status & 0x40) { +@@ -219,7 +219,7 @@ static v4l2_std_id determine_norm(struct v4l2_subdev *sd) + /*saa7110_write(sd,0x2E,0x9A);*/ + + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(250)); ++ schedule_msec_hrtimeout(250); + finish_wait(&decoder->wq, &wait); + + status = saa7110_read(sd); +diff --git a/drivers/media/pci/cx18/cx18-gpio.c b/drivers/media/pci/cx18/cx18-gpio.c +--- a/drivers/media/pci/cx18/cx18-gpio.c ++++ b/drivers/media/pci/cx18/cx18-gpio.c +@@ -81,11 +81,11 @@ static void gpio_reset_seq(struct cx18 *cx, u32 active_lo, u32 active_hi, + + /* Assert */ + gpio_update(cx, mask, ~active_lo); +- schedule_timeout_uninterruptible(msecs_to_jiffies(assert_msecs)); ++ schedule_msec_hrtimeout_uninterruptible(assert_msecs); + + /* Deassert */ + gpio_update(cx, mask, ~active_hi); +- schedule_timeout_uninterruptible(msecs_to_jiffies(recovery_msecs)); ++ schedule_msec_hrtimeout_uninterruptible(recovery_msecs); + } + + /* +diff --git a/drivers/media/pci/hws/hws_pci.c b/drivers/media/pci/hws/hws_pci.c +--- a/drivers/media/pci/hws/hws_pci.c ++++ b/drivers/media/pci/hws/hws_pci.c +@@ -215,7 +215,7 @@ static int main_ks_thread_handle(void *data) + /* If we're suspending, don't touch hardware; just sleep/freeze. */ + if (READ_ONCE(pdx->suspended)) { + try_to_freeze(); +- schedule_timeout_interruptible(msecs_to_jiffies(1000)); ++ schedule_msec_hrtimeout_interruptible(1000); + continue; + } + +@@ -225,7 +225,7 @@ static int main_ks_thread_handle(void *data) + try_to_freeze(); /* cooperate with freezer each loop */ + + /* Sleep 1s or until signaled to wake/stop */ +- schedule_timeout_interruptible(msecs_to_jiffies(1000)); ++ schedule_msec_hrtimeout_interruptible(1000); + } + + dev_dbg(&pdx->pdev->dev, "%s: exiting\n", __func__); +diff --git a/drivers/media/pci/ivtv/ivtv-gpio.c b/drivers/media/pci/ivtv/ivtv-gpio.c +--- a/drivers/media/pci/ivtv/ivtv-gpio.c ++++ b/drivers/media/pci/ivtv/ivtv-gpio.c +@@ -105,7 +105,7 @@ void ivtv_reset_ir_gpio(struct ivtv *itv) + curout = (curout & ~0xF) | 1; + write_reg(curout, IVTV_REG_GPIO_OUT); + /* We could use something else for smaller time */ +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + curout |= 2; + write_reg(curout, IVTV_REG_GPIO_OUT); + curdir &= ~0x80; +@@ -125,11 +125,11 @@ int ivtv_reset_tuner_gpio(void *dev, int component, int cmd, int value) + curout = read_reg(IVTV_REG_GPIO_OUT); + curout &= ~(1 << itv->card->xceive_pin); + write_reg(curout, IVTV_REG_GPIO_OUT); +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + + curout |= 1 << itv->card->xceive_pin; + write_reg(curout, IVTV_REG_GPIO_OUT); +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + return 0; + } + +diff --git a/drivers/media/pci/ivtv/ivtv-ioctl.c b/drivers/media/pci/ivtv/ivtv-ioctl.c +--- a/drivers/media/pci/ivtv/ivtv-ioctl.c ++++ b/drivers/media/pci/ivtv/ivtv-ioctl.c +@@ -1141,7 +1141,7 @@ void ivtv_s_std_dec(struct ivtv *itv, v4l2_std_id std) + TASK_UNINTERRUPTIBLE); + if ((read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16) < 100) + break; +- schedule_timeout(msecs_to_jiffies(25)); ++ schedule_msec_hrtimeout(25); + } + finish_wait(&itv->vsync_waitq, &wait); + mutex_lock(&itv->serialize_lock); +diff --git a/drivers/media/pci/ivtv/ivtv-streams.c b/drivers/media/pci/ivtv/ivtv-streams.c +--- a/drivers/media/pci/ivtv/ivtv-streams.c ++++ b/drivers/media/pci/ivtv/ivtv-streams.c +@@ -835,7 +835,7 @@ int ivtv_stop_v4l2_encode_stream(struct ivtv_stream *s, int gop_end) + while (!test_bit(IVTV_F_I_EOS, &itv->i_flags) && + time_before(jiffies, + then + msecs_to_jiffies(2000))) { +- schedule_timeout(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout(10); + } + + /* To convert jiffies to ms, we must multiply by 1000 +diff --git a/drivers/media/pci/ivtv/ivtvfb.c b/drivers/media/pci/ivtv/ivtvfb.c +--- a/drivers/media/pci/ivtv/ivtvfb.c ++++ b/drivers/media/pci/ivtv/ivtvfb.c +@@ -474,7 +474,7 @@ static int ivtvfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long ar + + case FBIO_WAITFORVSYNC: + prepare_to_wait(&itv->vsync_waitq, &wait, TASK_INTERRUPTIBLE); +- if (!schedule_timeout(msecs_to_jiffies(50))) ++ if (!schedule_msec_hrtimeout(50)) + rc = -ETIMEDOUT; + finish_wait(&itv->vsync_waitq, &wait); + return rc; +diff --git a/drivers/media/pci/saa7134/saa7134-tvaudio.c b/drivers/media/pci/saa7134/saa7134-tvaudio.c +--- a/drivers/media/pci/saa7134/saa7134-tvaudio.c ++++ b/drivers/media/pci/saa7134/saa7134-tvaudio.c +@@ -307,8 +307,7 @@ static int tvaudio_sleep(struct saa7134_dev *dev, int timeout) + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } else { +- schedule_timeout_interruptible +- (msecs_to_jiffies(timeout)); ++ schedule_msec_hrtimeout_interruptible(timeout); + } + } + return dev->thread.scan1 != dev->thread.scan2; +diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c +--- a/drivers/media/radio/radio-mr800.c ++++ b/drivers/media/radio/radio-mr800.c +@@ -366,7 +366,7 @@ static int vidioc_s_hw_freq_seek(struct file *file, void *priv, + retval = -ENODATA; + break; + } +- if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { ++ if (schedule_msec_hrtimeout_interruptible(10)) { + retval = -ERESTARTSYS; + break; + } +diff --git a/drivers/media/radio/radio-tea5777.c b/drivers/media/radio/radio-tea5777.c +--- a/drivers/media/radio/radio-tea5777.c ++++ b/drivers/media/radio/radio-tea5777.c +@@ -235,7 +235,7 @@ static int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait) + } + + if (wait) { +- if (schedule_timeout_interruptible(msecs_to_jiffies(wait))) ++ if (schedule_msec_hrtimeout_interruptible(wait)) + return -ERESTARTSYS; + } + +diff --git a/drivers/media/radio/tea575x.c b/drivers/media/radio/tea575x.c +--- a/drivers/media/radio/tea575x.c ++++ b/drivers/media/radio/tea575x.c +@@ -401,7 +401,7 @@ int snd_tea575x_s_hw_freq_seek(struct file *file, struct snd_tea575x *tea, + for (;;) { + if (time_after(jiffies, timeout)) + break; +- if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { ++ if (schedule_msec_hrtimeout_interruptible(10)) { + /* some signal arrived, stop search */ + tea->val &= ~TEA575X_BIT_SEARCH; + snd_tea575x_set_freq(tea); +diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c +--- a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c ++++ b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c +@@ -297,7 +297,7 @@ static int rotation_thread_function(void *data) + __s32 vflip, hflip; + + set_current_state(TASK_INTERRUPTIBLE); +- while (!schedule_timeout(msecs_to_jiffies(100))) { ++ while (!schedule_msec_hrtimeout(100)) { + if (mutex_lock_interruptible(&sd->gspca_dev.usb_lock)) + break; + +diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c +--- a/drivers/mfd/ucb1x00-core.c ++++ b/drivers/mfd/ucb1x00-core.c +@@ -253,7 +253,7 @@ unsigned int ucb1x00_adc_read(struct ucb1x00 *ucb, int adc_channel, int sync) + break; + /* yield to other processes */ + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } + + return UCB_ADC_DAT(val); +diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c +--- a/drivers/misc/mei/hw-me.c ++++ b/drivers/misc/mei/hw-me.c +@@ -1464,7 +1464,7 @@ int mei_me_polling_thread(void *_dev) + MEI_POLLING_TIMEOUT_IDLE); + } + +- schedule_timeout_interruptible(msecs_to_jiffies(polling_timeout)); ++ schedule_msec_hrtimeout_interruptible(polling_timeout); + } + + return 0; +diff --git a/drivers/misc/sgi-xp/xpc_channel.c b/drivers/misc/sgi-xp/xpc_channel.c +--- a/drivers/misc/sgi-xp/xpc_channel.c ++++ b/drivers/misc/sgi-xp/xpc_channel.c +@@ -834,7 +834,7 @@ xpc_allocate_msg_wait(struct xpc_channel *ch) + + atomic_inc(&ch->n_on_msg_allocate_wq); + prepare_to_wait(&ch->msg_allocate_wq, &wait, TASK_INTERRUPTIBLE); +- ret = schedule_timeout(1); ++ ret = schedule_min_hrtimeout(); + finish_wait(&ch->msg_allocate_wq, &wait); + atomic_dec(&ch->n_on_msg_allocate_wq); + +diff --git a/drivers/net/can/usb/peak_usb/pcan_usb.c b/drivers/net/can/usb/peak_usb/pcan_usb.c +--- a/drivers/net/can/usb/peak_usb/pcan_usb.c ++++ b/drivers/net/can/usb/peak_usb/pcan_usb.c +@@ -308,7 +308,7 @@ static int pcan_usb_write_mode(struct peak_usb_device *dev, u8 onoff) + } else { + /* the PCAN-USB needs time to init */ + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(PCAN_USB_STARTUP_TIMEOUT)); ++ schedule_msec_hrtimeout(PCAN_USB_STARTUP_TIMEOUT); + } + + return err; +diff --git a/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c b/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c +--- a/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c ++++ b/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c +@@ -1090,7 +1090,7 @@ static int lio_reset_queues(struct net_device *netdev, uint32_t num_qs) + struct napi_struct *napi, *n; + int ret; + +- schedule_timeout_uninterruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_uninterruptible(100); + + if (wait_for_pending_requests(oct)) + dev_err(&oct->pci_dev->dev, "There were pending requests\n"); +diff --git a/drivers/net/ethernet/cavium/liquidio/lio_main.c b/drivers/net/ethernet/cavium/liquidio/lio_main.c +--- a/drivers/net/ethernet/cavium/liquidio/lio_main.c ++++ b/drivers/net/ethernet/cavium/liquidio/lio_main.c +@@ -758,7 +758,7 @@ static int liquidio_watchdog(void *param) + while (!kthread_should_stop()) { + /* sleep for a couple of seconds so that we don't hog the CPU */ + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(2000)); ++ schedule_msec_hrtimeout(2000); + + mask_of_crashed_or_stuck_cores = + (u16)octeon_read_csr64(oct, CN23XX_SLI_SCRATCH2); +diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c +--- a/drivers/net/usb/lan78xx.c ++++ b/drivers/net/usb/lan78xx.c +@@ -3514,7 +3514,7 @@ static void lan78xx_terminate_urbs(struct lan78xx_net *dev) + /* maybe wait for deletions to finish. */ + while (!skb_queue_empty(&dev->rxq) || + !skb_queue_empty(&dev->txq)) { +- schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS)); ++ schedule_msec_hrtimeout(UNLINK_TIMEOUT_MS); + set_current_state(TASK_UNINTERRUPTIBLE); + netif_dbg(dev, ifdown, dev->net, + "waited for %d urb completions", temp); +diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c +--- a/drivers/net/usb/usbnet.c ++++ b/drivers/net/usb/usbnet.c +@@ -791,7 +791,7 @@ static void wait_skb_queue_empty(struct sk_buff_head *q) + spin_lock_irqsave(&q->lock, flags); + while (!skb_queue_empty(q)) { + spin_unlock_irqrestore(&q->lock, flags); +- schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS)); ++ schedule_msec_hrtimeout(UNLINK_TIMEOUT_MS); + set_current_state(TASK_UNINTERRUPTIBLE); + spin_lock_irqsave(&q->lock, flags); + } +diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2100.c b/drivers/net/wireless/intel/ipw2x00/ipw2100.c +--- a/drivers/net/wireless/intel/ipw2x00/ipw2100.c ++++ b/drivers/net/wireless/intel/ipw2x00/ipw2100.c +@@ -799,7 +799,7 @@ static int ipw2100_hw_send_command(struct ipw2100_priv *priv, + * doesn't seem to have as many firmware restart cycles... + * + * As a test, we're sticking in a 1/100s delay here */ +- schedule_timeout_uninterruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_uninterruptible(10); + + return 0; + +@@ -1250,7 +1250,7 @@ static int ipw2100_start_adapter(struct ipw2100_priv *priv) + IPW_DEBUG_FW("Waiting for f/w initialization to complete...\n"); + i = 5000; + do { +- schedule_timeout_uninterruptible(msecs_to_jiffies(40)); ++ schedule_msec_hrtimeout_uninterruptible(40); + /* Todo... wait for sync command ... */ + + read_register(priv->net_dev, IPW_REG_INTA, &inta); +diff --git a/drivers/parport/ieee1284.c b/drivers/parport/ieee1284.c +--- a/drivers/parport/ieee1284.c ++++ b/drivers/parport/ieee1284.c +@@ -202,7 +202,7 @@ int parport_wait_peripheral(struct parport *port, + /* parport_wait_event didn't time out, but the + * peripheral wasn't actually ready either. + * Wait for another 10ms. */ +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + } + } + +diff --git a/drivers/parport/ieee1284_ops.c b/drivers/parport/ieee1284_ops.c +--- a/drivers/parport/ieee1284_ops.c ++++ b/drivers/parport/ieee1284_ops.c +@@ -520,7 +520,7 @@ size_t parport_ieee1284_ecp_read_data (struct parport *port, + /* Yield the port for a while. */ + if (dev->port->irq != PARPORT_IRQ_NONE) { + parport_release (dev); +- schedule_timeout_interruptible(msecs_to_jiffies(40)); ++ schedule_msec_hrtimeout_interruptible(40); + parport_claim_or_block (dev); + } + else +diff --git a/drivers/parport/parport_pc.c b/drivers/parport/parport_pc.c +--- a/drivers/parport/parport_pc.c ++++ b/drivers/parport/parport_pc.c +@@ -177,8 +177,7 @@ static int change_mode(struct parport *p, int m) + if (time_after_eq(jiffies, expire)) + /* The FIFO is stuck. */ + return -EBUSY; +- schedule_timeout_interruptible( +- msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + if (signal_pending(current)) + break; + } +diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c +--- a/drivers/platform/x86/intel_ips.c ++++ b/drivers/platform/x86/intel_ips.c +@@ -806,7 +806,7 @@ static int ips_adjust(void *data) + ips_gpu_lower(ips); + + sleep: +- schedule_timeout_interruptible(msecs_to_jiffies(IPS_ADJUST_PERIOD)); ++ schedule_msec_hrtimeout_interruptible(IPS_ADJUST_PERIOD); + } while (!kthread_should_stop()); + + dev_dbg(ips->dev, "ips-adjust thread stopped\n"); +@@ -984,7 +984,7 @@ static int ips_monitor(void *data) + seqno_timestamp = get_jiffies_64(); + + old_cpu_power = thm_readl(THM_CEC); +- schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); ++ schedule_msec_hrtimeout_interruptible(IPS_SAMPLE_PERIOD); + + /* Collect an initial average */ + for (i = 0; i < IPS_SAMPLE_COUNT; i++) { +@@ -1011,7 +1011,7 @@ static int ips_monitor(void *data) + mchp_samples[i] = mchp; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); ++ schedule_msec_hrtimeout_interruptible(IPS_SAMPLE_PERIOD); + if (kthread_should_stop()) + break; + } +@@ -1038,7 +1038,7 @@ static int ips_monitor(void *data) + * us to reduce the sample frequency if the CPU and GPU are idle. + */ + old_cpu_power = thm_readl(THM_CEC); +- schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); ++ schedule_msec_hrtimeout_interruptible(IPS_SAMPLE_PERIOD); + last_sample_period = IPS_SAMPLE_PERIOD; + + timer_setup(&ips->timer, monitor_timeout, TIMER_DEFERRABLE); +diff --git a/drivers/rtc/rtc-wm8350.c b/drivers/rtc/rtc-wm8350.c +--- a/drivers/rtc/rtc-wm8350.c ++++ b/drivers/rtc/rtc-wm8350.c +@@ -114,7 +114,7 @@ static int wm8350_rtc_settime(struct device *dev, struct rtc_time *tm) + /* Wait until confirmation of stopping */ + do { + rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL); +- schedule_timeout_uninterruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_uninterruptible(1); + } while (--retries && !(rtc_ctrl & WM8350_RTC_STS)); + + if (!retries) { +@@ -197,7 +197,7 @@ static int wm8350_rtc_stop_alarm(struct wm8350 *wm8350) + /* Wait until confirmation of stopping */ + do { + rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL); +- schedule_timeout_uninterruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_uninterruptible(1); + } while (retries-- && !(rtc_ctrl & WM8350_RTC_ALMSTS)); + + if (!(rtc_ctrl & WM8350_RTC_ALMSTS)) +@@ -220,7 +220,7 @@ static int wm8350_rtc_start_alarm(struct wm8350 *wm8350) + /* Wait until confirmation */ + do { + rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL); +- schedule_timeout_uninterruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_uninterruptible(1); + } while (retries-- && rtc_ctrl & WM8350_RTC_ALMSTS); + + if (rtc_ctrl & WM8350_RTC_ALMSTS) +diff --git a/drivers/s390/char/sclp_sdias.c b/drivers/s390/char/sclp_sdias.c +--- a/drivers/s390/char/sclp_sdias.c ++++ b/drivers/s390/char/sclp_sdias.c +@@ -68,7 +68,7 @@ static int sdias_sclp_send(struct sclp_req *req) + /* not initiated, wait some time and retry */ + set_current_state(TASK_INTERRUPTIBLE); + TRACE("add request failed: rc = %i\n",rc); +- schedule_timeout(msecs_to_jiffies(500)); ++ schedule_msec_hrtimeout(500); + continue; + } + /* initiated, wait for completion of service call */ +diff --git a/drivers/scsi/be2iscsi/be_cmds.c b/drivers/scsi/be2iscsi/be_cmds.c +--- a/drivers/scsi/be2iscsi/be_cmds.c ++++ b/drivers/scsi/be2iscsi/be_cmds.c +@@ -611,7 +611,7 @@ static int be_mbox_db_ready_poll(struct be_ctrl_info *ctrl) + if (time_after(jiffies, timeout)) + break; + /* 1ms sleep is enough in most cases */ +- schedule_timeout_uninterruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_uninterruptible(1); + } while (!ready); + + beiscsi_log(phba, KERN_ERR, +diff --git a/drivers/scsi/esas2r/esas2r_flash.c b/drivers/scsi/esas2r/esas2r_flash.c +--- a/drivers/scsi/esas2r/esas2r_flash.c ++++ b/drivers/scsi/esas2r/esas2r_flash.c +@@ -965,7 +965,7 @@ static bool esas2r_flash_access(struct esas2r_adapter *a, u32 function) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > timeout) { + /* +diff --git a/drivers/scsi/esas2r/esas2r_init.c b/drivers/scsi/esas2r/esas2r_init.c +--- a/drivers/scsi/esas2r/esas2r_init.c ++++ b/drivers/scsi/esas2r/esas2r_init.c +@@ -994,7 +994,7 @@ bool esas2r_check_adapter(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > 180000) { + esas2r_hdebug("FW ready TMO"); +@@ -1017,7 +1017,7 @@ bool esas2r_check_adapter(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(50)); ++ schedule_msec_hrtimeout_interruptible(50); + + if ((jiffies_to_msecs(jiffies) - starttime) > 3000) { + esas2r_hdebug("timeout waiting for interface down"); +@@ -1106,7 +1106,7 @@ bool esas2r_check_adapter(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > 3000) { + esas2r_hdebug( +@@ -1356,7 +1356,7 @@ bool esas2r_init_adapter_hw(struct esas2r_adapter *a, bool init_poll) + atomic_dec(&a->disable_cnt); + + while (test_bit(AF_DISC_PENDING, &a->flags)) { +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + /* + * Determine the need for a timer tick based on the +@@ -1512,7 +1512,7 @@ static void esas2r_power_down_notify_firmware(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > 30000) { + esas2r_hdebug("Timeout waiting for power down"); +@@ -1557,7 +1557,7 @@ void esas2r_power_down(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > 3000) { + esas2r_hdebug( +diff --git a/drivers/scsi/esas2r/esas2r_main.c b/drivers/scsi/esas2r/esas2r_main.c +--- a/drivers/scsi/esas2r/esas2r_main.c ++++ b/drivers/scsi/esas2r/esas2r_main.c +@@ -1247,7 +1247,7 @@ void esas2r_wait_request(struct esas2r_adapter *a, struct esas2r_request *rq) + if (rq->req_stat != RS_STARTED) + break; + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > timeout) { + esas2r_hdebug("request TMO"); +diff --git a/drivers/scsi/fnic/fnic_scsi.c b/drivers/scsi/fnic/fnic_scsi.c +--- a/drivers/scsi/fnic/fnic_scsi.c ++++ b/drivers/scsi/fnic/fnic_scsi.c +@@ -258,7 +258,7 @@ int fnic_fw_reset_handler(struct fnic *fnic) + + /* wait for io cmpl */ + while (atomic_read(&fnic->in_flight)) +- schedule_timeout(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout(1); + + spin_lock_irqsave(&fnic->wq_copy_lock[0], flags); + +@@ -1634,7 +1634,7 @@ static void fnic_cleanup_io(struct fnic *fnic, int exclude_id) + io_count, + atomic64_read(&fnic->fnic_stats.io_stats.active_ios)); + +- schedule_timeout(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout(100); + } + } + +@@ -1912,7 +1912,7 @@ void fnic_rport_exch_reset(struct fnic *fnic, u32 port_id) + atomic_dec(&fnic->in_flight); + + while ((io_count = fnic_count_ioreqs(fnic, port_id))) +- schedule_timeout(msecs_to_jiffies(1000)); ++ schedule_msec_hrtimeout(1000); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "rport: 0x%x remaining portid-io-count: %d ", +@@ -2510,7 +2510,7 @@ static int fnic_clean_pending_aborts(struct fnic *fnic, + ret = iter_data.ret; + goto clean_pending_aborts_end; + } +- schedule_timeout(msecs_to_jiffies(2 * fnic->config.ed_tov)); ++ schedule_msec_hrtimeout(2 * fnic->config.ed_tov); + + /* walk again to check, if IOs are still pending in fw */ + if (fnic_is_abts_pending(fnic, lr_sc)) +@@ -2811,7 +2811,7 @@ int fnic_device_reset(struct scsi_cmnd *sc) + } + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Cannot clean up all IOs for the LUN\n"); +- schedule_timeout(msecs_to_jiffies(1000)); ++ schedule_msec_hrtimeout(1000); + count++; + } + +diff --git a/drivers/scsi/lpfc/lpfc_scsi.c b/drivers/scsi/lpfc/lpfc_scsi.c +--- a/drivers/scsi/lpfc/lpfc_scsi.c ++++ b/drivers/scsi/lpfc/lpfc_scsi.c +@@ -5920,7 +5920,7 @@ lpfc_chk_tgt_mapped(struct lpfc_vport *vport, struct fc_rport *rport) + return FAILED; + if (pnode->nlp_state == NLP_STE_MAPPED_NODE) + return SUCCESS; +- schedule_timeout_uninterruptible(msecs_to_jiffies(500)); ++ schedule_msec_hrtimeout_uninterruptible(500); + rdata = rport->dd_data; + if (!rdata) + return FAILED; +@@ -5962,7 +5962,7 @@ lpfc_reset_flush_io_context(struct lpfc_vport *vport, uint16_t tgt_id, + tgt_id, lun_id, context); + later = secs_to_jiffies(2 * vport->cfg_devloss_tmo) + jiffies; + while (time_after(later, jiffies) && cnt) { +- schedule_timeout_uninterruptible(msecs_to_jiffies(20)); ++ schedule_msec_hrtimeout_uninterruptible(20); + cnt = lpfc_sli_sum_iocb(vport, tgt_id, lun_id, context); + } + if (cnt) { +diff --git a/drivers/scsi/qla4xxx/ql4_nx.c b/drivers/scsi/qla4xxx/ql4_nx.c +--- a/drivers/scsi/qla4xxx/ql4_nx.c ++++ b/drivers/scsi/qla4xxx/ql4_nx.c +@@ -1552,7 +1552,7 @@ static int qla4_82xx_cmdpeg_ready(struct scsi_qla_host *ha, int pegtune_val) + (val == PHAN_INITIALIZE_ACK)) + return 0; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(500)); ++ schedule_msec_hrtimeout(500); + + } while (--retries); + +diff --git a/drivers/scsi/snic/snic_scsi.c b/drivers/scsi/snic/snic_scsi.c +--- a/drivers/scsi/snic/snic_scsi.c ++++ b/drivers/scsi/snic/snic_scsi.c +@@ -1863,7 +1863,7 @@ snic_dr_clean_pending_req(struct snic *snic, struct scsi_cmnd *lr_sc) + } + } + +- schedule_timeout(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout(100); + + /* Walk through all the cmds and check abts status. */ + if (snic_is_abts_pending(snic, lr_sc)) +@@ -2321,7 +2321,7 @@ snic_reset(struct Scsi_Host *shost, struct scsi_cmnd *sc) + + /* Wait for all the IOs that are entered in Qcmd */ + while (atomic_read(&snic->ios_inflight)) +- schedule_timeout(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout(1); + + ret = snic_issue_hba_reset(snic, sc); + if (ret) { +diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c +--- a/drivers/usb/class/usblp.c ++++ b/drivers/usb/class/usblp.c +@@ -923,7 +923,7 @@ static int usblp_wwait(struct usblp *usblp, int nonblock) + if (rc <= 0) + break; + +- if (schedule_timeout(msecs_to_jiffies(1500)) == 0) { ++ if (schedule_msec_hrtimeout(1500) == 0) { + if (usblp->flags & LP_ABORT) { + err = usblp_check_status(usblp, err); + if (err == 1) { /* Paper out */ +diff --git a/drivers/video/fbdev/omap/hwa742.c b/drivers/video/fbdev/omap/hwa742.c +--- a/drivers/video/fbdev/omap/hwa742.c ++++ b/drivers/video/fbdev/omap/hwa742.c +@@ -926,7 +926,7 @@ static void hwa742_resume(void) + if (hwa742_read_reg(HWA742_PLL_DIV_REG) & (1 << 7)) + break; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(5)); ++ schedule_msec_hrtimeout(5); + } + hwa742_set_update_mode(hwa742.update_mode_before_suspend); + } +diff --git a/drivers/video/fbdev/pxafb.c b/drivers/video/fbdev/pxafb.c +--- a/drivers/video/fbdev/pxafb.c ++++ b/drivers/video/fbdev/pxafb.c +@@ -1272,7 +1272,7 @@ static int pxafb_smart_thread(void *arg) + mutex_unlock(&fbi->ctrlr_lock); + + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(30)); ++ schedule_msec_hrtimeout(30); + } + + pr_debug("%s(): task ending\n", __func__); +diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c +--- a/fs/f2fs/checkpoint.c ++++ b/fs/f2fs/checkpoint.c +@@ -28,7 +28,7 @@ static inline void get_lock_elapsed_time(struct f2fs_time_stat *ts) + { + ts->total_time = ktime_get(); + #ifdef CONFIG_64BIT +- ts->running_time = current->se.sum_exec_runtime; ++ ts->running_time = tsk_seruntime(current); + #endif + #if defined(CONFIG_SCHED_INFO) && defined(CONFIG_SCHEDSTATS) + ts->runnable_time = current->sched_info.run_delay; +diff --git a/fs/jffs2/background.c b/fs/jffs2/background.c +--- a/fs/jffs2/background.c ++++ b/fs/jffs2/background.c +@@ -110,7 +110,7 @@ static int jffs2_garbage_collect_thread(void *_c) + * This forces the GCD to slow the hell down. Pulling an + * inode in with read_inode() is much preferable to having + * the GC thread get there first. */ +- schedule_timeout_interruptible(msecs_to_jiffies(50)); ++ schedule_msec_hrtimeout_interruptible(50); + + if (kthread_should_stop()) { + jffs2_dbg(1, "%s(): kthread_stop() called\n", __func__); +diff --git a/fs/proc/base.c b/fs/proc/base.c +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -513,7 +513,7 @@ static int proc_pid_schedstat(struct seq_file *m, struct pid_namespace *ns, + seq_puts(m, "0 0 0\n"); + else + seq_printf(m, "%llu %llu %lu\n", +- (unsigned long long)task->se.sum_exec_runtime, ++ (unsigned long long)tsk_seruntime(task), + (unsigned long long)task->sched_info.run_delay, + task->sched_info.pcount); + +@@ -521,6 +521,108 @@ static int proc_pid_schedstat(struct seq_file *m, struct pid_namespace *ns, + } + #endif + ++#ifdef CONFIG_MUQSS_IOTIME ++/* ++ * Provides /proc/PID/iotime ++ * ++ * Block device time consumed on this task's behalf, in nanoseconds. ++ * ++ * io_latency_ns is end to end per bio and so includes queue wait: it is what ++ * this task waited for. io_occupancy_ns is device service time per request: ++ * it is what this task cost everybody else, and is what scheduling charges ++ * are based on. io_debt_ns is occupancy not yet charged to the deadline. ++ * See block/blk-iotime.c. ++ * ++ * kern_time_ns is CPU time rather than device time: kernel work done for this ++ * task in another thread's context, which today means kworkers running work ++ * items it queued. kern_debt_ns is the part of it not yet charged. Work the ++ * task asks for in its own context is not counted here, because that already ++ * comes out of its own timeslice. ++ * ++ * Access is restricted exactly as /proc/PID/io is, and for the same reason: ++ * these counters say when a task used the device and for how long, which is ++ * enough to infer a good deal about what it is doing. The exec_update_lock ++ * keeps the permission check and the read on the same side of an exec, so ++ * credentials cannot change between the two. ++ * ++ * Also as /proc/PID/io: the tgid file sums the thread group and the tid file ++ * reports one thread. Charging is per thread, since each has its own deadline, ++ * but a multithreaded writer's total is only meaningful summed. ++ */ ++struct iotime_acct { ++ u64 latency_ns; ++ u64 count; ++ u64 occupancy_ns; ++ u64 debt_ns; ++ u64 kern_time_ns; ++ u64 kern_debt_ns; ++}; ++ ++static void iotime_add(struct iotime_acct *acct, struct task_struct *task) ++{ ++ acct->latency_ns += atomic64_read(&task->io_latency_ns); ++ acct->count += atomic64_read(&task->io_count); ++ acct->occupancy_ns += atomic64_read(&task->io_occupancy_ns); ++ acct->debt_ns += atomic64_read(&task->io_debt_ns); ++ acct->kern_time_ns += atomic64_read(&task->kern_time_ns); ++ acct->kern_debt_ns += atomic64_read(&task->kern_debt_ns); ++} ++ ++static int do_iotime_accounting(struct task_struct *task, struct seq_file *m, ++ int whole) ++{ ++ struct iotime_acct acct = { }; ++ int result; ++ ++ result = down_read_killable(&task->signal->exec_update_lock); ++ if (result) ++ return result; ++ ++ if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { ++ result = -EACCES; ++ goto out_unlock; ++ } ++ ++ if (whole) { ++ struct task_struct *t; ++ ++ rcu_read_lock(); ++ for_each_thread(task, t) ++ iotime_add(&acct, t); ++ rcu_read_unlock(); ++ } else { ++ iotime_add(&acct, task); ++ } ++ ++ seq_printf(m, ++ "io_latency_ns %llu\n" ++ "io_count %llu\n" ++ "io_occupancy_ns %llu\n" ++ "io_debt_ns %llu\n" ++ "kern_time_ns %llu\n" ++ "kern_debt_ns %llu\n", ++ acct.latency_ns, acct.count, acct.occupancy_ns, ++ acct.debt_ns, acct.kern_time_ns, acct.kern_debt_ns); ++ ++out_unlock: ++ up_read(&task->signal->exec_update_lock); ++ return result; ++} ++ ++static int proc_tgid_iotime(struct seq_file *m, struct pid_namespace *ns, ++ struct pid *pid, struct task_struct *task) ++{ ++ return do_iotime_accounting(task, m, 1); ++} ++ ++static int proc_tid_iotime(struct seq_file *m, struct pid_namespace *ns, ++ struct pid *pid, struct task_struct *task) ++{ ++ return do_iotime_accounting(task, m, 0); ++} ++ ++#endif ++ + #ifdef CONFIG_LATENCYTOP + static int lstats_show_proc(struct seq_file *m, void *v) + { +@@ -3336,6 +3438,9 @@ static const struct pid_entry tgid_base_stuff[] = { + #ifdef CONFIG_SCHED_INFO + ONE("schedstat", S_IRUGO, proc_pid_schedstat), + #endif ++#ifdef CONFIG_MUQSS_IOTIME ++ ONE("iotime", S_IRUSR, proc_tgid_iotime), ++#endif + #ifdef CONFIG_LATENCYTOP + REG("latency", S_IRUGO, proc_lstats_operations), + #endif +@@ -3691,6 +3796,9 @@ static const struct pid_entry tid_base_stuff[] = { + #ifdef CONFIG_SCHED_INFO + ONE("schedstat", S_IRUGO, proc_pid_schedstat), + #endif ++#ifdef CONFIG_MUQSS_IOTIME ++ ONE("iotime", S_IRUSR, proc_tid_iotime), ++#endif + #ifdef CONFIG_LATENCYTOP + REG("latency", S_IRUGO, proc_lstats_operations), + #endif +diff --git a/fs/xfs/xfs_trans_ail.c b/fs/xfs/xfs_trans_ail.c +--- a/fs/xfs/xfs_trans_ail.c ++++ b/fs/xfs/xfs_trans_ail.c +@@ -714,7 +714,7 @@ xfsaild( + spin_unlock(&ailp->ail_lock); + + if (tout) +- schedule_timeout(msecs_to_jiffies(tout)); ++ schedule_msec_hrtimeout(tout); + + __set_current_state(TASK_RUNNING); + +diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h +--- a/include/linux/blk-mq.h ++++ b/include/linux/blk-mq.h +@@ -137,6 +137,23 @@ struct request { + /* Time that I/O was submitted to the device. */ + u64 io_start_time_ns; + ++#ifdef CONFIG_MUQSS_IOTIME ++ /* ++ * Task this request is being served for, holding a reference. Set ++ * once from the first bio and cleared if a merge brings in a bio ++ * belonging to somebody else, since occupancy cannot be split ++ * between owners at completion time. ++ */ ++ struct task_struct *muqss_owner; ++ /* ++ * Completion time, captured where the block layer already takes it. ++ * rq_qos_done() runs after blk_update_request() has completed every ++ * bio, so a timestamp taken there would include the completion path ++ * and overstate device service time. ++ */ ++ u64 muqss_done_ns; ++#endif ++ + #ifdef CONFIG_BLK_WBT + unsigned short wbt_flags; + #endif +diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h +--- a/include/linux/blk_types.h ++++ b/include/linux/blk_types.h +@@ -252,13 +252,25 @@ struct bio { + * on release of the bio. + */ + struct blkcg_gq *bi_blkg; +- /* Time that this bio was issued. */ +- u64 issue_time_ns; + #ifdef CONFIG_BLK_CGROUP_IOCOST + u64 bi_iocost_cost; + #endif + #endif + ++ /* ++ * Time that this bio was issued. Wanted by blkcg accounting and by ++ * MuQSS I/O aware scheduling, which must work on kernels built ++ * without any block cgroup support. ++ */ ++#if defined(CONFIG_BLK_CGROUP) || defined(CONFIG_MUQSS_IOTIME) ++ u64 issue_time_ns; ++#endif ++ ++#ifdef CONFIG_MUQSS_IOTIME ++ /* Task this I/O is being done for. Holds a reference. */ ++ struct task_struct *bi_muqss_owner; ++#endif ++ + #ifdef CONFIG_BLK_INLINE_ENCRYPTION + struct bio_crypt_ctx *bi_crypt_context; + #endif +diff --git a/include/linux/freezer.h b/include/linux/freezer.h +--- a/include/linux/freezer.h ++++ b/include/linux/freezer.h +@@ -91,6 +91,7 @@ static inline bool try_to_freeze(void) { return false; } + + static inline void set_freezable(void) {} + ++#define pm_freezing (false) + #endif /* !CONFIG_FREEZER */ + + #endif /* FREEZER_H_INCLUDED */ +diff --git a/include/linux/ioprio.h b/include/linux/ioprio.h +--- a/include/linux/ioprio.h ++++ b/include/linux/ioprio.h +@@ -29,6 +29,8 @@ static inline bool ioprio_valid(unsigned short ioprio) + */ + static inline int task_nice_ioprio(struct task_struct *task) + { ++ if (iso_task(task)) ++ return 0; + return (task_nice(task) + 20) / 5; + } + +diff --git a/include/linux/mm.h b/include/linux/mm.h +--- a/include/linux/mm.h ++++ b/include/linux/mm.h +@@ -2460,6 +2460,61 @@ static inline bool folio_use_access_time(struct folio *folio) + } + #endif /* CONFIG_NUMA_BALANCING */ + ++#if MUQSS_IOWNER_WIDTH ++/* ++ * MuQSS I/O owner tag: an index into the owner table in block/blk-iotime.c, ++ * recorded when a folio is dirtied so that the writeback issued for it later ++ * by a flusher thread can still be charged to the task responsible. Zero ++ * means no owner. ++ * ++ * The tag itself is lossy by design: which of two racing dirtiers wins the ++ * field does not matter, and a wrong answer costs one misattributed ++ * writeback. The *word* is not ours to be lossy with, though. Other fields in ++ * folio->flags are updated by atomic bit operations from contexts that do not ++ * hold the folio lock -- PG_waiters is set by lock waiters, and losing it ++ * skips the wakeup on unlock -- so a plain read-modify-write store here would ++ * discard concurrent updates to unrelated bits. Update the field the way ++ * folio_xchg_last_cpupid() does, with a cmpxchg loop that touches only our ++ * own bits. ++ */ ++static inline unsigned int folio_io_owner(const struct folio *folio) ++{ ++ return (READ_ONCE(folio->flags.f) >> MUQSS_IOWNER_PGSHIFT) & ++ MUQSS_IOWNER_MASK; ++} ++ ++static inline void folio_set_io_owner(struct folio *folio, unsigned int slot) ++{ ++ unsigned long old_flags, flags; ++ ++ old_flags = READ_ONCE(folio->flags.f); ++ do { ++ flags = old_flags; ++ flags &= ~(MUQSS_IOWNER_MASK << MUQSS_IOWNER_PGSHIFT); ++ flags |= ((unsigned long)slot & MUQSS_IOWNER_MASK) << ++ MUQSS_IOWNER_PGSHIFT; ++ } while (unlikely(!try_cmpxchg(&folio->flags.f, &old_flags, flags))); ++} ++ ++static inline void page_io_owner_reset(struct page *page) ++{ ++ page->flags.f &= ~(MUQSS_IOWNER_MASK << MUQSS_IOWNER_PGSHIFT); ++} ++#else ++static inline unsigned int folio_io_owner(const struct folio *folio) ++{ ++ return 0; ++} ++ ++static inline void folio_set_io_owner(struct folio *folio, unsigned int slot) ++{ ++} ++ ++static inline void page_io_owner_reset(struct page *page) ++{ ++} ++#endif /* MUQSS_IOWNER_WIDTH */ ++ + #if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS) + + /* +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1240,6 +1240,7 @@ static inline bool zone_is_empty(const struct zone *zone) + #define KASAN_TAG_PGOFF (LAST_CPUPID_PGOFF - KASAN_TAG_WIDTH) + #define LRU_GEN_PGOFF (KASAN_TAG_PGOFF - LRU_GEN_WIDTH) + #define LRU_REFS_PGOFF (LRU_GEN_PGOFF - LRU_REFS_WIDTH) ++#define MUQSS_IOWNER_PGOFF (LRU_REFS_PGOFF - MUQSS_IOWNER_WIDTH) + + /* + * Define the bit shifts to access each section. For non-existent +@@ -1251,6 +1252,7 @@ static inline bool zone_is_empty(const struct zone *zone) + #define ZONES_PGSHIFT (ZONES_PGOFF * (ZONES_WIDTH != 0)) + #define LAST_CPUPID_PGSHIFT (LAST_CPUPID_PGOFF * (LAST_CPUPID_WIDTH != 0)) + #define KASAN_TAG_PGSHIFT (KASAN_TAG_PGOFF * (KASAN_TAG_WIDTH != 0)) ++#define MUQSS_IOWNER_PGSHIFT (MUQSS_IOWNER_PGOFF * (MUQSS_IOWNER_WIDTH != 0)) + + /* NODE:ZONE or SECTION:ZONE is used to ID a zone for the buddy allocator */ + #ifdef NODE_NOT_IN_PAGE_FLAGS +@@ -1269,6 +1271,7 @@ static inline bool zone_is_empty(const struct zone *zone) + #define NODES_MASK ((1UL << NODES_WIDTH) - 1) + #define SECTIONS_MASK ((1UL << SECTIONS_WIDTH) - 1) + #define LAST_CPUPID_MASK ((1UL << LAST_CPUPID_SHIFT) - 1) ++#define MUQSS_IOWNER_MASK ((1UL << MUQSS_IOWNER_WIDTH) - 1) + #define KASAN_TAG_MASK ((1UL << KASAN_TAG_WIDTH) - 1) + #define ZONEID_MASK ((1UL << ZONEID_SHIFT) - 1) + +diff --git a/include/linux/muqss.h b/include/linux/muqss.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/muqss.h +@@ -0,0 +1,51 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Scheduler-neutral accessors for task fields that MuQSS keeps somewhere ++ * other than the mainline scheduling entities. ++ * ++ * Kept out of so that enabling MuQSS costs that header only ++ * the task_struct members themselves. Include this after task_struct is ++ * defined; already does so on your behalf. ++ */ ++#ifndef _LINUX_MUQSS_H ++#define _LINUX_MUQSS_H ++ ++#ifdef CONFIG_SCHED_MUQSS ++ ++/* MuQSS accounts runtime and RT timeouts directly on the task. */ ++#define tsk_seruntime(t) ((t)->sched_time) ++#define tsk_rttimeout(t) ((t)->rt_timeout) ++ ++/* ++ * The rtmutex PI chain sorts equal-priority waiters by SCHED_DEADLINE ++ * deadline. MuQSS implements no deadline class, and that tie-break is only ++ * consulted when dl_prio(prio) holds - i.e. prio < MAX_DL_PRIO, which is 0 - ++ * so it is unreachable here. Report 0 rather than carry a dl entity. ++ */ ++#define tsk_dl_deadline(t) (0) ++ ++/* No deadline class: no budget to overrun and no bandwidth to migrate. */ ++#define tsk_dl_overrun(t) (0) ++#define tsk_dl_bw(t) (0) ++ ++static inline bool iso_task(struct task_struct *p) ++{ ++ return (p->policy == SCHED_ISO); ++} ++ ++#else /* CONFIG_SCHED_MUQSS */ ++ ++#define tsk_seruntime(t) ((t)->se.sum_exec_runtime) ++#define tsk_rttimeout(t) ((t)->rt.timeout) ++#define tsk_dl_deadline(t) ((t)->dl.deadline) ++#define tsk_dl_overrun(t) ((t)->dl.dl_overrun) ++#define tsk_dl_bw(t) ((t)->dl.dl_bw) ++ ++static inline bool iso_task(struct task_struct *p) ++{ ++ return false; ++} ++ ++#endif /* CONFIG_SCHED_MUQSS */ ++ ++#endif /* _LINUX_MUQSS_H */ +diff --git a/include/linux/muqss_iotime.h b/include/linux/muqss_iotime.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/muqss_iotime.h +@@ -0,0 +1,168 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * I/O aware scheduling support for MuQSS. ++ * ++ * Attributes block device time back to the task that caused the I/O, so the ++ * CPU scheduler can see what a task costs the storage device rather than only ++ * what it costs the CPU. ++ * ++ * The attributed time is charged against the task's virtual deadline one for ++ * one, a nanosecond of deadline for each nanosecond of device time, so that ++ * I/O and CPU cost a task the same. ++ * ++ * The same attribution carries CPU time as well as device time. A kworker ++ * running a work item declares itself to be acting for whoever queued that ++ * work, so the time it spends is charged back to them rather than lost to a ++ * kernel thread. That half is charged one for one: a nanosecond of CPU time ++ * costs the same wherever the kernel chose to spend it, and work a task does ++ * in its own context already costs it exactly that. ++ * ++ * See MuQSS-iotime-design.md. ++ */ ++#ifndef _LINUX_MUQSS_IOTIME_H ++#define _LINUX_MUQSS_IOTIME_H ++ ++#include ++ ++struct bio; ++struct folio; ++struct request; ++struct gendisk; ++struct task_struct; ++struct work_struct; ++ ++#ifdef CONFIG_MUQSS_IOTIME ++ ++/* ++ * A window during which current is running on behalf of another task, opened ++ * by a kworker around one work item. Lives on the stack of the thread doing ++ * the work; see muqss_kerntime_begin(). ++ */ ++struct muqss_kern_window { ++ struct task_struct *owner; /* who to charge, NULL for nobody */ ++ struct task_struct *prev; /* override to restore on close */ ++ u64 start; /* current's runtime when opened */ ++}; ++ ++/* ++ * Tag @bio with the task that caused it. Called from the block layer submit ++ * path, where current is the originating task for reads, readahead and ++ * synchronous writes alike. Takes a reference that is dropped by ++ * muqss_iotime_put_owner(). ++ */ ++void muqss_iotime_set_owner(struct bio *bio); ++ ++/* ++ * Drop the owner reference taken above. Idempotent, so it can be called from ++ * every site that releases bio state without tracking which ran first. ++ */ ++void muqss_iotime_put_owner(struct bio *bio); ++ ++/* ++ * Propagate ownership from @bio_src to @bio. Stacked drivers (md, dm, loop) ++ * resubmit clones from their own threads, where current is a kernel thread ++ * rather than the originating task, so without this attribution is lost on ++ * every RAID and LUKS setup. ++ */ ++void muqss_iotime_clone_owner(struct bio *bio, struct bio *bio_src); ++ ++/* Attach the accounting policy to a newly registered queue. */ ++void muqss_iotime_enable(struct gendisk *disk); ++ ++/* Zero a new task's counters so nothing is inherited across fork. */ ++void muqss_iotime_task_init(struct task_struct *p); ++ ++/* ++ * Owner table, used to attribute writeback. A dirtying task is given a slot, ++ * whose index is small enough to store in spare page->flags bits; writeback ++ * issued later by a flusher thread reads the index back off the folio and ++ * resolves it to the task that dirtied it. ++ */ ++ ++/* Slot for current, allocating one on first use. 0 if none is available. */ ++unsigned int muqss_iotime_owner_slot(void); ++ ++/* Resolve a slot to its task, taking a reference. NULL if the slot is stale. */ ++struct task_struct *muqss_iotime_owner_task(unsigned int slot); ++ ++/* Give up a task's slot. Called from exit. */ ++void muqss_iotime_release_slot(struct task_struct *p); ++ ++/* Record current as the dirtier of @folio, if it can be attributed. */ ++void muqss_iotime_dirty_folio(struct folio *folio); ++ ++/* ++ * Declare that current is about to do I/O on behalf of @owner, and undo it. ++ * Used by io_uring's io-wq workers, which issue requests that another task ++ * queued. Returns the previous value, to be handed back to _proxy_end(). ++ * ++ * The caller must keep a reference to @owner across the window. ++ */ ++struct task_struct *muqss_iotime_proxy_begin(struct task_struct *owner); ++void muqss_iotime_proxy_end(struct task_struct *prev); ++ ++/* ++ * Two requests are being merged into one. Disowns @rq if @next belongs to a ++ * different task, so the survivor is not charged for both. ++ */ ++void muqss_iotime_merge_requests(struct request *rq, struct request *next); ++ ++/* ++ * Record who queued @work, so the kworker that eventually runs it can be ++ * charged to them. Called from the queueing task's own context, which rules ++ * out the deferred re-entries into __queue_work() made by the delayed work ++ * timer and the rcu_work callback: those run in interrupt and softirq ++ * context, where current is whoever was unlucky enough to be interrupted. ++ */ ++void muqss_work_set_owner(struct work_struct *work); ++ ++/* ++ * Open and close a window in which current is running work on behalf of the ++ * task in @slot. The CPU time current consumes across the window is charged ++ * to that task, and any I/O the work issues is attributed to it too. ++ * ++ * Cheap when there is nobody to charge, which is the common case: an unowned ++ * slot costs one comparison and reads no clocks. ++ */ ++void muqss_kerntime_begin(struct muqss_kern_window *w, unsigned int slot); ++void muqss_kerntime_end(struct muqss_kern_window *w); ++ ++/* ++ * The CPU time current has consumed so far, including the part not yet banked ++ * into ->sched_time. Implemented by the scheduler; see MuQSS.c. ++ */ ++u64 muqss_task_runtime_live(void); ++ ++#else /* !CONFIG_MUQSS_IOTIME */ ++ ++struct muqss_kern_window { }; ++ ++static inline void muqss_iotime_set_owner(struct bio *bio) { } ++static inline void muqss_iotime_put_owner(struct bio *bio) { } ++static inline void muqss_iotime_clone_owner(struct bio *bio, ++ struct bio *bio_src) { } ++static inline void muqss_iotime_enable(struct gendisk *disk) { } ++static inline void muqss_iotime_task_init(struct task_struct *p) { } ++static inline unsigned int muqss_iotime_owner_slot(void) { return 0; } ++static inline struct task_struct *muqss_iotime_owner_task(unsigned int slot) ++{ ++ return NULL; ++} ++static inline void muqss_iotime_release_slot(struct task_struct *p) { } ++static inline void muqss_iotime_dirty_folio(struct folio *folio) { } ++static inline struct task_struct * ++muqss_iotime_proxy_begin(struct task_struct *owner) ++{ ++ return NULL; ++} ++static inline void muqss_iotime_proxy_end(struct task_struct *prev) { } ++static inline void muqss_iotime_merge_requests(struct request *rq, ++ struct request *next) { } ++static inline void muqss_work_set_owner(struct work_struct *work) { } ++static inline void muqss_kerntime_begin(struct muqss_kern_window *w, ++ unsigned int slot) { } ++static inline void muqss_kerntime_end(struct muqss_kern_window *w) { } ++ ++#endif /* CONFIG_MUQSS_IOTIME */ ++ ++#endif /* _LINUX_MUQSS_IOTIME_H */ +diff --git a/include/linux/page-flags-layout.h b/include/linux/page-flags-layout.h +--- a/include/linux/page-flags-layout.h ++++ b/include/linux/page-flags-layout.h +@@ -113,9 +113,39 @@ + ZONES_WIDTH - LRU_GEN_WIDTH - SECTIONS_WIDTH - \ + NODES_WIDTH - KASAN_TAG_WIDTH - LAST_CPUPID_WIDTH) + ++/* ++ * MuQSS I/O aware scheduling stores an index into its owner table here, so ++ * that a page dirtied now can be charged to the task that dirtied it when it ++ * is written back later by a flusher thread. ++ * ++ * These bits are affordable because MuQSS excludes NUMA balancing, which is ++ * the only user of LAST_CPUPID: with CONFIG_NUMA_BALANCING off, ++ * LAST_CPUPID_SHIFT is 0 and its share of page->flags is unclaimed. ++ * ++ * Taken last so nothing else has to shrink. __LRU_REFS_WIDTH rather than ++ * LRU_REFS_WIDTH is used below because the latter is a min() expression and ++ * so cannot be evaluated by the preprocessor; the two are equal whenever ++ * there is room for MGLRU's full allocation, which is the only case in which ++ * anything is left over for us anyway. ++ */ ++#ifdef CONFIG_MUQSS_IOTIME ++#define MUQSS_IOWNER_SHIFT 8 ++#else ++#define MUQSS_IOWNER_SHIFT 0 ++#endif ++ ++#if ZONES_WIDTH + LRU_GEN_WIDTH + SECTIONS_WIDTH + NODES_WIDTH + \ ++ KASAN_TAG_WIDTH + LAST_CPUPID_WIDTH + __LRU_REFS_WIDTH + \ ++ MUQSS_IOWNER_SHIFT <= BITS_PER_LONG - NR_PAGEFLAGS ++#define MUQSS_IOWNER_WIDTH MUQSS_IOWNER_SHIFT ++#else ++#define MUQSS_IOWNER_WIDTH 0 ++#endif ++ + #define NR_NON_PAGEFLAG_BITS (SECTIONS_WIDTH + NODES_WIDTH + ZONES_WIDTH + \ + LAST_CPUPID_SHIFT + KASAN_TAG_WIDTH + \ +- LRU_GEN_WIDTH + LRU_REFS_WIDTH) ++ LRU_GEN_WIDTH + LRU_REFS_WIDTH + \ ++ MUQSS_IOWNER_WIDTH) + + #define NR_UNUSED_PAGEFLAG_BITS (BITS_PER_LONG - \ + (NR_NON_PAGEFLAG_BITS + NR_PAGEFLAGS)) +diff --git a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -44,6 +44,9 @@ + #include + #include + #include ++#ifdef CONFIG_SCHED_MUQSS ++#include ++#endif + #include + #include + #include +@@ -334,6 +337,12 @@ extern long schedule_timeout_interruptible(long timeout); + extern long schedule_timeout_killable(long timeout); + extern long schedule_timeout_uninterruptible(long timeout); + extern long schedule_timeout_idle(long timeout); ++ ++extern long schedule_msec_hrtimeout(long timeout); ++extern long schedule_min_hrtimeout(void); ++extern long schedule_msec_hrtimeout_interruptible(long timeout); ++extern long schedule_msec_hrtimeout_uninterruptible(long timeout); ++ + asmlinkage void schedule(void); + extern void schedule_preempt_disabled(void); + asmlinkage void preempt_schedule_irq(void); +@@ -877,6 +886,79 @@ struct task_struct { + int normal_prio; + unsigned int rt_priority; + ++#ifdef CONFIG_SCHED_MUQSS ++ int time_slice; ++ u64 deadline; ++ skiplist_node node; /* Skip list node */ ++ u64 last_ran; ++ u64 sched_time; /* sched_clock time spent running */ ++#ifdef CONFIG_SMT_NICE ++ int smt_bias; /* Policy/nice level bias across smt siblings */ ++#endif ++#ifdef CONFIG_HOTPLUG_CPU ++ bool zerobound; /* Bound to CPU0 for hotplug */ ++#endif ++ unsigned long rt_timeout; ++ /* Unbanked cpu time */ ++ unsigned long utime_ns, stime_ns; ++#ifdef CONFIG_MUQSS_IOTIME ++ /* ++ * Block device time consumed on this task's behalf. Updated from ++ * I/O completion, which may be any CPU, so these are atomic. Read ++ * via /proc//iotime. Not inherited across fork. Bytes are not ++ * counted here, task_io_accounting already has them. ++ * ++ * io_latency_ns is end to end per bio, so it includes queue wait and ++ * is what a waiting task experiences. io_occupancy_ns is device ++ * service time per request, which is what this task cost everybody ++ * else, and is the only one of the two fit to charge for. ++ * ++ * io_debt_ns is occupancy not yet charged to the deadline. The ++ * scheduler consumes and zeroes it; see consume_iotime_penalty(). ++ */ ++ atomic64_t io_latency_ns; ++ atomic64_t io_count; ++ atomic64_t io_occupancy_ns; ++ atomic64_t io_debt_ns; ++ ++ /* ++ * CPU time burnt in some other thread's context on this task's ++ * behalf, currently kworkers running work items it queued. Unlike the ++ * I/O counters above this is CPU time, which the task would have been ++ * charged against its own time_slice had the kernel done the work ++ * synchronously instead of handing it to a worker. ++ * ++ * kern_time_ns is cumulative and only for reporting. kern_debt_ns is ++ * the part not yet charged to the deadline; the scheduler consumes ++ * and zeroes it, see consume_kerntime_penalty(). ++ */ ++ atomic64_t kern_time_ns; ++ atomic64_t kern_debt_ns; ++ ++ /* ++ * Index of this task's slot in the I/O owner table, stamped into ++ * page->flags when it dirties a folio so writeback can be charged ++ * back to it. 0 means no slot; allocated lazily on first dirty and ++ * released on exit. ++ */ ++ unsigned int io_owner_slot; ++ ++ /* ++ * The task this one is currently working on behalf of, or NULL. ++ * io_uring's io-wq workers publish the task that queued the request ++ * here for the duration of the issue, so a submission punted to a ++ * worker is still charged to the task that asked for it. kworkers do ++ * the same around each work item, which additionally attributes any ++ * I/O the work item submits, and any pages it dirties, to the task ++ * that queued the work rather than losing them to a kernel thread. ++ * ++ * Only ever written by the task itself and only read while it is ++ * running, so it needs no locking. The publisher holds a reference ++ * to the pointee for the whole window. ++ */ ++ struct task_struct *io_owner_override; ++#endif ++#else /* CONFIG_SCHED_MUQSS */ + struct sched_entity se; + struct sched_rt_entity rt; + struct sched_dl_entity dl; +@@ -885,6 +967,7 @@ struct task_struct { + struct sched_ext_entity scx; + #endif + const struct sched_class *sched_class; ++#endif /* CONFIG_SCHED_MUQSS */ + + #ifdef CONFIG_SCHED_CORE + struct rb_node core_node; +@@ -1677,6 +1760,8 @@ struct task_struct { + randomized_struct_fields_end + } __attribute__ ((aligned (64))); + ++#include ++ + #ifdef CONFIG_SCHED_PROXY_EXEC + DECLARE_STATIC_KEY_TRUE(__sched_proxy_exec); + static inline bool sched_proxy_exec(void) +@@ -2293,7 +2378,12 @@ static inline void set_task_cpu(struct task_struct *p, unsigned int cpu) + + static inline bool task_is_runnable(struct task_struct *p) + { ++#ifdef CONFIG_SCHED_MUQSS ++ /* MuQSS has no delayed dequeue: queued means runnable. */ ++ return p->on_rq; ++#else + return p->on_rq && !p->se.sched_delayed; ++#endif + } + + extern bool sched_task_on_rq(struct task_struct *p); +diff --git a/include/linux/sched/deadline.h b/include/linux/sched/deadline.h +--- a/include/linux/sched/deadline.h ++++ b/include/linux/sched/deadline.h +@@ -30,6 +30,24 @@ static inline bool dl_time_before(u64 a, u64 b) + } + + struct root_domain; ++#ifdef CONFIG_SCHED_MUQSS ++/* ++ * MuQSS implements no deadline scheduling class, so there is never any DL ++ * bandwidth to account for or migrate between root domains. ++ */ ++static inline void dl_add_task_root_domain(struct task_struct *p) { } ++static inline void dl_clear_root_domain(struct root_domain *rd) { } ++static inline void dl_clear_root_domain_cpu(int cpu) { } ++static inline bool dl_task_needs_bw_move(struct task_struct *p, ++ const struct cpumask *new_mask) ++{ ++ return false; ++} ++static inline bool dl_bw_visited(int cpu, u64 cookie) ++{ ++ return false; ++} ++#else /* CONFIG_SCHED_MUQSS */ + extern void dl_add_task_root_domain(struct task_struct *p); + extern void dl_clear_root_domain(struct root_domain *rd); + extern void dl_clear_root_domain_cpu(int cpu); +@@ -43,19 +61,23 @@ extern void dl_clear_root_domain_cpu(int cpu); + extern bool dl_task_needs_bw_move(struct task_struct *p, + const struct cpumask *new_mask); + +-extern u64 dl_cookie; + extern bool dl_bw_visited(int cpu, u64 cookie); ++#endif /* CONFIG_SCHED_MUQSS */ ++ ++extern u64 dl_cookie; + + static inline bool dl_server(struct sched_dl_entity *dl_se) + { + return dl_se->dl_server; + } + ++#ifndef CONFIG_SCHED_MUQSS + static inline struct task_struct *dl_task_of(struct sched_dl_entity *dl_se) + { + BUG_ON(dl_server(dl_se)); + return container_of(dl_se, struct task_struct, dl); + } ++#endif + + /* + * Regarding the deadline, a task with implicit deadline has a relative +diff --git a/include/linux/sched/nohz.h b/include/linux/sched/nohz.h +--- a/include/linux/sched/nohz.h ++++ b/include/linux/sched/nohz.h +@@ -13,7 +13,7 @@ extern int get_nohz_timer_target(void); + static inline void nohz_balance_enter_idle(int cpu) { } + #endif + +-#ifdef CONFIG_NO_HZ_COMMON ++#if defined(CONFIG_NO_HZ_COMMON) && !defined(CONFIG_SCHED_MUQSS) + void calc_load_nohz_start(void); + void calc_load_nohz_remote(struct rq *rq); + void calc_load_nohz_stop(void); +diff --git a/include/linux/sched/prio.h b/include/linux/sched/prio.h +--- a/include/linux/sched/prio.h ++++ b/include/linux/sched/prio.h +@@ -16,6 +16,21 @@ + #define MAX_RT_PRIO 100 + #define MAX_DL_PRIO 0 + ++#ifdef CONFIG_SCHED_MUQSS ++/* ++ * MuQSS ranks its non-RT policies immediately below the RT range, and has no ++ * deadline class. SCHED_IDLEPRIO is an alias for the SCHED_IDLE policy value. ++ */ ++#define ISO_PRIO (MAX_RT_PRIO) ++#define NORMAL_PRIO (MAX_RT_PRIO + 1) ++#define IDLE_PRIO (MAX_RT_PRIO + 2) ++#define PRIO_LIMIT ((IDLE_PRIO) + 1) ++ ++#define SCHED_IDLEPRIO SCHED_IDLE ++#define SCHED_MAX (SCHED_IDLEPRIO) ++#define SCHED_RANGE(policy) ((policy) <= SCHED_MAX) ++#endif /* CONFIG_SCHED_MUQSS */ ++ + #define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH) + #define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2) + +diff --git a/include/linux/skip_list.h b/include/linux/skip_list.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/skip_list.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef _LINUX_SKIP_LISTS_H ++#define _LINUX_SKIP_LISTS_H ++ ++/* ++ * Four levels is enough: randomLevel() returns 0..3, and insert only ++ * grows the list one level at a time, so next/prev[4..] were unused. ++ */ ++#define SKIPLIST_MAXLEVEL 4 ++ ++typedef u64 keyType; ++ ++typedef struct nodeStructure skiplist_node; ++ ++struct nodeStructure { ++ keyType key; ++ skiplist_node *next[SKIPLIST_MAXLEVEL]; ++ skiplist_node *prev[SKIPLIST_MAXLEVEL]; ++}; ++ ++typedef struct listStructure { ++ int entries; ++ int level; /* Maximum level of the list ++ (1 more than the number of levels in the list) */ ++ u64 best_key; /* Earliest queued key; lockless hint for EDT */ ++ skiplist_node *header; /* pointer to header */ ++} skiplist; ++ ++void skiplist_init(skiplist_node *slnode); ++void skiplist_cache_init(void); ++skiplist *new_skiplist(skiplist_node *slnode); ++void skiplist_free(skiplist *l); ++void free_skiplist(skiplist *l); ++void skiplist_node_init(skiplist_node *node); ++void skiplist_insert(skiplist *l, skiplist_node *node, keyType key, unsigned int randseed); ++bool skiplist_delete(skiplist *l, skiplist_node *node); ++ ++static inline bool skiplist_node_empty(skiplist_node *node) { ++ return (!node->next[0]); ++} ++#endif /* _LINUX_SKIP_LISTS_H */ +diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h +--- a/include/linux/workqueue.h ++++ b/include/linux/workqueue.h +@@ -288,6 +288,7 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; } + lockdep_init_map(&(_work)->lockdep_map, "(work_completion)"#_work, (_key), 0); \ + INIT_LIST_HEAD(&(_work)->entry); \ + (_work)->func = (_func); \ ++ muqss_work_init_owner(_work); \ + } while (0) + #else + #define __INIT_WORK_KEY(_work, _func, _onstack, _key) \ +@@ -296,6 +297,7 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; } + (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \ + INIT_LIST_HEAD(&(_work)->entry); \ + (_work)->func = (_func); \ ++ muqss_work_init_owner(_work); \ + } while (0) + #endif + +diff --git a/include/linux/workqueue_types.h b/include/linux/workqueue_types.h +--- a/include/linux/workqueue_types.h ++++ b/include/linux/workqueue_types.h +@@ -17,9 +17,51 @@ struct work_struct { + atomic_long_t data; + struct list_head entry; + work_func_t func; ++#ifdef CONFIG_MUQSS_IOTIME ++ /* ++ * Index in the I/O owner table of the task that queued this work, so ++ * the CPU time the kworker spends running it can be charged back to ++ * whoever asked for it. 0 means nobody to charge, which is the case ++ * for the great deal of work queued by kernel threads on their own ++ * account. ++ * ++ * A slot index rather than a task pointer deliberately: work items ++ * are cancelled and disabled through half a dozen paths that clear ++ * the pending bit without ever running the work, and none of them ++ * would drop a reference taken here. A stale slot simply resolves to ++ * NULL and the work is charged to nobody. ++ */ ++ unsigned int muqss_owner_slot; ++#endif + #ifdef CONFIG_LOCKDEP + struct lockdep_map lockdep_map; + #endif + }; + ++#ifdef CONFIG_MUQSS_IOTIME ++static inline unsigned int muqss_work_owner(struct work_struct *work) ++{ ++ return work->muqss_owner_slot; ++} ++ ++/* ++ * Every queueing path stamps the owner, so this only matters for a work item ++ * initialised on memory that was never zeroed and then reaching execution by ++ * some path that does not. It is here so that such a path, if one exists, ++ * charges nobody rather than charging whichever unlucky task holds the slot ++ * the garbage happens to name. ++ */ ++static inline void muqss_work_init_owner(struct work_struct *work) ++{ ++ work->muqss_owner_slot = 0; ++} ++#else ++static inline unsigned int muqss_work_owner(struct work_struct *work) ++{ ++ return 0; ++} ++ ++static inline void muqss_work_init_owner(struct work_struct *work) { } ++#endif ++ + #endif /* _LINUX_WORKQUEUE_TYPES_H */ +diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h +--- a/include/uapi/linux/sched.h ++++ b/include/uapi/linux/sched.h +@@ -125,7 +125,7 @@ struct clone_args { + #define SCHED_FIFO 1 + #define SCHED_RR 2 + #define SCHED_BATCH 3 +-/* SCHED_ISO: reserved but not implemented yet */ ++#define SCHED_ISO 4 /* Implemented on MuQSS only */ + #define SCHED_IDLE 5 + #define SCHED_DEADLINE 6 + #define SCHED_EXT 7 +diff --git a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -218,6 +218,8 @@ config THREAD_INFO_IN_TASK + + menu "General setup" + ++source "kernel/Kconfig.MuQSS" ++ + config BROKEN + bool + help +@@ -887,6 +889,7 @@ menu "Scheduler features" + config UCLAMP_TASK + bool "Enable utilization clamping for RT/FAIR tasks" + depends on CPU_FREQ_GOV_SCHEDUTIL ++ depends on !SCHED_MUQSS + help + This feature enables the scheduler to track the clamped utilization + of each CPU based on RUNNABLE tasks scheduled on that CPU. +@@ -935,6 +938,8 @@ config UCLAMP_BUCKETS_COUNT + + config SCHED_PROXY_EXEC + bool "Proxy Execution" ++ # MuQSS has no sched_class donor/proxy plumbing ++ depends on !SCHED_MUQSS + # Avoid some build failures w/ PREEMPT_RT until it can be fixed + depends on !PREEMPT_RT + # Need to investigate how to inform sched_ext of split contexts +@@ -1012,6 +1017,7 @@ config ARCH_WANT_NUMA_VARIABLE_LOCALITY + + config NUMA_BALANCING + bool "Memory placement aware NUMA scheduler" ++ depends on !SCHED_MUQSS + depends on ARCH_SUPPORTS_NUMA_BALANCING + depends on !ARCH_WANT_NUMA_VARIABLE_LOCALITY + depends on SMP && NUMA_MIGRATION && !PREEMPT_RT +@@ -1026,6 +1032,8 @@ config SCHED_CACHE + bool "Cache aware load balance" + default y + depends on SMP ++ # Cache aware load balancing lives in CFS, which MuQSS replaces ++ depends on !SCHED_MUQSS + help + When enabled, the scheduler will attempt to aggregate tasks from + the same process onto a single Last Level Cache (LLC) domain when +@@ -1148,7 +1156,7 @@ menuconfig CGROUP_SCHED + bandwidth allocation to such task groups. It uses cgroups to group + tasks. + +-if CGROUP_SCHED ++if CGROUP_SCHED && !SCHED_MUQSS + config GROUP_SCHED_WEIGHT + def_bool n + +@@ -1210,7 +1218,7 @@ config EXT_SUB_SCHED + + config SCHED_MM_CID + def_bool y +- depends on SMP && RSEQ ++ depends on SMP && RSEQ && !SCHED_MUQSS + + config UCLAMP_TASK_GROUP + bool "Utilization clamping per group of tasks" +@@ -1337,6 +1345,7 @@ config CGROUP_DEVICE + + config CGROUP_CPUACCT + bool "Simple CPU accounting controller" ++ depends on !SCHED_MUQSS + help + Provides a simple controller for monitoring the + total CPU consumed by the tasks in a cgroup. +@@ -1482,6 +1491,7 @@ config CHECKPOINT_RESTORE + + config SCHED_AUTOGROUP + bool "Automatic process group scheduling" ++ depends on !SCHED_MUQSS + select CGROUPS + select CGROUP_SCHED + select FAIR_GROUP_SCHED +@@ -2005,6 +2015,7 @@ config RSEQ + config RSEQ_SLICE_EXTENSION + bool "Enable rseq-based time slice extension mechanism" + depends on RSEQ && HIGH_RES_TIMERS && GENERIC_ENTRY && HAVE_GENERIC_TIF_BITS ++ depends on !SCHED_MUQSS + help + Allows userspace to request a limited time slice extension when + returning from an interrupt to user space via the RSEQ shared +diff --git a/init/init_task.c b/init/init_task.c +--- a/init/init_task.c ++++ b/init/init_task.c +@@ -111,9 +111,17 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { + .stack = init_stack, + .usage = REFCOUNT_INIT(2), + .flags = PF_KTHREAD, ++#ifdef CONFIG_SCHED_MUQSS ++ .prio = NORMAL_PRIO, ++ .static_prio = MAX_PRIO - 20, ++ .normal_prio = NORMAL_PRIO, ++ .deadline = 0, ++ .time_slice = 1000000, ++#else + .prio = MAX_PRIO - 20, + .static_prio = MAX_PRIO - 20, + .normal_prio = MAX_PRIO - 20, ++#endif + .policy = SCHED_NORMAL, + .cpus_ptr = &init_task.cpus_mask, + .user_cpus_ptr = NULL, +@@ -126,6 +134,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { + .restart_block = { + .fn = do_no_restart_syscall, + }, ++#ifndef CONFIG_SCHED_MUQSS + .se = { + .group_node = LIST_HEAD_INIT(init_task.se.group_node), + }, +@@ -133,6 +142,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { + .run_list = LIST_HEAD_INIT(init_task.rt.run_list), + .time_slice = RR_TIMESLICE, + }, ++#endif + .tasks = LIST_HEAD_INIT(init_task.tasks), + #ifdef CONFIG_SMP + .pushable_tasks = PLIST_NODE_INIT(init_task.pushable_tasks, MAX_PRIO), +diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c +--- a/io_uring/io_uring.c ++++ b/io_uring/io_uring.c +@@ -56,6 +56,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1531,7 +1532,19 @@ void io_wq_submit_work(struct io_wq_work *work) + } + + do { ++ struct task_struct *prev; ++ ++ /* ++ * Anything this issue submits to the block layer is being done ++ * on behalf of the task that queued the request, not this ++ * worker. Publish it so the I/O is charged to the right task; ++ * req->tctx pins it for the duration. ++ */ ++ prev = muqss_iotime_proxy_begin(req->tctx ? req->tctx->task : ++ NULL); + ret = io_issue_sqe(req, issue_flags); ++ muqss_iotime_proxy_end(prev); ++ + if (ret != -EAGAIN) + break; + +diff --git a/kernel/Kconfig.MuQSS b/kernel/Kconfig.MuQSS +new file mode 100644 +--- /dev/null ++++ b/kernel/Kconfig.MuQSS +@@ -0,0 +1,220 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++# ++# MuQSS - The Multiple Queue Skiplist Scheduler by Con Kolivas. ++# ++# Everything MuQSS needs from Kconfig lives in this file, including the ++# negative dependencies that disable mainline scheduler features MuQSS ++# replaces or does not implement. Kconfig merges properties from repeated ++# "config" blocks, so those features are gated here rather than by editing ++# each mainline Kconfig in place. The only change required outside this file ++# is a single source line in init/Kconfig. ++ ++config SCHED_MUQSS ++ bool "MuQSS cpu scheduler" ++ # Architectures the port is built and tested on. Widening this needs ++ # arch review: gains a NORMAL_PRIO definition, ++ # which collides with the private one in ++ # arch/powerpc/platforms/cell/spufs/sched.c. ++ depends on X86 || ARM64 ++ select HIGH_RES_TIMERS ++ default y ++ help ++ The Multiple Queue Skiplist Scheduler for excellent interactivity and ++ responsiveness on the desktop and highly scalable deterministic ++ low latency on any hardware. ++ ++ Say Y here. ++ ++config SMT_NICE ++ bool "SMT (Hyperthreading) aware nice priority and policy support" ++ depends on SCHED_MUQSS && SCHED_SMT ++ default y ++ help ++ Enabling Hyperthreading on Intel CPUs decreases the effectiveness ++ of the use of 'nice' levels and different scheduling policies ++ (e.g. realtime) due to sharing of CPU power between hyperthreads. ++ SMT nice support makes each logical CPU aware of what is running on ++ its hyperthread siblings, maintaining appropriate distribution of ++ CPU according to nice levels and scheduling policies at the expense ++ of slightly increased overhead. ++ ++ If unsure say Y here. ++ ++choice ++ depends on SMP ++ prompt "CPU scheduler runqueue sharing" ++ default RQ_MC if SCHED_MUQSS ++ default RQ_NONE ++ ++config RQ_NONE ++ bool "No sharing" ++ help ++ This is the default behaviour where the CPU scheduler has one runqueue ++ per CPU, whether it is a physical or logical CPU (hyperthread). ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=none ++ ++ If unsure, say N. ++ ++config RQ_SMT ++ bool "SMT (hyperthread) siblings" ++ depends on SCHED_SMT && SCHED_MUQSS ++ ++ help ++ With this option enabled, the CPU scheduler will have one runqueue ++ shared by SMT (hyperthread) siblings. As these logical cores share ++ one physical core, sharing the runqueue resource can lead to decreased ++ overhead, lower latency and higher throughput. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=smt ++ ++ If unsure, say N. ++ ++config RQ_MC ++ bool "Multicore siblings" ++ depends on SCHED_MC && SCHED_MUQSS ++ help ++ With this option enabled, the CPU scheduler will have one runqueue ++ shared by multicore siblings in addition to any SMT siblings. ++ As these physical cores share caches, sharing the runqueue resource ++ will lead to lower latency, but its effects on overhead and throughput ++ are less predictable. As a general rule, 6 or fewer cores will likely ++ benefit from this, while larger CPUs will only derive a latency ++ benefit. If your workloads are primarily single threaded, this will ++ possibly worsen throughput. If you are only concerned about latency ++ then enable this regardless of how many cores you have. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=mc ++ ++ If unsure, say Y. ++ ++config RQ_MC_LLC ++ bool "Multicore siblings (LLC)" ++ depends on SCHED_MC && SCHED_MUQSS ++ help ++ With this option enabled, the CPU scheduler will behave similarly as ++ with "Multicore siblings". ++ This option takes LLC cache into account when scheduling tasks. ++ Option may benefit CPUs with multiple LLC caches, such as Ryzen ++ and Xeon CPUs. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=llc ++ ++ If unsure, say N. ++ ++config RQ_SMP ++ bool "Symmetric Multi-Processing" ++ depends on SMP && SCHED_MUQSS ++ help ++ With this option enabled, the CPU scheduler will have one runqueue ++ shared by all physical CPUs unless they are on separate NUMA nodes. ++ As physical CPUs usually do not share resources, sharing the runqueue ++ will normally worsen throughput but improve latency. If you only ++ care about latency enable this. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=smp ++ ++ If unsure, say N. ++ ++config RQ_ALL ++ bool "NUMA" ++ depends on SMP && SCHED_MUQSS ++ help ++ With this option enabled, the CPU scheduler will have one runqueue ++ regardless of the architecture configuration, including across NUMA ++ nodes. This can substantially decrease throughput in NUMA ++ configurations, but light NUMA designs will not be dramatically ++ affected. This option should only be chosen if latency is the prime ++ concern. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=all ++ ++ If unsure, say N. ++endchoice ++ ++config SHARERQ ++ int ++ default 0 if RQ_NONE ++ default 1 if RQ_SMT ++ default 2 if RQ_MC ++ default 3 if RQ_MC_LLC ++ default 4 if RQ_SMP ++ default 5 if RQ_ALL ++ ++config MUQSS_IOTIME ++ bool "I/O aware CPU scheduling support" ++ depends on SCHED_MUQSS && BLOCK ++ default y ++ help ++ Ordinarily the CPU scheduler can see how much CPU a task uses, but it ++ is blind to how much block device time that task consumes. A task ++ that keeps a disk busy on everyone else's behalf looks identical to ++ one that is merely idle, so it keeps full CPU priority while other ++ tasks wait behind its I/O. ++ ++ This option attaches the scheduler to the block layer so that the ++ time a device spends servicing each request is attributed back to the ++ task that asked for it, and accumulates that as a per task I/O time ++ figure. It is exported per task through /proc//iotime and per ++ device through debugfs. ++ ++ That figure is charged against the task's virtual deadline, demoting a ++ task in proportion to the device time it consumed. This stops a task ++ that keeps a disk busy on everybody else's behalf from retaining full ++ CPU priority for free, while leaving tasks that merely wait on I/O as ++ responsive as they are now. ++ ++ The charge is one nanosecond of deadline per nanosecond of device ++ time, so I/O and CPU cost a task equally. That rate is what the ++ feature means rather than a setting within it, so there is nothing to ++ tune it by. ++ ++ The same machinery charges a task for kernel work done in another ++ thread's context on its behalf, which today means kworkers running ++ work items it queued. Kernel work a task asks for in its own context ++ needs no such help, since it already comes out of that task's own ++ timeslice; what escapes the scheduler is the work handed to somebody ++ else, and forcing IRQ threading moves more work into that blind spot. ++ That is charged one for one, since a nanosecond of CPU time costs the ++ same wherever the kernel chose to spend it. It is reported alongside ++ the I/O figures. ++ ++ Doing this costs a timestamp and an owner reference on every block ++ request, and an accounting update on every completion. On very fast ++ devices, or under heavy small random I/O, that overhead is measurable ++ and may reduce peak I/O throughput. Say N if you are tuning purely ++ for storage benchmarks. ++ ++ When disabled this is compiled out entirely and costs nothing. ++ ++ If unsure, say Y. ++ ++# ++# Mainline features MuQSS replaces or does not implement are gated with ++# "depends on !SCHED_MUQSS" at their point of definition. Kconfig scopes a ++# "depends on" to the properties declared in the same config block, so a ++# dependency cannot be attached to a symbol from here; each gate has to live ++# beside the definition it constrains. They are: ++# ++# init/Kconfig UCLAMP_TASK ++# SCHED_PROXY_EXEC ++# NUMA_BALANCING ++# SCHED_CACHE ++# SCHED_MM_CID ++# CGROUP_CPUACCT ++# SCHED_AUTOGROUP ++# RSEQ_SLICE_EXTENSION ++# if CGROUP_SCHED (group scheduling ++# block: FAIR_GROUP_SCHED, ++# CFS_BANDWIDTH, RT_GROUP_SCHED, ++# GROUP_SCHED_*, EXT_GROUP_SCHED) ++# kernel/Kconfig.preempt PREEMPT_RT ++# SCHED_CORE ++# SCHED_CLASS_EXT ++# kernel/trace/rv/monitors/deadline/Kconfig RV_MON_DEADLINE +diff --git a/kernel/Kconfig.hz b/kernel/Kconfig.hz +--- a/kernel/Kconfig.hz ++++ b/kernel/Kconfig.hz +@@ -3,9 +3,29 @@ + # Timer Interrupt Frequency Configuration + # + ++# ++# Silent symbols keep CONFIG_HZ_* available to the rest of the tree and to ++# inherited .configs, but they are not choice members. The promptable ++# *_NODEF names are what olddefconfig can stick on; renaming every rate ++# (including 100) means a saved "# CONFIG_HZ_100 is not set" cannot pin the ++# choice away from the MuQSS default. ++# ++config HZ_100 ++ bool ++ ++config HZ_250 ++ bool ++ ++config HZ_300 ++ bool ++ ++config HZ_1000 ++ bool ++ + choice + prompt "Timer frequency" +- default HZ_250 ++ default HZ_100_NODEF if SCHED_MUQSS ++ default HZ_250_NODEF if !SCHED_MUQSS + help + Allows the configuration of the timer frequency. It is customary + to have the timer interrupt run at 1000 Hz but 100 Hz may be more +@@ -17,31 +37,45 @@ choice + per second. + + +- config HZ_100 ++ config HZ_100_NODEF + bool "100 HZ" ++ select HZ_100 + help ++ 100 Hz is a suitable choice in combination with MuQSS which does ++ not rely on ticks for rescheduling interrupts, and is not Hz limited ++ for timeouts and sleeps from both the kernel and userspace. ++ This allows us to benefit from the lower overhead and higher ++ throughput of fewer timer ticks. ++ ++ Non-MuQSS kernels: + 100 Hz is a typical choice for servers, SMP and NUMA systems + with lots of processors that may show reduced performance if + too many timer interrupts are occurring. + +- config HZ_250 ++ config HZ_250_NODEF + bool "250 HZ" ++ select HZ_250 + help + 250 Hz is a good compromise choice allowing server performance + while also showing good interactive responsiveness even + on SMP and NUMA systems. If you are going to be using NTSC video + or multimedia, select 300Hz instead. + +- config HZ_300 ++ 250 Hz is the default choice for the mainline scheduler but not ++ advantageous in combination with MuQSS. ++ ++ config HZ_300_NODEF + bool "300 HZ" ++ select HZ_300 + help + 300 Hz is a good compromise choice allowing server performance + while also showing good interactive responsiveness even + on SMP and NUMA systems and exactly dividing by both PAL and + NTSC frame rates for video and multimedia work. + +- config HZ_1000 ++ config HZ_1000_NODEF + bool "1000 HZ" ++ select HZ_1000 + help + 1000 Hz is the preferred choice for desktop systems and other + systems requiring fast interactive responses to events. +diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt +--- a/kernel/Kconfig.preempt ++++ b/kernel/Kconfig.preempt +@@ -14,15 +14,35 @@ config PREEMPT_BUILD + config ARCH_HAS_PREEMPT_LAZY + bool + ++# ++# Silent symbols keep CONFIG_PREEMPT{,_NONE,_VOLUNTARY,_LAZY} available to ++# the rest of the tree and to inherited .configs, but they are not choice ++# members. The promptable *_NODEF names are what olddefconfig can stick on; ++# renaming every model (including PREEMPT) means a saved ++# "# CONFIG_PREEMPT is not set" or CONFIG_PREEMPT_{NONE,VOLUNTARY,LAZY}=y ++# cannot pin the choice away from full preempt. ++# ++config PREEMPT_NONE ++ bool ++ ++config PREEMPT_VOLUNTARY ++ bool ++ ++config PREEMPT ++ bool ++ ++config PREEMPT_LAZY ++ bool ++ + choice + prompt "Preemption Model" +- default PREEMPT_LAZY if ARCH_HAS_PREEMPT_LAZY +- default PREEMPT_NONE ++ default PREEMPT_NODEF + +-config PREEMPT_NONE ++config PREEMPT_NONE_NODEF + bool "No Forced Preemption (Server)" + depends on !PREEMPT_RT + depends on ARCH_NO_PREEMPT ++ select PREEMPT_NONE + select PREEMPT_NONE_BUILD if !PREEMPT_DYNAMIC + help + This is the traditional Linux preemption model, geared towards +@@ -35,11 +55,12 @@ config PREEMPT_NONE + raw processing power of the kernel, irrespective of scheduling + latencies. + +-config PREEMPT_VOLUNTARY +- bool "Voluntary Kernel Preemption (Desktop)" ++config PREEMPT_VOLUNTARY_NODEF ++ bool "Voluntary Kernel Preemption (Nothing)" + depends on !ARCH_HAS_PREEMPT_LAZY + depends on !ARCH_NO_PREEMPT + depends on !PREEMPT_RT ++ select PREEMPT_VOLUNTARY + select PREEMPT_VOLUNTARY_BUILD if !PREEMPT_DYNAMIC + help + This option reduces the latency of the kernel by adding more +@@ -54,11 +75,13 @@ config PREEMPT_VOLUNTARY + applications to run more 'smoothly' even when the system is + under load. + +- Select this if you are building a kernel for a desktop system. ++ Select this for no system in particular (choose Preemptible ++ instead on a desktop if you know what's good for you). + +-config PREEMPT ++config PREEMPT_NODEF + bool "Preemptible Kernel (Low-Latency Desktop)" + depends on !ARCH_NO_PREEMPT ++ select PREEMPT + select PREEMPT_BUILD if !PREEMPT_DYNAMIC + help + This option reduces the latency of the kernel by making +@@ -75,10 +98,11 @@ config PREEMPT + embedded system with latency requirements in the milliseconds + range. + +-config PREEMPT_LAZY ++config PREEMPT_LAZY_NODEF + bool "Scheduler controlled preemption model" + depends on !ARCH_NO_PREEMPT + depends on ARCH_HAS_PREEMPT_LAZY ++ select PREEMPT_LAZY + select PREEMPT_BUILD if !PREEMPT_DYNAMIC + help + This option provides a scheduler driven preemption model that +@@ -87,11 +111,17 @@ config PREEMPT_LAZY + reduce lock holder preemption and recover some of the performance + gains seen from using Voluntary preemption. + ++ It trades the latency that makes a desktop feel responsive for ++ throughput, so choose Preemptible instead unless you are building ++ for a throughput oriented system. ++ + endchoice + + config PREEMPT_RT + bool "Fully Preemptible Kernel (Real-Time)" + depends on EXPERT && ARCH_SUPPORTS_RT && !COMPILE_TEST ++ # MuQSS does not implement the PREEMPT_RT scheduler/locking contract ++ depends on !SCHED_MUQSS + select PREEMPTION + help + This option turns the kernel into a real-time kernel by replacing +@@ -150,7 +180,7 @@ config PREEMPT_DYNAMIC + + config SCHED_CORE + bool "Core Scheduling for SMT" +- depends on SCHED_SMT ++ depends on SCHED_SMT && !SCHED_MUQSS + help + This option permits Core Scheduling, a means of coordinated task + selection across SMT siblings. When enabled -- see +@@ -168,7 +198,7 @@ config SCHED_CORE + + config SCHED_CLASS_EXT + bool "Extensible Scheduling Class" +- depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF ++ depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF && !SCHED_MUQSS + select GENERIC_ALLOCATOR + select STACKTRACE if STACKTRACE_SUPPORT + help +diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c +--- a/kernel/cgroup/cpuset.c ++++ b/kernel/cgroup/cpuset.c +@@ -3083,7 +3083,7 @@ static int cpuset_can_attach(struct cgroup_taskset *tset) + */ + cs->nr_migrate_dl_tasks++; + if (dl_task_needs_bw_move(task, cs->effective_cpus)) +- cs->sum_migrate_dl_bw += task->dl.dl_bw; ++ cs->sum_migrate_dl_bw += tsk_dl_bw(task); + } + } + +diff --git a/kernel/delayacct.c b/kernel/delayacct.c +--- a/kernel/delayacct.c ++++ b/kernel/delayacct.c +@@ -170,7 +170,7 @@ int delayacct_add_tsk(struct taskstats *d, struct task_struct *tsk) + */ + t1 = tsk->sched_info.pcount; + t2 = tsk->sched_info.run_delay; +- t3 = tsk->se.sum_exec_runtime; ++ t3 = tsk_seruntime(tsk); + + d->cpu_count += t1; + +diff --git a/kernel/exit.c b/kernel/exit.c +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -34,6 +34,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -207,7 +208,7 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts + sig->inblock += task_io_get_inblock(tsk); + sig->oublock += task_io_get_oublock(tsk); + task_io_accounting_add(&sig->ioac, &tsk->ioac); +- sig->sum_sched_runtime += tsk->se.sum_exec_runtime; ++ sig->sum_sched_runtime += tsk_seruntime(tsk); + sig->nr_threads--; + __unhash_process(post, tsk, group_dead); + write_sequnlock(&sig->stats_lock); +@@ -298,8 +299,8 @@ void release_task(struct task_struct *p) + proc_flush_pid(thread_pid); + put_pid(thread_pid); + exit_cred_namespaces(p); +- add_device_randomness(&p->se.sum_exec_runtime, +- sizeof(p->se.sum_exec_runtime)); ++ add_device_randomness(&tsk_seruntime(p), ++ sizeof(tsk_seruntime(p))); + free_pids(post.pids); + release_thread(p); + /* +@@ -1037,6 +1038,9 @@ void __noreturn do_exit(long code) + if (tsk->io_context) + exit_io_context(tsk); + ++ /* Give up the I/O owner table slot used to attribute writeback. */ ++ muqss_iotime_release_slot(tsk); ++ + if (tsk->splice_pipe) + free_pipe_info(tsk->splice_pipe); + +diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c +--- a/kernel/locking/rtmutex.c ++++ b/kernel/locking/rtmutex.c +@@ -372,7 +372,7 @@ waiter_update_prio(struct rt_mutex_waiter *waiter, struct task_struct *task) + lockdep_assert(RB_EMPTY_NODE(&waiter->tree.entry)); + + waiter->tree.prio = __waiter_prio(task); +- waiter->tree.deadline = task->dl.deadline; ++ waiter->tree.deadline = tsk_dl_deadline(task); + } + + /* +@@ -393,7 +393,7 @@ waiter_clone_prio(struct rt_mutex_waiter *waiter, struct task_struct *task) + * Only use with rt_waiter_node_{less,equal}() + */ + #define task_to_waiter_node(p) \ +- &(struct rt_waiter_node){ .prio = __waiter_prio(p), .deadline = (p)->dl.deadline } ++ &(struct rt_waiter_node){ .prio = __waiter_prio(p), .deadline = tsk_dl_deadline(p) } + #define task_to_waiter(p) \ + &(struct rt_mutex_waiter){ .tree = *task_to_waiter_node(p) } + +diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile +--- a/kernel/sched/Makefile ++++ b/kernel/sched/Makefile +@@ -23,12 +23,14 @@ ifneq ($(CONFIG_SCHED_OMIT_FRAME_POINTER),y) + # I turn this off for IA-64 only. Andreas Schwab says it's also needed on m68k + # to get a correct value for the wait-channel (WCHAN in ps). --davidm + CFLAGS_core.o := $(PROFILING) -fno-omit-frame-pointer ++CFLAGS_MuQSS.o := $(PROFILING) -fno-omit-frame-pointer + endif + + # Branch profiling isn't noinstr-safe + ifdef CONFIG_TRACE_BRANCH_PROFILING + CFLAGS_build_policy.o += -DDISABLE_BRANCH_PROFILING + CFLAGS_build_utility.o += -DDISABLE_BRANCH_PROFILING ++CFLAGS_build_muqss.o += -DDISABLE_BRANCH_PROFILING + endif + # + # Build efficiency: +@@ -36,7 +38,12 @@ endif + # These compilation units have roughly the same size and complexity - so their + # build parallelizes well and finishes roughly at once: + # ++ifdef CONFIG_SCHED_MUQSS ++obj-y += MuQSS.o ++obj-y += build_muqss.o ++else + obj-y += core.o + obj-y += fair.o + obj-y += build_policy.o + obj-y += build_utility.o ++endif +diff --git a/kernel/sched/MuQSS.c b/kernel/sched/MuQSS.c +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/MuQSS.c +@@ -0,0 +1,10795 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * kernel/sched/MuQSS.c, was kernel/sched.c ++ * ++ * Kernel scheduler and related syscalls ++ * ++ * Copyright (C) 1991-2002 Linus Torvalds ++ * ++ * 1996-12-23 Modified by Dave Grothe to fix bugs in semaphores and ++ * make semaphores SMP safe ++ * 1998-11-19 Implemented schedule_timeout() and related stuff ++ * by Andrea Arcangeli ++ * 2002-01-04 New ultra-scalable O(1) scheduler by Ingo Molnar: ++ * hybrid priority-list and round-robin design with ++ * an array-switch method of distributing timeslices ++ * and per-CPU runqueues. Cleanups and useful suggestions ++ * by Davide Libenzi, preemptible kernel bits by Robert Love. ++ * 2003-09-03 Interactivity tuning by Con Kolivas. ++ * 2004-04-02 Scheduler domains code by Nick Piggin ++ * 2007-04-15 Work begun on replacing all interactivity tuning with a ++ * fair scheduling design by Con Kolivas. ++ * 2007-05-05 Load balancing (smp-nice) and other improvements ++ * by Peter Williams ++ * 2007-05-06 Interactivity improvements to CFS by Mike Galbraith ++ * 2007-07-01 Group scheduling enhancements by Srivatsa Vaddagiri ++ * 2007-11-29 RT balancing improvements by Steven Rostedt, Gregory Haskins, ++ * Thomas Gleixner, Mike Kravetz ++ * 2009-08-13 Brainfuck deadline scheduling policy by Con Kolivas deletes ++ * a whole lot of those previous things. ++ * 2016-10-01 Multiple Queue Skiplist Scheduler scalable evolution of BFS ++ * scheduler by Con Kolivas. ++ * 2019-08-31 LLC bits by Eduards Bezverhijs ++ */ ++/* Out-of-line migrate_{disable,enable} for modules; see linux/sched.h. */ ++#define INSTANTIATE_EXPORTED_MIGRATE_DISABLE ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "../workqueue_internal.h" ++#include "../../io_uring/io-wq.h" ++#include "../smpboot.h" ++ ++#define CREATE_TRACE_POINTS ++#include ++#include ++#include ++#undef CREATE_TRACE_POINTS ++ ++#include "MuQSS.h" ++#include "smp.h" ++ ++/* Modules probe these; core.c exports the same set under CFS. */ ++EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpu); ++EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpumask); ++EXPORT_TRACEPOINT_SYMBOL(sched_set_state_tp); ++ ++#define rt_prio(prio) unlikely((prio) < MAX_RT_PRIO) ++#define rt_task(p) rt_prio((p)->prio) ++#define batch_task(p) (unlikely((p)->policy == SCHED_BATCH)) ++#define is_rt_policy(policy) ((policy) == SCHED_FIFO || \ ++ (policy) == SCHED_RR) ++#define has_rt_policy(p) unlikely(is_rt_policy((p)->policy)) ++ ++#define is_idle_policy(policy) ((policy) == SCHED_IDLEPRIO) ++#define idleprio_task(p) unlikely(is_idle_policy((p)->policy)) ++#define task_running_idle(p) unlikely((p)->prio == IDLE_PRIO) ++ ++#define is_iso_policy(policy) ((policy) == SCHED_ISO) ++#define iso_task(p) unlikely(is_iso_policy((p)->policy)) ++#define task_running_iso(p) unlikely((p)->prio == ISO_PRIO) ++ ++#define rq_idle(rq) ((rq)->rq_prio == PRIO_LIMIT) ++ ++#define ISO_PERIOD (5 * HZ) ++ ++/* ++ * 'User priority' is the nice value converted to something we ++ * can work with better when scaling various scheduler parameters, ++ * it's a [ 0 ... 39 ] range. ++ */ ++#define USER_PRIO(p) ((p)-MAX_RT_PRIO) ++#define TASK_USER_PRIO(p) USER_PRIO((p)->static_prio) ++#define MAX_USER_PRIO (USER_PRIO(MAX_PRIO)) ++#define STOP_PRIO (MAX_RT_PRIO - 1) ++ ++/* ++ * Some helpers for converting to/from various scales. Use shifts to get ++ * approximate multiples of ten for less overhead. These are internal scales ++ * only - niffies themselves are real nanoseconds, so anything converting the ++ * tick into niffies must use TICK_NSEC rather than the approximations here. ++ */ ++#define APPROX_NS_PS (1073741824) /* Approximate ns per second */ ++#define JIFFY_NS (APPROX_NS_PS / HZ) ++#define NS_TO_JIFFIES(TIME) ((TIME) / JIFFY_NS) ++#define HALF_JIFFY_NS (APPROX_NS_PS / HZ / 2) ++/* ++ * time_slice is banked in NS_TO_US() of real nanoseconds, so half a tick in ++ * those units has to come from TICK_NSEC, not from the approximate scale. ++ */ ++#define HALF_JIFFY_US (NS_TO_US(TICK_NSEC) / 2) ++#define MS_TO_NS(TIME) ((TIME) << 20) ++#define MS_TO_US(TIME) ((TIME) << 10) ++#define NS_TO_MS(TIME) ((TIME) >> 20) ++#define NS_TO_US(TIME) ((TIME) >> 10) ++#define US_TO_NS(TIME) ((TIME) << 10) ++#define TICK_APPROX_NS ((APPROX_NS_PS+HZ/2)/HZ) ++ ++#define RESCHED_US (100) /* Reschedule if less than this many ÎŒs left */ ++ ++static void print_scheduler_version(void) ++{ ++ printk(KERN_INFO "MuQSS CPU scheduler v0.31 by Con Kolivas.\n"); ++} ++ ++/* ++ * This is the time all tasks within the same priority round robin. ++ * Value is in ms and set to a minimum of 6ms. ++ * Tunable via /proc interface. ++ */ ++int rr_interval __read_mostly = 6; ++ ++/* ++ * Tunable to choose whether to prioritise latency or throughput, simple ++ * binary yes or no ++ */ ++int sched_interactive __read_mostly = 1; ++ ++/* ++ * sched_iso_cpu - sysctl which determines the cpu percentage SCHED_ISO tasks ++ * are allowed to run five seconds as real time tasks. This is the total over ++ * all online cpus. ++ */ ++int sched_iso_cpu __read_mostly = 70; ++ ++/* ++ * sched_yield_type - Choose what sort of yield sched_yield will perform. ++ * 0: No yield. ++ * 1: Yield only to better priority/deadline tasks. (default) ++ * 2: Expire timeslice and recalculate deadline. ++ */ ++int sched_yield_type __read_mostly = 1; ++ ++/* ++ * The relative length of deadline for each priority(nice) level. ++ */ ++static int prio_ratios[NICE_WIDTH] __read_mostly; ++ ++ ++/* ++ * The quota handed out to tasks of all priority levels when refilling their ++ * time_slice. ++ */ ++static inline int timeslice(void) ++{ ++ return MS_TO_US(rr_interval); ++} ++ ++DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); ++ ++#ifdef CONFIG_SMP ++ ++/* Define RQ share levels */ ++#define RQSHARE_NONE 0 ++#define RQSHARE_SMT 1 ++#define RQSHARE_MC 2 ++#define RQSHARE_MC_LLC 3 ++#define RQSHARE_SMP 4 ++#define RQSHARE_ALL 5 ++ ++/* Define locality levels */ ++#define LOCALITY_SAME 0 ++#define LOCALITY_SMT 1 ++#define LOCALITY_MC_LLC 2 ++#define LOCALITY_MC 3 ++#define LOCALITY_SMP 4 ++#define LOCALITY_DISTANT 5 ++ ++/* ++ * This determines what level of runqueue sharing will be done and is ++ * configurable at boot time with the bootparam rqshare = ++ */ ++static int rqshare __read_mostly = CONFIG_SHARERQ; /* Default RQSHARE_MC */ ++ ++static int __init set_rqshare(char *str) ++{ ++ if (!strncmp(str, "none", 4)) { ++ rqshare = RQSHARE_NONE; ++ return 1; ++ } ++ if (!strncmp(str, "smt", 3)) { ++ rqshare = RQSHARE_SMT; ++ return 1; ++ } ++ if (!strncmp(str, "mc", 2)) { ++ rqshare = RQSHARE_MC; ++ return 1; ++ } ++ if (!strncmp(str, "llc", 3)) { ++ rqshare = RQSHARE_MC_LLC; ++ return 1; ++ } ++ if (!strncmp(str, "smp", 3)) { ++ rqshare = RQSHARE_SMP; ++ return 1; ++ } ++ if (!strncmp(str, "all", 3)) { ++ rqshare = RQSHARE_ALL; ++ return 1; ++ } ++ return 0; ++} ++__setup("rqshare=", set_rqshare); ++ ++/* ++ * Total number of runqueues. Equals number of CPUs when there is no runqueue ++ * sharing but is usually less with SMT/MC sharing of runqueues. ++ */ ++static int total_runqueues __read_mostly = 1; ++ ++static cpumask_t cpu_idle_map ____cacheline_aligned_in_smp; ++ ++/* ++ * For asym packing, by default the lower numbered cpu has higher priority. ++ */ ++int __weak arch_asym_cpu_priority(int cpu) ++{ ++ return -cpu; ++} ++ ++#else ++struct rq *uprq; ++#endif /* CONFIG_SMP */ ++ ++/* ++ * sched_smt_active() is unconditionally available now that cpu_smt_mask() is ++ * cpumask_of(cpu) for !CONFIG_SCHED_SMT, so the key must always be defined. ++ */ ++DEFINE_STATIC_KEY_FALSE(sched_smt_present); ++EXPORT_SYMBOL_GPL(sched_smt_present); ++ ++#include "stats.h" ++ ++/* ++ * All common locking functions performed on rq->lock. rq->clock is local to ++ * the CPU accessing it so it can be modified just with interrupts disabled ++ * when we're not updating niffies. ++ * Looking up task_rq must be done under rq->lock to be safe. ++ */ ++ ++/* ++ * RQ-clock updating methods: ++ */ ++ ++#ifdef HAVE_SCHED_AVG_IRQ ++static void update_irq_load_avg(struct rq *rq, long delta); ++#else ++static inline void update_irq_load_avg(struct rq *rq, long delta) {} ++#endif ++ ++/* Use CONFIG_PARAVIRT as this will avoid more #ifdef in arch code. */ ++#ifdef CONFIG_PARAVIRT ++struct static_key paravirt_steal_rq_enabled; ++#endif ++ ++static void update_rq_clock_task(struct rq *rq, s64 delta) ++{ ++/* ++ * In theory, the compile should just see 0 here, and optimize out the call ++ * to sched_rt_avg_update. But I don't trust it... ++ */ ++ s64 __maybe_unused steal = 0, irq_delta = 0; ++#ifdef CONFIG_IRQ_TIME_ACCOUNTING ++ irq_delta = irq_time_read(cpu_of(rq)) - rq->prev_irq_time; ++ ++ /* ++ * Since irq_time is only updated on {soft,}irq_exit, we might run into ++ * this case when a previous update_rq_clock() happened inside a ++ * {soft,}irq region. ++ * ++ * When this happens, we stop ->clock_task and only update the ++ * prev_irq_time stamp to account for the part that fit, so that a next ++ * update will consume the rest. This ensures ->clock_task is ++ * monotonic. ++ * ++ * It does however cause some slight miss-attribution of {soft,}irq ++ * time, a more accurate solution would be to update the irq_time using ++ * the current rq->clock timestamp, except that would require using ++ * atomic ops. ++ */ ++ if (irq_delta > delta) ++ irq_delta = delta; ++ ++ rq->prev_irq_time += irq_delta; ++ delta -= irq_delta; ++#endif ++#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING ++ if (static_key_false((¶virt_steal_rq_enabled))) { ++ steal = paravirt_steal_clock(cpu_of(rq)); ++ steal -= rq->prev_steal_time_rq; ++ ++ if (unlikely(steal > delta)) ++ steal = delta; ++ ++ rq->prev_steal_time_rq += steal; ++ delta -= steal; ++ } ++#endif ++ rq->clock_task += delta; ++ ++#ifdef CONFIG_HAVE_SCHED_AVG_IRQ ++ if (irq_delta + steal) ++ update_irq_load_avg(rq, irq_delta + steal); ++#endif ++} ++ ++static inline void update_rq_clock(struct rq *rq) ++{ ++ s64 delta = sched_clock_cpu(cpu_of(rq)) - rq->clock; ++ ++ if (unlikely(delta < 0)) ++ return; ++ rq->clock += delta; ++ update_rq_clock_task(rq, delta); ++} ++ ++/* ++ * Niffies are a globally increasing nanosecond counter. They're only used by ++ * update_load_avg and time_slice_expired, however deadlines are based on them ++ * across CPUs. Update them whenever we will call one of those functions, and ++ * synchronise them across CPUs whenever we hold both runqueue locks. ++ * ++ * Niffies are the highest of two absolute values that each advance at real ++ * time: this runqueue's clock, and a tick line advanced by whole jiffies so ++ * that we keep counting if the rq clock stalls. Because both are absolute, ++ * time contributed by the tick line, or imported by synchronise_niffies(), is ++ * never counted again when the rq clock catches up - niffies simply stall ++ * until it does. Accumulating the maximum of the two *deltas* instead keeps ++ * every overshoot for good and drifts upwards without bound. ++ */ ++static inline void update_clocks(struct rq *rq) ++{ ++ long jdiff; ++ ++ update_rq_clock(rq); ++ jdiff = jiffies - rq->last_jiffy; ++ if (jdiff > 0) { ++ rq->last_jiffy += jdiff; ++ rq->jiffy_niffies += (u64)jdiff * TICK_NSEC; ++ } ++ if (rq->niffies < rq->jiffy_niffies) ++ rq->niffies = rq->jiffy_niffies; ++ if (rq->niffies < rq->clock) ++ rq->niffies = rq->clock; ++} ++ ++/* ++ * Any time we have two runqueues locked we use that as an opportunity to ++ * synchronise niffies to the highest value as idle ticks may have artificially ++ * kept niffies low on one CPU and the truth can only be later. ++ */ ++static inline void synchronise_niffies(struct rq *rq1, struct rq *rq2) ++{ ++ if (rq1->niffies > rq2->niffies) ++ rq2->niffies = rq1->niffies; ++ else ++ rq1->niffies = rq2->niffies; ++} ++ ++/* ++ * double_rq_lock - safely lock two runqueues ++ * ++ * Note this does not disable interrupts like task_rq_lock, ++ * you need to do so manually before calling. ++ */ ++ ++/* For when we know rq1 != rq2 */ ++static inline void __double_rq_lock(struct rq *rq1, struct rq *rq2) ++ __acquires(rq1->lock) ++ __acquires(rq2->lock) ++{ ++ if (rq1 < rq2) { ++ raw_spin_lock(rq1->lock); ++ raw_spin_lock_nested(rq2->lock, SINGLE_DEPTH_NESTING); ++ } else { ++ raw_spin_lock(rq2->lock); ++ raw_spin_lock_nested(rq1->lock, SINGLE_DEPTH_NESTING); ++ } ++} ++ ++static inline void double_rq_lock(struct rq *rq1, struct rq *rq2) ++ __acquires(rq1->lock) ++ __acquires(rq2->lock) ++{ ++ BUG_ON(!irqs_disabled()); ++ if (rq1->lock == rq2->lock) { ++ raw_spin_lock(rq1->lock); ++ __acquire(rq2->lock); /* Fake it out ;) */ ++ } else ++ __double_rq_lock(rq1, rq2); ++ synchronise_niffies(rq1, rq2); ++} ++ ++/* ++ * double_rq_unlock - safely unlock two runqueues ++ * ++ * Note this does not restore interrupts like task_rq_unlock, ++ * you need to do so manually after calling. ++ */ ++static inline void double_rq_unlock(struct rq *rq1, struct rq *rq2) ++ __releases(rq1->lock) ++ __releases(rq2->lock) ++{ ++ raw_spin_unlock(rq1->lock); ++ if (rq1->lock != rq2->lock) ++ raw_spin_unlock(rq2->lock); ++ else ++ __release(rq2->lock); ++} ++ ++static inline void lock_all_rqs(void) ++{ ++ int cpu; ++ ++ preempt_disable(); ++ for_each_possible_cpu(cpu) { ++ struct rq *rq = cpu_rq(cpu); ++ ++ do_raw_spin_lock(rq->lock); ++ } ++} ++ ++static inline void unlock_all_rqs(void) ++{ ++ int cpu; ++ ++ for_each_possible_cpu(cpu) { ++ struct rq *rq = cpu_rq(cpu); ++ ++ do_raw_spin_unlock(rq->lock); ++ } ++ preempt_enable(); ++} ++ ++/* Specially nest trylock an rq */ ++static inline bool trylock_rq(struct rq *this_rq, struct rq *rq) ++{ ++ if (unlikely(!do_raw_spin_trylock(rq->lock))) ++ return false; ++ spin_acquire(&rq->lock->dep_map, SINGLE_DEPTH_NESTING, 1, _RET_IP_); ++ synchronise_niffies(this_rq, rq); ++ return true; ++} ++ ++/* Unlock a specially nested trylocked rq */ ++static inline void unlock_rq(struct rq *rq) ++{ ++ spin_release(&rq->lock->dep_map, _RET_IP_); ++ do_raw_spin_unlock(rq->lock); ++} ++ ++/* ++ * cmpxchg based fetch_or, macro so it works for different integer types ++ */ ++#define fetch_or(ptr, mask) \ ++ ({ \ ++ typeof(ptr) _ptr = (ptr); \ ++ typeof(mask) _mask = (mask); \ ++ typeof(*_ptr) _old, _val = *_ptr; \ ++ \ ++ for (;;) { \ ++ _old = cmpxchg(_ptr, _val, _val | _mask); \ ++ if (_old == _val) \ ++ break; \ ++ _val = _old; \ ++ } \ ++ _old; \ ++}) ++ ++#if defined(CONFIG_SMP) && defined(TIF_POLLING_NRFLAG) ++/* ++ * Atomically set TIF_NEED_RESCHED and test for TIF_POLLING_NRFLAG, ++ * this avoids any races wrt polling state changes and thereby avoids ++ * spurious IPIs. ++ */ ++static bool set_nr_and_not_polling(struct task_struct *p) ++{ ++ struct thread_info *ti = task_thread_info(p); ++ return !(fetch_or(&ti->flags, _TIF_NEED_RESCHED) & _TIF_POLLING_NRFLAG); ++} ++ ++/* ++ * Atomically set TIF_NEED_RESCHED if TIF_POLLING_NRFLAG is set. ++ * ++ * If this returns true, then the idle task promises to call ++ * sched_ttwu_pending() and reschedule soon. ++ */ ++static bool set_nr_if_polling(struct task_struct *p) ++{ ++ struct thread_info *ti = task_thread_info(p); ++ typeof(ti->flags) old, val = READ_ONCE(ti->flags); ++ ++ for (;;) { ++ if (!(val & _TIF_POLLING_NRFLAG)) ++ return false; ++ if (val & _TIF_NEED_RESCHED) ++ return true; ++ old = cmpxchg(&ti->flags, val, val | _TIF_NEED_RESCHED); ++ if (old == val) ++ break; ++ val = old; ++ } ++ return true; ++} ++ ++#else ++static bool set_nr_and_not_polling(struct task_struct *p) ++{ ++ set_tsk_need_resched(p); ++ return true; ++} ++ ++#ifdef CONFIG_SMP ++static bool set_nr_if_polling(struct task_struct *p) ++{ ++ return false; ++} ++#endif ++#endif ++ ++static bool __wake_q_add(struct wake_q_head *head, struct task_struct *task) ++{ ++ struct wake_q_node *node = &task->wake_q; ++ ++ /* ++ * Atomically grab the task, if ->wake_q is !nil already it means ++ * it's already queued (either by us or someone else) and will get the ++ * wakeup due to that. ++ * ++ * In order to ensure that a pending wakeup will observe our pending ++ * state, even in the failed case, an explicit smp_mb() must be used. ++ */ ++ smp_mb__before_atomic(); ++ if (unlikely(cmpxchg_relaxed(&node->next, NULL, WAKE_Q_TAIL))) ++ return false; ++ ++ /* ++ * The head is context local, there can be no concurrency. ++ */ ++ *head->lastp = node; ++ head->lastp = &node->next; ++ return true; ++} ++ ++/** ++ * wake_q_add() - queue a wakeup for 'later' waking. ++ * @head: the wake_q_head to add @task to ++ * @task: the task to queue for 'later' wakeup ++ * ++ * Queue a task for later wakeup, most likely by the wake_up_q() call in the ++ * same context, _HOWEVER_ this is not guaranteed, the wakeup can come ++ * instantly. ++ * ++ * This function must be used as-if it were wake_up_process(); IOW the task ++ * must be ready to be woken at this location. ++ */ ++void wake_q_add(struct wake_q_head *head, struct task_struct *task) ++{ ++ if (__wake_q_add(head, task)) ++ get_task_struct(task); ++} ++ ++/** ++ * wake_q_add_safe() - safely queue a wakeup for 'later' waking. ++ * @head: the wake_q_head to add @task to ++ * @task: the task to queue for 'later' wakeup ++ * ++ * Queue a task for later wakeup, most likely by the wake_up_q() call in the ++ * same context, _HOWEVER_ this is not guaranteed, the wakeup can come ++ * instantly. ++ * ++ * This function must be used as-if it were wake_up_process(); IOW the task ++ * must be ready to be woken at this location. ++ * ++ * This function is essentially a task-safe equivalent to wake_q_add(). Callers ++ * that already hold reference to @task can call the 'safe' version and trust ++ * wake_q to do the right thing depending whether or not the @task is already ++ * queued for wakeup. ++ */ ++void wake_q_add_safe(struct wake_q_head *head, struct task_struct *task) ++{ ++ if (!__wake_q_add(head, task)) ++ put_task_struct(task); ++} ++ ++void wake_up_q(struct wake_q_head *head) ++{ ++ struct wake_q_node *node = head->first; ++ ++ while (node != WAKE_Q_TAIL) { ++ struct task_struct *task; ++ ++ task = container_of(node, struct task_struct, wake_q); ++ BUG_ON(!task); ++ /* Task can safely be re-inserted now */ ++ node = node->next; ++ task->wake_q.next = NULL; ++ ++ /* ++ * wake_up_process() executes a full barrier, which pairs with ++ * the queueing in wake_q_add() so as not to miss wakeups. ++ */ ++ wake_up_process(task); ++ put_task_struct(task); ++ } ++} ++ ++static inline void smp_sched_reschedule(int cpu) ++{ ++ if (likely(cpu_online(cpu))) ++ smp_send_reschedule(cpu); ++} ++ ++/* ++ * resched_task - mark a task 'to be rescheduled now'. ++ * ++ * On UP this means the setting of the need_resched flag, on SMP it ++ * might also involve a cross-CPU call to trigger the scheduler on ++ * the target CPU. ++ */ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++static DEFINE_STATIC_KEY_FALSE(sk_dynamic_preempt_lazy); ++static __always_inline bool dynamic_preempt_lazy(void) ++{ ++ return static_branch_unlikely(&sk_dynamic_preempt_lazy); ++} ++#else ++static __always_inline bool dynamic_preempt_lazy(void) ++{ ++ return IS_ENABLED(CONFIG_PREEMPT_LAZY); ++} ++#endif ++ ++static void resched_task(struct task_struct *p) ++{ ++ int cpu; ++#ifdef CONFIG_LOCKDEP ++ /* Kernel threads call this when creating workqueues while still ++ * inactive from __kthread_bind_mask, holding only the pi_lock */ ++ if (!(p->flags & PF_KTHREAD)) { ++ struct rq *rq = task_rq(p); ++ ++ lockdep_assert_held(rq->lock); ++ } ++#endif ++ if (test_tsk_need_resched(p)) ++ return; ++ ++ cpu = task_cpu(p); ++ if (cpu == smp_processor_id()) { ++ set_tsk_need_resched(p); ++ set_preempt_need_resched(); ++ return; ++ } ++ ++ if (set_nr_and_not_polling(p)) ++ smp_sched_reschedule(cpu); ++ else ++ trace_sched_wake_idle_without_ipi(cpu); ++} ++ ++/* ++ * A task that is not running or queued will not have a node set. ++ * A task that is queued but not running will have a node set. ++ * A task that is currently running will have ->on_cpu set but no node set. ++ */ ++static inline bool task_queued(struct task_struct *p) ++{ ++ return !skiplist_node_empty(&p->node); ++} ++ ++static void enqueue_task(struct rq *rq, struct task_struct *p, int flags); ++static inline void resched_if_idle(struct rq *rq); ++ ++static inline bool deadline_before(u64 deadline, u64 time) ++{ ++ return (deadline < time); ++} ++ ++/* ++ * Deadline is "now" in niffies + (offset by priority). Setting the deadline ++ * is the key to everything. It distributes cpu fairly amongst tasks of the ++ * same nice value, it proportions cpu according to nice level, it means the ++ * task that last woke up the longest ago has the earliest deadline, thus ++ * ensuring that interactive tasks get low latency on wake up. The CPU ++ * proportion works out to the square of the virtual deadline difference, so ++ * this equation will give nice 19 3% CPU compared to nice 0. ++ */ ++static inline u64 prio_deadline_diff(int user_prio) ++{ ++ return (prio_ratios[user_prio] * rr_interval * (MS_TO_NS(1) / 128)); ++} ++ ++static inline u64 task_deadline_diff(struct task_struct *p) ++{ ++ return prio_deadline_diff(TASK_USER_PRIO(p)); ++} ++ ++static inline u64 static_deadline_diff(int static_prio) ++{ ++ return prio_deadline_diff(USER_PRIO(static_prio)); ++} ++ ++static inline int longest_deadline_diff(void) ++{ ++ return prio_deadline_diff(39); ++} ++ ++static inline int ms_longest_deadline_diff(void) ++{ ++ return NS_TO_MS(longest_deadline_diff()); ++} ++ ++#ifdef CONFIG_MUQSS_IOTIME ++/* ++ * Scale an I/O time penalty by nice level, exactly as prio_deadline_diff() ++ * scales rr_interval. A deadline is virtual time, already stretched by ++ * prio_ratios[] before it means anything; adding raw nanoseconds of device ++ * time to it mixes two different currencies. Passing the penalty through the ++ * same ratio makes it commensurate with the deadline it is added to, so a ++ * given amount of I/O costs a task the same share of its own allotment ++ * whatever its nice level, and a niced down task is pushed back further in ++ * absolute terms than a niced up one for the same device time. ++ * ++ * The multiply is 64 bit, as it is in prio_deadline_diff(). Wrapping it would ++ * need a penalty over U64_MAX / prio_ratios[39], which is some forty days of ++ * device occupancy charged to one task between two scheduling events, so it ++ * is not a case worth writing code for. ++ */ ++static inline u64 prio_penalty_diff(int user_prio, u64 penalty) ++{ ++ return penalty * prio_ratios[user_prio] / 128; ++} ++ ++/* ++ * Idleprio tasks are charged at the highest nice level whatever nice value ++ * they happen to carry. SCHED_IDLEPRIO is a class beneath the whole nice ++ * range rather than a position within it, and a task there has said it should ++ * run only when nothing else wants the CPU. Scaling by its own nice would let ++ * an idleprio task sitting at nice -20 be charged the lightest rate of all ++ * for keeping the disk busy, which is backwards. Their deadlines already sort ++ * below everything else in enqueue_task(); this keeps what they are charged ++ * for I/O consistent with that. ++ */ ++static inline u64 task_penalty_diff(struct task_struct *p, u64 penalty) ++{ ++ if (idleprio_task(p)) ++ return prio_penalty_diff(39, penalty); ++ ++ return prio_penalty_diff(TASK_USER_PRIO(p), penalty); ++} ++#endif ++ ++#ifdef CONFIG_MUQSS_IOTIME ++/* ++ * Take the block device time accumulated on this task's behalf since it was ++ * last charged, and convert it to an amount of virtual deadline to push the ++ * task back by. The debt is consumed as it is read, so each nanosecond of ++ * device time demotes the task exactly once no matter which caller gets to it ++ * first. The charge is added to the deadline, so it is a demotion, not a ++ * boost: a task that keeps a device busy for everybody else stops getting ++ * full CPU priority for free. ++ * ++ * Charged one for one, a nanosecond of deadline for each nanosecond of device ++ * time consumed. That rate says device time and CPU time cost a task the ++ * same, which is the premise of the feature rather than a setting within it, ++ * so there is nothing to weight one against the other with. ++ * ++ * The charge is then scaled by nice through task_penalty_diff(), the same way ++ * rr_interval is, so that it is in the same virtual currency as the deadline ++ * it is added to. One for one is therefore one for one in deadline terms ++ * rather than in raw nanoseconds: at nice 0 the ratio is prio_ratios[20] / ++ * 128, about 6.7, exactly as an ordinary nice 0 timeslice is worth 6.7 ++ * rr_intervals of deadline. ++ * ++ * There is no ceiling. A task that keeps a device busy can be demoted ++ * arbitrarily far behind, past the deadline of the lowest nice level. See ++ * MuQSS-iotime-design.md. ++ * ++ * This has to be consumed from both time_slice_expired() and enqueue_task(). ++ * Expiry alone misses the streaming reader, which blocks before exhausting its ++ * slice and so never refreshes its deadline. Enqueue alone is not enough ++ * either: time_slice_expired() assigns the deadline absolutely, resetting the ++ * task to an uncharged baseline, and two of its callers (sched_yield() and ++ * yield_to()) have no enqueue behind them to reapply the charge. ++ */ ++static inline u64 consume_iotime_penalty(struct task_struct *p) ++{ ++ u64 debt = atomic64_xchg(&p->io_debt_ns, 0); ++ ++ if (!debt) ++ return 0; ++ ++ return task_penalty_diff(p, debt); ++} ++ ++/* ++ * The same for CPU time spent in another thread's context on this task's ++ * behalf, which today means kworkers running work items it queued. ++ * ++ * Charged one for one, as device time is. A nanosecond of CPU time is a ++ * nanosecond of CPU time wherever it was spent, and the whole premise ++ * here is that the thread it was spent in should not decide whether it counts. ++ * Work a task asks for in its own context is already charged at exactly that ++ * rate: update_cpu_clock_switch() and update_cpu_clock_tick() deduct it from ++ * ->time_slice with no regard for which mode it was spent in, so a task that ++ * burns its whole quantum inside a syscall is demoted as surely as one that ++ * spun in userspace. Charging deferred work at anything other than parity ++ * would say that where the kernel chose to do the work changes what it cost, ++ * which is the bug this exists to fix. Forcing IRQ threading, as -ck does, ++ * only moves more work into that blind spot. ++ * ++ * It is still scaled by nice through task_penalty_diff(), like every other ++ * addition to a deadline, so that it is in the same virtual currency as the ++ * deadline it lands on. ++ * ++ * As with iotime there is no ceiling. The runaway this invites is a different ++ * shape to the I/O case and worth naming, because here CPU consumption is ++ * being punished with CPU demotion: a demoted task need not stop generating ++ * the work, so it can be starved while kworkers keep charging it. It is ++ * bounded in practice because the debt a task can accrue is bounded by the ++ * work it queued, and a task demoted enough to stop running stops queueing ++ * more. ++ * ++ * Kept as a separate counter from io_debt_ns rather than folded into it, ++ * because the two are quantities of very different size: work item runtimes ++ * are microseconds where device occupancy is milliseconds, and sharing an ++ * accumulator would let one vanish into the rounding of the other. Separate ++ * also keeps them separable in /proc//iotime when the question is why a ++ * task was demoted. ++ */ ++static inline u64 consume_kerntime_penalty(struct task_struct *p) ++{ ++ u64 debt = atomic64_xchg(&p->kern_debt_ns, 0); ++ ++ if (!debt) ++ return 0; ++ ++ return task_penalty_diff(p, debt); ++} ++#else ++static inline u64 consume_iotime_penalty(struct task_struct *p) ++{ ++ return 0; ++} ++ ++static inline u64 consume_kerntime_penalty(struct task_struct *p) ++{ ++ return 0; ++} ++#endif ++ ++/* ++ * Everything charged against a task's deadline that did not come out of its ++ * own time_slice. ++ */ ++static inline u64 consume_task_penalty(struct task_struct *p) ++{ ++ return consume_iotime_penalty(p) + consume_kerntime_penalty(p); ++} ++ ++static inline bool rq_local(struct rq *rq); ++ ++#ifndef SCHED_CAPACITY_SCALE ++#define SCHED_CAPACITY_SCALE 1024 ++#endif ++ ++static inline int rq_load(struct rq *rq) ++{ ++ return rq->nr_running; ++} ++ ++/* ++ * Update the load average for feeding into cpu frequency governors. Use a ++ * rough estimate of a rolling average with ~ time constant of 32ms. ++ * 80/128 ~ 0.63. * 80 / 32768 / 128 == * 5 / 262144 ++ * Make sure a call to update_clocks has been made before calling this to get ++ * an updated rq->niffies. ++ */ ++static void update_load_avg(struct rq *rq, unsigned int flags) ++{ ++ long us_interval, load; ++ ++ us_interval = NS_TO_US(rq->niffies - rq->load_update); ++ if (unlikely(us_interval <= 0)) ++ return; ++ ++ load = rq->load_avg - (rq->load_avg * us_interval * 5 / 262144); ++ if (unlikely(load < 0)) ++ load = 0; ++ load += rq_load(rq) * SCHED_CAPACITY_SCALE * us_interval * 5 / 262144; ++ rq->load_avg = load; ++ ++ rq->load_update = rq->niffies; ++ update_irq_load_avg(rq, 0); ++ if (likely(rq_local(rq))) ++ cpufreq_trigger(rq, flags); ++} ++ ++#ifdef HAVE_SCHED_AVG_IRQ ++/* ++ * IRQ variant of update_load_avg below. delta is actually time in nanoseconds ++ * here so we scale curload to how long it's been since the last update. ++ */ ++static void update_irq_load_avg(struct rq *rq, long delta) ++{ ++ long us_interval, load; ++ ++ us_interval = NS_TO_US(rq->niffies - rq->irq_load_update); ++ if (unlikely(us_interval <= 0)) ++ return; ++ ++ load = rq->irq_load_avg - (rq->irq_load_avg * us_interval * 5 / 262144); ++ if (unlikely(load < 0)) ++ load = 0; ++ load += NS_TO_US(delta) * SCHED_CAPACITY_SCALE * 5 / 262144; ++ rq->irq_load_avg = load; ++ ++ rq->irq_load_update = rq->niffies; ++} ++#endif ++ ++static inline void update_best_key(struct rq *rq) ++{ ++ WRITE_ONCE(rq->sl->best_key, rq->node->next[0]->key); ++} ++ ++/* ++ * Removing from the runqueue. Enter with rq locked. Deleting a task ++ * from the skip list is done via the stored node reference in the task struct ++ * and does not require a full look up. Thus it occurs in O(k) time where k ++ * is the "level" of the list the task was stored at - usually 0, max 3. ++ */ ++static void dequeue_task(struct rq *rq, struct task_struct *p, int flags) ++{ ++ /* ++ * Everything below is accounting for @p having been on @rq, so none of ++ * it may happen if it was not: a task whose node is unlinked is on no ++ * runqueue at all and skiplist_delete() has left the list untouched. ++ * ++ * nr_running in particular is unsigned, so a single spurious decrement ++ * wraps it to ~0 and the runqueue never looks empty again. That wedges ++ * sched_cpu_wait_empty(), whose only exit is nr_running <= 1, and a CPU ++ * offline then hangs forever with the dying CPU idle. psi_dequeue() ++ * would likewise clear state that is counted somewhere else. Leaving ++ * best_key alone is right for the same reason - the list did not ++ * change. ++ * ++ * skiplist_delete() has already warned; this only keeps a lost race in ++ * the caller from becoming permanent corruption of the runqueue. ++ */ ++ if (unlikely(!skiplist_delete(rq->sl, &p->node))) ++ return; ++ ++ update_best_key(rq); ++ update_clocks(rq); ++ ++ if (!(flags & DEQUEUE_SAVE)) { ++ sched_info_dequeue(rq, p); ++ /* Pass full flags: DEQUEUE_SLEEP vs migration clear all state. */ ++ psi_dequeue(p, flags); ++ } ++ rq->nr_running--; ++ if (rt_task(p)) ++ rq->rt_nr_running--; ++ update_load_avg(rq, flags); ++} ++ ++#ifdef CONFIG_PREEMPT_RCU ++static bool rcu_read_critical(struct task_struct *p) ++{ ++ return p->rcu_read_unlock_special.b.blocked; ++} ++#else /* CONFIG_PREEMPT_RCU */ ++#define rcu_read_critical(p) (false) ++#endif /* CONFIG_PREEMPT_RCU */ ++ ++/* ++ * To determine if it's safe for a task of SCHED_IDLEPRIO to actually run as ++ * an idle task, we ensure none of the following conditions are met. ++ */ ++static bool idleprio_suitable(struct task_struct *p) ++{ ++ return (!(p->sched_contributes_to_load) && !(p->flags & (PF_EXITING)) && ++ !signal_pending(p) && !rcu_read_critical(p) && !freezing(p)); ++} ++ ++/* ++ * To determine if a task of SCHED_ISO can run in pseudo-realtime, we check ++ * that the iso_refractory flag is not set. ++ */ ++static inline bool isoprio_suitable(struct rq *rq) ++{ ++ return !rq->iso_refractory; ++} ++ ++static inline void inc_nr_running(struct rq *rq) ++{ ++ rq->nr_running++; ++ if (trace_sched_update_nr_running_tp_enabled()) { ++ call_trace_sched_update_nr_running(rq, 1); ++ } ++} ++ ++static inline void dec_nr_running(struct rq *rq) ++{ ++ rq->nr_running--; ++ if (trace_sched_update_nr_running_tp_enabled()) { ++ call_trace_sched_update_nr_running(rq, -1); ++ } ++} ++ ++/* ++ * A running task is off the skiplist, so its share of rt_nr_running is not ++ * maintained by the enqueue/dequeue pair; __schedule() hands that slot over on ++ * every switch instead. When a *running* task's priority crosses the rt ++ * boundary neither happens, so fix the count up here. ++ */ ++static inline void rt_running_reprio(struct rq *rq, int oldprio, int newprio) ++{ ++ if (!rt_prio(newprio) == !rt_prio(oldprio)) ++ return; ++ ++ if (rt_prio(newprio)) ++ rq->rt_nr_running++; ++ else ++ rq->rt_nr_running--; ++} ++ ++/* ++ * Adding to the runqueue. Enter with rq locked. ++ */ ++static void enqueue_task(struct rq *rq, struct task_struct *p, int flags) ++{ ++ unsigned int randseed, cflags = 0; ++ u64 sl_id, penalty; ++ ++ if (!rt_task(p)) { ++ /* Check it hasn't gotten rt from PI */ ++ if ((idleprio_task(p) && idleprio_suitable(p)) || ++ (iso_task(p) && isoprio_suitable(rq))) ++ p->prio = p->normal_prio; ++ else ++ p->prio = NORMAL_PRIO; ++ } else ++ rq->rt_nr_running++; ++ /* ++ * The sl_id key passed to the skiplist generates a sorted list. ++ * Realtime and sched iso tasks run FIFO so they only need be sorted ++ * according to priority. The skiplist will put tasks of the same ++ * key inserted later in FIFO order. Tasks of sched normal, batch ++ * and idleprio are sorted according to their deadlines. Idleprio ++ * tasks are offset by an impossibly large deadline value ensuring ++ * they get sorted into last positions, but still according to their ++ * own deadlines. This creates a "landscape" of skiplists running ++ * from priority 0 realtime in first place to the lowest priority ++ * idleprio tasks last. Skiplist insertion is an O(log n) process. ++ */ ++ /* ++ * Charge for device time and kernel work consumed since this task was ++ * last queued. Consumed unconditionally so debt cannot accumulate ++ * while a task is realtime, but only applied below, since realtime and ++ * iso tasks sort by priority rather than by deadline and so are never ++ * demoted. ++ */ ++ penalty = consume_task_penalty(p); ++ ++ if (p->prio <= ISO_PRIO) { ++ sl_id = p->prio; ++ } else { ++ p->deadline += penalty; ++ sl_id = p->deadline; ++ if (idleprio_task(p)) { ++ if (p->prio == IDLE_PRIO) ++ sl_id |= 0xF000000000000000; ++ else ++ sl_id += longest_deadline_diff(); ++ } ++ } ++ /* ++ * Some architectures don't have better than microsecond resolution ++ * so mask out ~microseconds as the random seed for skiplist insertion. ++ */ ++ update_clocks(rq); ++ if (!(flags & ENQUEUE_RESTORE)) { ++ sched_info_enqueue(rq, p); ++ /* Full flags so ENQUEUE_MIGRATED is visible to psi_enqueue(). */ ++ psi_enqueue(p, flags); ++ } ++ ++ randseed = (rq->niffies >> 10) & 0xFFFFFFFF; ++ skiplist_insert(rq->sl, &p->node, sl_id, randseed); ++ update_best_key(rq); ++ if (p->in_iowait) ++ cflags |= SCHED_CPUFREQ_IOWAIT; ++ inc_nr_running(rq); ++ update_load_avg(rq, cflags); ++} ++ ++/* ++ * Returns the relative length of deadline all compared to the shortest ++ * deadline which is that of nice -20. ++ */ ++static inline int task_prio_ratio(struct task_struct *p) ++{ ++ return prio_ratios[TASK_USER_PRIO(p)]; ++} ++ ++/* ++ * task_timeslice - all tasks of all priorities get the exact same timeslice ++ * length. CPU distribution is handled by giving different deadlines to ++ * tasks of different priorities. Use 128 as the base value for fast shifts. ++ */ ++static inline int task_timeslice(struct task_struct *p) ++{ ++ return (rr_interval * task_prio_ratio(p) / 128); ++} ++ ++#ifdef CONFIG_SMP ++/* Entered with rq locked */ ++static inline void resched_if_idle(struct rq *rq) ++{ ++ if (rq_idle(rq)) ++ resched_task(rq->curr); ++} ++ ++static inline bool rq_local(struct rq *rq) ++{ ++ return (rq->cpu == smp_processor_id()); ++} ++#ifdef CONFIG_SMT_NICE ++static const cpumask_t *thread_cpumask(int cpu); ++ ++/* Find the best real time priority running on any SMT siblings of cpu and if ++ * none are running, the static priority of the best deadline task running. ++ * The lookups to the other runqueues is done lockless as the occasional wrong ++ * value would be harmless. */ ++static int best_smt_bias(struct rq *this_rq) ++{ ++ int other_cpu, best_bias = 0; ++ ++ for_each_cpu(other_cpu, &this_rq->thread_mask) { ++ struct rq *rq = cpu_rq(other_cpu); ++ ++ if (rq_idle(rq)) ++ continue; ++ if (unlikely(!rq->online)) ++ continue; ++ if (!rq->rq_mm) ++ continue; ++ if (likely(rq->rq_smt_bias > best_bias)) ++ best_bias = rq->rq_smt_bias; ++ } ++ return best_bias; ++} ++ ++static int task_prio_bias(struct task_struct *p) ++{ ++ if (rt_task(p)) ++ return 1 << 30; ++ else if (task_running_iso(p)) ++ return 1 << 29; ++ else if (task_running_idle(p)) ++ return 0; ++ return MAX_PRIO - p->static_prio; ++} ++ ++static DEFINE_STATIC_KEY_FALSE(smt_nice_enabled); ++ ++/* We've already decided p can run on CPU, now test if it shouldn't for SMT ++ * nice reasons. */ ++static bool smt_should_schedule(struct task_struct *p, struct rq *this_rq) ++{ ++ int best_bias, task_bias; ++ ++ if (!idleprio_suitable(p)) ++ return true; ++ best_bias = best_smt_bias(this_rq); ++ /* The smt siblings are all idle or running IDLEPRIO */ ++ if (best_bias < 1) ++ return true; ++ task_bias = task_prio_bias(p); ++ if (task_bias < 1) ++ return false; ++ if (task_bias >= best_bias) ++ return true; ++ /* Dither 25% cpu of normal tasks regardless of nice difference */ ++ if (best_bias % 4 == 1) ++ return true; ++ /* Sorry, you lose */ ++ return false; ++} ++ ++static inline bool smt_schedule(struct task_struct *p, struct rq *this_rq) ++{ ++ if (!static_branch_unlikely(&smt_nice_enabled)) ++ return true; ++ /* Kernel threads and RT tasks always run */ ++ if (unlikely(!p->mm) || rt_task(p)) ++ return true; ++ return smt_should_schedule(p, this_rq); ++} ++#else /* CONFIG_SMT_NICE */ ++#define smt_schedule(p, this_rq) (true) ++#endif /* CONFIG_SMT_NICE */ ++ ++static inline void atomic_set_cpu(int cpu, cpumask_t *cpumask) ++{ ++ set_bit(cpu, (volatile unsigned long *)cpumask); ++} ++ ++/* ++ * The cpu_idle_map stores a bitmap of all the CPUs currently idle to ++ * allow easy lookup of whether any suitable idle CPUs are available. ++ * It's cheaper to maintain a binary yes/no if there are any idle CPUs on the ++ * idle_cpus variable than to do a full bitmask check when we are busy. The ++ * bits are set atomically but read locklessly as occasional false positive / ++ * negative is harmless. ++ */ ++static inline void set_cpuidle_map(int cpu) ++{ ++ /* ++ * Only an active CPU may advertise itself as a wakeup target. ++ * select_best_cpu() returns resched_best_idle()'s pick directly, ++ * and that pick comes straight out of this map without consulting ++ * is_cpu_allowed(), so a CPU on its way down (online but no longer ++ * active) would keep re-arming itself here every time it idled and ++ * collect fresh wakeups that sched_cpu_wait_empty() has to chase. ++ * set_rq_offline() clears the map; this keeps it clear. ++ */ ++ if (likely(cpu_active(cpu))) ++ atomic_set_cpu(cpu, &cpu_idle_map); ++} ++ ++static inline void atomic_clear_cpu(int cpu, cpumask_t *cpumask) ++{ ++ clear_bit(cpu, (volatile unsigned long *)cpumask); ++} ++ ++static inline void clear_cpuidle_map(int cpu) ++{ ++ atomic_clear_cpu(cpu, &cpu_idle_map); ++} ++ ++static bool suitable_idle_cpus(struct task_struct *p) ++{ ++ return (cpumask_intersects(p->cpus_ptr, &cpu_idle_map)); ++} ++ ++/* ++ * Resched current on rq. We don't know if rq is local to this CPU nor if it ++ * is locked so we do not use an intermediate variable for the task to avoid ++ * having it dereferenced. ++ */ ++static void resched_curr(struct rq *rq) ++{ ++ int cpu; ++ ++ if (test_tsk_need_resched(rq->curr)) ++ return; ++ ++ rq->preempt = rq->curr; ++ cpu = rq->cpu; ++ ++ /* We're doing this without holding the rq lock if it's not task_rq */ ++ ++ if (cpu == smp_processor_id()) { ++ set_tsk_need_resched(rq->curr); ++ set_preempt_need_resched(); ++ return; ++ } ++ ++ if (set_nr_and_not_polling(rq->curr)) ++ smp_sched_reschedule(cpu); ++ else ++ trace_sched_wake_idle_without_ipi(cpu); ++} ++ ++#define CPUIDLE_NO_SIBLING (1) ++#define CPUIDLE_DIFF_THREAD (2) ++#define CPUIDLE_DIFF_CORE_LLC (4) ++#define CPUIDLE_DIFF_CORE (8) ++#define CPUIDLE_CACHE_BUSY (16) ++#define CPUIDLE_DIFF_CPU (32) ++#define CPUIDLE_THREAD_BUSY (64) ++#define CPUIDLE_DIFF_NODE (128) ++ ++/* ++ * The best idle CPU is chosen according to the CPUIDLE ranking above where the ++ * lowest value would give the most suitable CPU to schedule p onto next. The ++ * order works out to be the following: ++ * ++ * Same thread, idle or busy cache, idle or busy threads ++ * Other core, same cache, idle or busy cache, idle threads. ++ * Same node, other CPU, idle cache, idle threads. ++ * Same node, other CPU, busy cache, idle threads. ++ * Other core, same cache, busy threads. ++ * Same node, other CPU, busy threads. ++ * Other node, other CPU, idle cache, idle threads. ++ * Other node, other CPU, busy cache, idle threads. ++ * Other node, other CPU, busy threads. ++ * ++ * CPUIDLE_NO_SIBLING is the least significant rank so it only separates cores ++ * that are otherwise equal, preferring a core with an idle SMT sibling over one ++ * with no siblings at all. On hybrid CPUs that indirectly prefers P cores over ++ * E cores, since only the former have siblings. A core whose sibling is busy ++ * still ranks below a core with no siblings as it only offers half a core. ++ * This does not treat CPUs with a single offline sibling as idle for ++ * simplicity. ++ */ ++static int best_mask_cpu(int best_cpu, struct rq *rq, cpumask_t *tmpmask) ++{ ++ int best_ranking = CPUIDLE_DIFF_NODE | CPUIDLE_THREAD_BUSY | ++ CPUIDLE_DIFF_CPU | CPUIDLE_CACHE_BUSY | CPUIDLE_DIFF_CORE | ++ CPUIDLE_DIFF_CORE_LLC | CPUIDLE_DIFF_THREAD | CPUIDLE_NO_SIBLING; ++ unsigned long best_idle_jiffy = 0; ++ int cpu_tmp; ++ ++ if (cpumask_test_cpu(best_cpu, tmpmask)) ++ goto out; ++ ++ for_each_cpu(cpu_tmp, tmpmask) { ++ int ranking, locality; ++ struct rq *tmp_rq; ++ ++ ranking = 0; ++ tmp_rq = cpu_rq(cpu_tmp); ++ ++ locality = rq->cpu_locality[cpu_tmp]; ++#ifdef CONFIG_NUMA ++ if (locality > LOCALITY_SMP) ++ ranking |= CPUIDLE_DIFF_NODE; ++ else ++#endif ++ if (locality > LOCALITY_MC) ++ ranking |= CPUIDLE_DIFF_CPU; ++#ifdef CONFIG_SCHED_MC ++ else if (locality == LOCALITY_MC_LLC) ++ ranking |= CPUIDLE_DIFF_CORE_LLC; ++ else if (locality == LOCALITY_MC) ++ ranking |= CPUIDLE_DIFF_CORE; ++ if (!(tmp_rq->cache_idle(tmp_rq))) ++ ranking |= CPUIDLE_CACHE_BUSY; ++#endif ++#ifdef CONFIG_SCHED_SMT ++ if (locality == LOCALITY_SMT) ++ ranking |= CPUIDLE_DIFF_THREAD; ++ if (!tmp_rq->has_smt_sibling) ++ ranking |= CPUIDLE_NO_SIBLING; ++ else if (!cpumask_subset(&tmp_rq->thread_mask, &cpu_idle_map)) ++ ranking |= CPUIDLE_THREAD_BUSY; ++#endif ++ /* Also look for the most recently idled CPU as it will likely ++ * be still at a higher CPU frequency */ ++ if (ranking < best_ranking || ++ (ranking == best_ranking && tmp_rq->idle_jiffy > best_idle_jiffy)) { ++ best_cpu = cpu_tmp; ++ best_ranking = ranking; ++ best_idle_jiffy = tmp_rq->idle_jiffy; ++ } ++ } ++out: ++ return best_cpu; ++} ++ ++bool cpus_share_cache(int this_cpu, int that_cpu) ++{ ++ struct rq *this_rq = cpu_rq(this_cpu); ++ ++ return (this_rq->cpu_locality[that_cpu] < LOCALITY_SMP); ++} ++ ++/* As per resched_curr but only will resched idle task */ ++static inline void resched_idle(struct rq *rq) ++{ ++ if (test_tsk_need_resched(rq->idle)) ++ return; ++ ++ rq->preempt = rq->idle; ++ ++ if (rq_local(rq)) { ++ set_tsk_need_resched(rq->idle); ++ set_preempt_need_resched(); ++ return; ++ } ++ ++ /* ++ * Atomically set NEED_RESCHED and only IPI if the idle task is not ++ * polling — same protocol as resched_curr / wake_up_idle_cpu. ++ */ ++ if (set_nr_and_not_polling(rq->idle)) ++ smp_sched_reschedule(rq->cpu); ++ else ++ trace_sched_wake_idle_without_ipi(rq->cpu); ++} ++ ++DEFINE_PER_CPU(cpumask_t, idlemask); ++ ++static struct rq *resched_best_idle(struct task_struct *p, int cpu) ++{ ++ cpumask_t *tmpmask = &(per_cpu(idlemask, cpu)); ++ struct rq *rq; ++ int best_cpu; ++ ++ cpumask_and(tmpmask, p->cpus_ptr, &cpu_idle_map); ++ best_cpu = best_mask_cpu(cpu, task_rq(p), tmpmask); ++ rq = cpu_rq(best_cpu); ++ if (!smt_schedule(p, rq)) ++ return NULL; ++ rq->preempt = p; ++ resched_idle(rq); ++ return rq; ++} ++ ++static inline void resched_suitable_idle(struct task_struct *p) ++{ ++ if (suitable_idle_cpus(p)) ++ resched_best_idle(p, task_cpu(p)); ++} ++ ++static inline struct rq *rq_order(struct rq *rq, int cpu) ++{ ++ return rq->rq_order[cpu]; ++} ++#else /* CONFIG_SMP */ ++static inline void set_cpuidle_map(int cpu) ++{ ++} ++ ++static inline void clear_cpuidle_map(int cpu) ++{ ++} ++ ++static inline bool suitable_idle_cpus(struct task_struct *p) ++{ ++ return uprq->curr == uprq->idle; ++} ++ ++static inline void resched_suitable_idle(struct task_struct *p) ++{ ++} ++ ++static inline void resched_curr(struct rq *rq) ++{ ++ resched_task(rq->curr); ++} ++ ++static inline void resched_if_idle(struct rq *rq) ++{ ++} ++ ++static inline bool rq_local(struct rq *rq) ++{ ++ return true; ++} ++ ++static inline struct rq *rq_order(struct rq *rq, int cpu) ++{ ++ return rq; ++} ++ ++static inline bool smt_schedule(struct task_struct *p, struct rq *rq) ++{ ++ return true; ++} ++ ++/* One CPU shares its cache with itself. */ ++bool cpus_share_cache(int this_cpu, int that_cpu) ++{ ++ return true; ++} ++#endif /* CONFIG_SMP */ ++ ++static inline int normal_prio(struct task_struct *p) ++{ ++ if (has_rt_policy(p)) ++ return MAX_RT_PRIO - 1 - p->rt_priority; ++ if (idleprio_task(p)) ++ return IDLE_PRIO; ++ if (iso_task(p)) ++ return ISO_PRIO; ++ return NORMAL_PRIO; ++} ++ ++/* ++ * Calculate the current priority, i.e. the priority ++ * taken into account by the scheduler. This value might ++ * be boosted by RT tasks as it will be RT if the task got ++ * RT-boosted. If not then it returns p->normal_prio. ++ */ ++static int effective_prio(struct task_struct *p) ++{ ++ p->normal_prio = normal_prio(p); ++ /* ++ * If we are RT tasks or we were boosted to RT priority, ++ * keep the priority unchanged. Otherwise, update priority ++ * to the normal priority: ++ */ ++ if (!rt_prio(p->prio)) ++ return p->normal_prio; ++ return p->prio; ++} ++ ++/* ++ * activate_task - move a task to the runqueue. Enter with rq locked. ++ */ ++static void activate_task(struct rq *rq, struct task_struct *p, int flags) ++{ ++ resched_if_idle(rq); ++ ++ /* Sleep profiling (SLEEP_PROFILING) was removed upstream. */ ++ ++ p->prio = effective_prio(p); ++ enqueue_task(rq, p, flags); ++ p->on_rq = TASK_ON_RQ_QUEUED; ++} ++ ++/* ++ * deactivate_task - If it's running, it's not on the runqueue and we can just ++ * decrement the nr_running. Enter with rq locked. ++ */ ++static inline void deactivate_task(struct task_struct *p, struct rq *rq) ++{ ++ p->on_rq = 0; ++ sched_info_dequeue(rq, p); ++ /* deactivate_task is always DEQUEUE_SLEEP in muqss */ ++ psi_dequeue(p, DEQUEUE_SLEEP); ++} ++ ++/* ++ * PSI counts the state a task is in per CPU, indexed by task_cpu(), so ++ * whenever task_cpu() changes for a task that still carries state, that state ++ * has to be moved with it. That is TSK_RUNNING for a task being taken to ++ * another runqueue, and TSK_IOWAIT for one that is still asleep. Whoever ++ * clears them next does so against task_cpu(p), and if that is no longer the ++ * CPU they were counted on the per-CPU counter underflows. ++ * ++ * TSK_ONCPU is deliberately not moved. It belongs to psi_sched_switch(), and ++ * a task whose CPU is being changed here is by definition not on one. ++ */ ++#ifdef CONFIG_PSI ++static inline unsigned int psi_migrate_begin(struct task_struct *p) ++{ ++ unsigned int migrate = p->psi_flags & ~TSK_ONCPU; ++ ++ if (migrate) ++ psi_task_change(p, migrate, 0); ++ return migrate; ++} ++ ++static inline void psi_migrate_end(struct task_struct *p, unsigned int migrate) ++{ ++ if (migrate) ++ psi_task_change(p, 0, migrate); ++} ++#else /* !CONFIG_PSI */ ++static inline unsigned int psi_migrate_begin(struct task_struct *p) ++{ ++ return 0; ++} ++ ++static inline void psi_migrate_end(struct task_struct *p, unsigned int migrate) ++{ ++} ++#endif /* CONFIG_PSI */ ++ ++#ifdef CONFIG_SMP ++void set_task_cpu(struct task_struct *p, unsigned int new_cpu) ++{ ++ unsigned int migrate; ++ struct rq *rq; ++ ++ if (task_cpu(p) == new_cpu) ++ return; ++ ++ /* Do NOT call set_task_cpu on a currently queued task as we will not ++ * be reliably holding the rq lock after changing CPU. */ ++ BUG_ON(task_queued(p)); ++ rq = task_rq(p); ++ ++#ifdef CONFIG_LOCKDEP ++ /* ++ * The caller should hold either p->pi_lock or rq->lock, when changing ++ * a task's CPU. ->pi_lock for waking tasks, rq->lock for runnable tasks. ++ * ++ * Furthermore, all task_rq users should acquire both locks, see ++ * task_rq_lock(). ++ */ ++ WARN_ON_ONCE(debug_locks && !(lockdep_is_held(&p->pi_lock) || ++ lockdep_is_held(rq->lock))); ++#endif ++ ++ trace_sched_migrate_task(p, new_cpu); ++ rseq_sched_set_ids_changed(p); ++ perf_event_task_migrate(p); ++ ++ /* ++ * After ->cpu is set up to a new value, task_rq_lock(p, ...) can be ++ * successfully executed on another CPU. We must ensure that updates of ++ * per-task data have been completed by this moment. ++ */ ++ smp_wmb(); ++ ++ p->wake_cpu = new_cpu; ++ ++ if (task_running(rq, p)) { ++ /* ++ * We should only be calling this on a running task if we're ++ * holding rq lock. ++ */ ++ lockdep_assert_held(rq->lock); ++ ++ /* ++ * We can't change the task_thread_info CPU on a running task ++ * as p will still be protected by the rq lock of the CPU it ++ * is still running on so we only set the wake_cpu for it to be ++ * lazily updated once off the CPU. ++ */ ++ return; ++ } ++ ++ migrate = psi_migrate_begin(p); ++ WRITE_ONCE(task_thread_info(p)->cpu, new_cpu); ++ psi_migrate_end(p, migrate); ++ /* We're no longer protecting p after this point since we're holding ++ * the wrong runqueue lock. */ ++} ++#endif /* CONFIG_SMP */ ++ ++/* ++ * Move a task off the runqueue and take it to a cpu for it will ++ * become the running task. ++ */ ++static inline void take_task(struct rq *rq, int cpu, struct task_struct *p) ++{ ++ struct rq *p_rq = task_rq(p); ++ ++ dequeue_task(p_rq, p, DEQUEUE_SAVE); ++ if (p_rq != rq) { ++ sched_info_dequeue(p_rq, p); ++ sched_info_enqueue(rq, p); ++ } ++ /* ++ * DEQUEUE_SAVE left TSK_RUNNING in place, and set_task_cpu() moves it ++ * to @cpu along with the task. ++ */ ++ set_task_cpu(p, cpu); ++} ++ ++/* ++ * Returns a descheduling task to the runqueue unless it is being ++ * deactivated. ++ */ ++static inline void return_task(struct task_struct *p, struct rq *rq, ++ int cpu, bool deactivate) ++{ ++ if (deactivate) ++ deactivate_task(p, rq); ++ else { ++#ifdef CONFIG_SMP ++ /* ++ * set_task_cpu was called on the running task that doesn't ++ * want to deactivate so it has to be enqueued to a different ++ * CPU and we need its lock. Tag it to be moved with as the ++ * lock is dropped in finish_lock_switch. ++ */ ++ if (unlikely(p->wake_cpu != cpu)) ++ WRITE_ONCE(p->on_rq, TASK_ON_RQ_MIGRATING); ++ else ++#endif ++ enqueue_task(rq, p, ENQUEUE_RESTORE); ++ } ++} ++ ++/* Enter with rq lock held. We know p is on the local cpu */ ++static inline void __set_tsk_resched(struct task_struct *p) ++{ ++ set_tsk_need_resched(p); ++ set_preempt_need_resched(); ++} ++ ++/** ++ * task_curr - is this task currently executing on a CPU? ++ * @p: the task in question. ++ * ++ * Return: 1 if the task is currently executing. 0 otherwise. ++ */ ++inline int task_curr(const struct task_struct *p) ++{ ++ return cpu_curr(task_cpu(p)) == p; ++} ++ ++static __always_inline ++int __task_state_match(struct task_struct *p, unsigned int state) ++{ ++ if (READ_ONCE(p->__state) & state) ++ return 1; ++ ++ if (READ_ONCE(p->saved_state) & state) ++ return -1; ++ ++ return 0; ++} ++ ++static __always_inline ++int task_state_match(struct task_struct *p, unsigned int state) ++{ ++ int match; ++ ++ /* ++ * Serialize against current_save_and_set_rtlock_wait_state(), ++ * current_restore_rtlock_saved_state(), and __refrigerator(). ++ */ ++ raw_spin_lock_irq(&p->pi_lock); ++ match = __task_state_match(p, state); ++ raw_spin_unlock_irq(&p->pi_lock); ++ ++ return match; ++} ++ ++/* ++ * wait_task_inactive - wait for a thread to unschedule. ++ * ++ * Wait for the thread to block in any of the states set in @match_state. ++ * If it changes, i.e. @p might have woken up, then return zero. When we ++ * succeed in waiting for @p to be off its CPU, we return a positive number ++ * (its total switch count). If a second call a short while later returns ++ * the same number, the caller can be sure that @p has remained unscheduled ++ * the whole time. ++ * ++ * The caller must ensure that the task *will* unschedule sometime soon, ++ * else this function might spin for a *long* time. This function can't ++ * be called with interrupts off, or it may introduce deadlock with ++ * smp_call_function() if an IPI is sent by the same process we are ++ * waiting to become inactive. ++ */ ++unsigned long wait_task_inactive(struct task_struct *p, unsigned int match_state) ++{ ++ int running, queued, match; ++ struct rq_flags rf; ++ unsigned long ncsw; ++ struct rq *rq; ++ ++ for (;;) { ++ rq = task_rq(p); ++ ++ /* ++ * If the task is actively running on another CPU ++ * still, just relax and busy-wait without holding ++ * any locks. ++ * ++ * NOTE! Since we don't hold any locks, it's not ++ * even sure that "rq" stays as the right runqueue! ++ * But we don't care, since this will return false ++ * if the runqueue has changed and p is actually now ++ * running somewhere else! ++ */ ++ while (task_running(rq, p)) { ++ if (!task_state_match(p, match_state)) ++ return 0; ++ cpu_relax(); ++ } ++ ++ /* ++ * Ok, time to look more closely! We need the rq ++ * lock now, to be *sure*. If we're wrong, we'll ++ * just go back and repeat. ++ */ ++ rq = task_rq_lock(p, &rf); ++ trace_sched_wait_task(p); ++ running = task_running(rq, p); ++ queued = task_on_rq_queued(p); ++ ncsw = 0; ++ if ((match = __task_state_match(p, match_state))) { ++ /* ++ * When matching on p->saved_state, consider this task ++ * still queued so it will wait. ++ */ ++ if (match < 0) ++ queued = 1; ++ ncsw = p->nvcsw | LONG_MIN; /* sets MSB */ ++ } ++ task_rq_unlock(rq, p, &rf); ++ ++ /* ++ * If it changed from the expected state, bail out now. ++ */ ++ if (unlikely(!ncsw)) ++ break; ++ ++ /* ++ * Was it really running after all now that we ++ * checked with the proper locks actually held? ++ * ++ * Oops. Go back and try again.. ++ */ ++ if (unlikely(running)) { ++ cpu_relax(); ++ continue; ++ } ++ ++ /* ++ * It's not enough that it's not actively running, ++ * it must be off the runqueue _entirely_, and not ++ * preempted! ++ * ++ * So if it was still runnable (but just not actively ++ * running right now), it's preempted, and we should ++ * yield - it could be a while. ++ */ ++ if (unlikely(queued)) { ++ ktime_t to = NSEC_PER_SEC / HZ; ++ ++ set_current_state(TASK_UNINTERRUPTIBLE); ++ schedule_hrtimeout(&to, HRTIMER_MODE_REL); ++ continue; ++ } ++ ++ /* ++ * Ahh, all good. It wasn't running, and it wasn't ++ * runnable, which means that it will never become ++ * running in the future either. We're all done! ++ */ ++ break; ++ } ++ ++ return ncsw; ++} ++ ++/*** ++ * kick_process - kick a running thread to enter/exit the kernel ++ * @p: the to-be-kicked thread ++ * ++ * Cause a process which is running on another CPU to enter ++ * kernel-mode, without any delay. (to get signals handled.) ++ * ++ * NOTE: this function doesn't have to take the runqueue lock, ++ * because all it wants to ensure is that the remote task enters ++ * the kernel. If the IPI races and the task has been migrated ++ * to another CPU then no harm is done and the purpose has been ++ * achieved as well. ++ */ ++#ifdef CONFIG_SMP ++void kick_process(struct task_struct *p) ++{ ++ int cpu; ++ ++ preempt_disable(); ++ cpu = task_cpu(p); ++ if ((cpu != smp_processor_id()) && task_curr(p)) ++ smp_sched_reschedule(cpu); ++ preempt_enable(); ++} ++#else /* !CONFIG_SMP */ ++/* @p can only be running on the CPU we are already running on. */ ++void kick_process(struct task_struct *p) ++{ ++} ++#endif /* CONFIG_SMP */ ++EXPORT_SYMBOL_GPL(kick_process); ++ ++/* ++ * RT tasks preempt purely on priority. SCHED_NORMAL tasks preempt on the ++ * basis of earlier deadlines. SCHED_IDLEPRIO don't preempt anything else or ++ * between themselves, they cooperatively multitask. An idle rq scores as ++ * prio PRIO_LIMIT so it is always preempted. ++ */ ++static inline bool ++can_preempt(struct task_struct *p, int prio, u64 deadline) ++{ ++ /* Better static priority RT task or better policy preemption */ ++ if (p->prio < prio) ++ return true; ++ if (p->prio > prio) ++ return false; ++ if (p->policy == SCHED_BATCH) ++ return false; ++ /* SCHED_NORMAL and ISO will preempt based on deadline */ ++ if (!deadline_before(p->deadline, deadline)) ++ return false; ++ return true; ++} ++ ++#ifdef CONFIG_SMP ++ ++/* ++ * Per-CPU kthreads are allowed to run on !active && online CPUs, see ++ * __set_cpus_allowed_ptr(). ++ */ ++static inline bool is_cpu_allowed(struct task_struct *p, int cpu) ++{ ++ if (!cpumask_test_cpu(cpu, p->cpus_ptr)) ++ return false; ++ ++ /* migrate_disable() must be allowed to finish on an online CPU. */ ++ if (is_migration_disabled(p)) ++ return cpu_online(cpu); ++ ++ if (!(p->flags & PF_KTHREAD)) ++ return cpu_active(cpu); ++ ++ /* KTHREAD_IS_PER_CPU is always allowed. */ ++ if (kthread_is_per_cpu(p)) ++ return cpu_online(cpu); ++ ++ /* Regular kernel threads don't get to stay during offline. */ ++ if (cpu_dying(cpu)) ++ return false; ++ ++ /* But are allowed during online. */ ++ return cpu_online(cpu); ++} ++ ++/* ++ * Check to see if p can run on cpu, and if not, whether there are any online ++ * CPUs it can run on instead. This only happens with the hotplug threads that ++ * bring up the CPUs. ++ */ ++static inline bool sched_other_cpu(struct task_struct *p, int cpu) ++{ ++ if (unlikely(is_migration_disabled(p) && task_cpu(p) != cpu)) ++ return true; ++ /* ++ * Defer to the single placement predicate: userspace is refused a ++ * deactivated CPU, a regular kthread is refused a dying one, while ++ * per-CPU kthreads and a migrate_disable() pin on this CPU still ++ * may run there. ++ */ ++ if (likely(cpumask_test_cpu(cpu, p->cpus_ptr))) ++ return !is_cpu_allowed(p, cpu); ++ if (p->nr_cpus_allowed == 1) { ++ cpumask_t valid_mask; ++ ++ /* ++ * Nowhere left to send it, so it may as well run here. The mask ++ * has to be the same one valid_task_cpu() uses to pick the ++ * destination, or the two disagree and the task is placed on a ++ * CPU that then refuses to run it. ++ * ++ * That is what a regular kthread affine to a single dying CPU ++ * hits: the CPU is still online, so intersecting with ++ * cpu_online_mask alone leaves it non-empty and this escape ++ * hatch never fires, while is_cpu_allowed() has already refused ++ * the dying CPU itself. It ends up runnable with no CPU willing ++ * to pick it. bind_zero() would free it by overriding the ++ * affinity, but that only runs from sched_cpu_wait_empty(), ++ * several hotplug states later - and anything the CPU going ++ * down waits for in between, such as blk_mq_hctx_notify_offline() ++ * waiting on a threaded completion interrupt, deadlocks against ++ * it. ++ */ ++ cpumask_and(&valid_mask, p->cpus_ptr, cpu_online_mask); ++ if (!kthread_is_per_cpu(p)) ++ cpumask_andnot(&valid_mask, &valid_mask, cpu_dying_mask); ++ if (unlikely(cpumask_empty(&valid_mask))) ++ return false; ++ } ++ return true; ++} ++ ++static inline bool needs_other_cpu(struct task_struct *p, int cpu) ++{ ++ if (unlikely(is_migration_disabled(p))) ++ return task_cpu(p) != cpu; ++ return !is_cpu_allowed(p, cpu); ++} ++ ++/* ++ * Pin p->cpus_ptr to this CPU so EDT / select_best_cpu cannot steal a ++ * preempted migrate_disable() task. Called under rq->lock on prev. ++ */ ++static void migrate_disable_switch(struct rq *rq, struct task_struct *p) ++{ ++ if (likely(!p->migration_disabled)) ++ return; ++ if (p->cpus_ptr != &p->cpus_mask) ++ return; ++ p->cpus_ptr = cpumask_of(cpu_of(rq)); ++} ++ ++#define cpu_online_map (*(cpumask_t *)cpu_online_mask) ++ ++static void try_preempt(struct task_struct *p, struct rq *this_rq) ++{ ++ int i, this_entries = rq_load(this_rq); ++ cpumask_t tmp; ++ ++ /* ++ * An idle CPU first: waking one costs less than bouncing a task ++ * that is already running, and it is what keeps work spread out. ++ * Only if none is available do we consider preemption, and the ++ * loop below starts at cpu_order[0] - this_rq - so @p still gets ++ * to preempt its own dest before any remote runqueue. ++ */ ++ if (suitable_idle_cpus(p) && resched_best_idle(p, task_cpu(p))) ++ return; ++ ++ /* IDLEPRIO tasks never preempt anything but idle */ ++ if (p->policy == SCHED_IDLEPRIO) ++ return; ++ ++ cpumask_and(&tmp, &cpu_online_map, p->cpus_ptr); ++ ++ for (i = 0; i < num_online_cpus(); i++) { ++ struct rq *rq = this_rq->cpu_order[i]; ++ ++ if (!cpumask_test_cpu(rq->cpu, &tmp)) ++ continue; ++ ++ if (!sched_interactive && rq != this_rq && rq_load(rq) <= this_entries) ++ continue; ++ if (smt_schedule(p, rq) && can_preempt(p, rq->rq_prio, rq->rq_deadline)) { ++ /* We set rq->preempting lockless, it's a hint only */ ++ rq->preempting = p; ++ resched_curr(rq); ++ return; ++ } ++ } ++} ++ ++static int __set_cpus_allowed_ptr(struct task_struct *p, ++ const struct cpumask *new_mask, ++ u32 flags); ++#else /* CONFIG_SMP */ ++static inline bool needs_other_cpu(struct task_struct *p, int cpu) ++{ ++ return false; ++} ++ ++static void try_preempt(struct task_struct *p, struct rq *this_rq) ++{ ++ if (p->policy == SCHED_IDLEPRIO) ++ return; ++ if (can_preempt(p, uprq->rq_prio, uprq->rq_deadline)) ++ resched_curr(uprq); ++} ++ ++static inline int __set_cpus_allowed_ptr(struct task_struct *p, ++ const struct cpumask *new_mask, ++ u32 __always_unused flags) ++{ ++ return set_cpus_allowed_ptr(p, new_mask); ++} ++#endif /* CONFIG_SMP */ ++ ++static void ++ttwu_stat(struct task_struct *p, int cpu, int wake_flags) ++{ ++ struct rq *rq; ++ ++ if (!schedstat_enabled()) ++ return; ++ ++ rq = this_rq(); ++ ++#ifdef CONFIG_SMP ++ if (cpu == rq->cpu) { ++ __schedstat_inc(rq->ttwu_local); ++ } else { ++ struct sched_domain *sd; ++ ++ rcu_read_lock(); ++ for_each_domain(rq->cpu, sd) { ++ if (cpumask_test_cpu(cpu, sched_domain_span(sd))) { ++ __schedstat_inc(sd->ttwu_wake_remote); ++ break; ++ } ++ } ++ rcu_read_unlock(); ++ } ++ ++#endif /* CONFIG_SMP */ ++ ++ __schedstat_inc(rq->ttwu_count); ++} ++ ++/* ++ * Mark the task runnable and perform wakeup-preemption. ++ */ ++static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags) ++{ ++ /* ++ * WF_SYNC means the waker will leave the CPU shortly. Prefer an ++ * idle CPU and do not preempt the waker itself — it will pick the ++ * wakee up on the next schedule(). A remote dest still needs ++ * kicking, or the wakee sits until dest's next tick (or forever ++ * on a nohz_full hog). ++ */ ++ if ((wake_flags & WF_SYNC) && rq == this_rq()) ++ resched_suitable_idle(p); ++ else ++ try_preempt(p, rq); ++ WRITE_ONCE(p->__state, TASK_RUNNING); ++ trace_sched_wakeup(p); ++} ++ ++static void ++ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags) ++{ ++ int en_flags = ENQUEUE_WAKEUP; ++ ++ lockdep_assert_held(rq->lock); ++ ++ if (p->sched_contributes_to_load) ++ rq->nr_uninterruptible--; ++ ++#ifdef CONFIG_SMP ++ if (wake_flags & WF_MIGRATED) ++ en_flags |= ENQUEUE_MIGRATED; ++ else ++#endif ++ if (p->in_iowait) { ++ delayacct_blkio_end(p); ++ atomic_dec(&task_rq(p)->nr_iowait); ++ } ++ ++ activate_task(rq, p, en_flags); ++ ttwu_do_wakeup(rq, p, wake_flags); ++} ++ ++/* ++ * Consider @p being inside a wait loop: ++ * ++ * for (;;) { ++ * set_current_state(TASK_UNINTERRUPTIBLE); ++ * ++ * if (CONDITION) ++ * break; ++ * ++ * schedule(); ++ * } ++ * __set_current_state(TASK_RUNNING); ++ * ++ * between set_current_state() and schedule(). In this case @p is still ++ * runnable, so all that needs doing is change p->__state back to TASK_RUNNING in ++ * an atomic manner. ++ * ++ * By taking task_rq(p)->lock we serialize against schedule(), if @p->on_rq ++ * then schedule() must still happen and p->__state can be changed to ++ * TASK_RUNNING. Otherwise we lost the race, schedule() has happened, and we ++ * need to do a full wakeup with enqueue. ++ * ++ * Returns: %true when the wakeup is done, ++ * %false otherwise. ++ */ ++static int ttwu_runnable(struct task_struct *p, int wake_flags) ++{ ++ struct rq *rq; ++ int ret = 0; ++ ++ rq = __task_rq_lock(p, NULL); ++ if (likely(task_on_rq_queued(p))) { ++ ttwu_do_wakeup(rq, p, wake_flags); ++ ret = 1; ++ } ++ __task_rq_unlock(rq, p, NULL); ++ ++ return ret; ++} ++ ++#ifdef CONFIG_SMP ++void sched_ttwu_pending(void *arg) ++{ ++ struct llist_node *llist = arg; ++ struct rq *rq = this_rq(); ++ struct task_struct *p, *t; ++ struct rq_flags rf; ++ ++ if (!llist) ++ return; ++ ++ /* ++ * rq::ttwu_pending racy indication of out-standing wakeups. ++ * Races such that false-negatives are possible, since they ++ * are shorter lived that false-positives would be. ++ */ ++ WRITE_ONCE(rq->ttwu_pending, 0); ++ ++ rq_lock_irqsave(rq, &rf); ++ ++ llist_for_each_entry_safe(p, t, llist, wake_entry.llist) { ++ if (WARN_ON_ONCE(p->on_cpu)) ++ smp_cond_load_acquire(&p->on_cpu, !VAL); ++ ++ if (WARN_ON_ONCE(task_cpu(p) != cpu_of(rq))) ++ set_task_cpu(p, cpu_of(rq)); ++ ++ ttwu_do_activate(rq, p, p->sched_remote_wakeup ? WF_MIGRATED : 0); ++ } ++ ++ rq_unlock_irqrestore(rq, &rf); ++} ++ ++/* ++ * Called from smp.c: report whether the caller still needs to send the IPI, ++ * having woken a polling idle task in passing. ++ */ ++bool call_function_single_prep_ipi(int cpu) ++{ ++ if (set_nr_if_polling(cpu_rq(cpu)->idle)) { ++ trace_sched_wake_idle_without_ipi(cpu); ++ return false; ++ } ++ ++ return true; ++} ++ ++/* ++ * Queue a task on the target CPUs wake_list and wake the CPU via IPI if ++ * necessary. The wakee CPU on receipt of the IPI will queue the task ++ * via sched_ttwu_wakeup() for activation so the wakee incurs the cost ++ * of the wakeup instead of the waker. ++ */ ++static void __ttwu_queue_wakelist(struct task_struct *p, int cpu, int wake_flags) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ ++ /* ++ * Carry WF_MIGRATED across to the CPU that will do the enqueue. Losing ++ * it there makes ttwu_do_activate() treat an already migrated wakeup ++ * as a local one, decrementing rq->nr_iowait a second time and telling ++ * PSI to clear an iowait that psi_ttwu_dequeue() has already dropped. ++ */ ++ p->sched_remote_wakeup = !!(wake_flags & WF_MIGRATED); ++ ++ WRITE_ONCE(rq->ttwu_pending, 1); ++ __smp_call_single_queue(cpu, &p->wake_entry.llist); ++} ++ ++void wake_up_if_idle(int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ ++ rcu_read_lock(); ++ ++ if (!is_idle_task(rcu_dereference(rq->curr))) ++ goto out; ++ ++ /* ++ * Match mainline: always go through resched_curr so TIF_NEED_RESCHED ++ * is set before any IPI. Sending a reschedule IPI alone does nothing ++ * useful — scheduler_ipi() only folds an already-set need_resched. ++ */ ++ rq_lock_irqsave(rq, &rf); ++ if (is_idle_task(rq->curr)) ++ resched_curr(rq); ++ rq_unlock_irqrestore(rq, &rf); ++ ++out: ++ rcu_read_unlock(); ++} ++ ++static inline bool ttwu_queue_cond(int cpu, int wake_flags) ++{ ++ /* ++ * Do not complicate things with the async wake_list while the CPU is ++ * in hotplug state. ++ */ ++ if (!cpu_active(cpu)) ++ return false; ++ ++ /* ++ * If the CPU does not share cache, then queue the task on the ++ * remote rqs wakelist to avoid accessing remote data. ++ */ ++ if (!cpus_share_cache(smp_processor_id(), cpu)) ++ return true; ++ ++ /* ++ * If the task is descheduling and the only running task on the ++ * CPU then use the wakelist to offload the task activation to ++ * the soon-to-be-idle CPU as the current CPU is likely busy. ++ * nr_running is checked to avoid unnecessary task stacking. ++ */ ++ if ((wake_flags & WF_ON_CPU) && cpu_rq(cpu)->nr_running <= 1) ++ return true; ++ ++ return false; ++} ++ ++static bool ttwu_queue_wakelist(struct task_struct *p, int cpu, int wake_flags) ++{ ++ /* CFS would require sched_feat(TTWU_QUEUE) here but that is ++ * fixed enabled */ ++ if (ttwu_queue_cond(cpu, wake_flags)) { ++ if (WARN_ON_ONCE(cpu == smp_processor_id())) ++ return false; ++ ++ sched_clock_cpu(cpu); /* Sync clocks across CPUs */ ++ __ttwu_queue_wakelist(p, cpu, wake_flags); ++ return true; ++ } ++ ++ return false; ++} ++ ++static int valid_task_cpu(struct task_struct *p) ++{ ++ cpumask_t valid_mask; ++ ++ if (unlikely(is_migration_disabled(p))) ++ return task_cpu(p); ++ ++ /* ++ * Kthreads may be bound to a CPU that is not yet online (per-CPU ++ * hotplug threads created during smp_init). Placement for running ++ * must still pick an online CPU — sched_other_cpu() allows them to ++ * be selected despite the affinity mismatch until their CPU is up. ++ * Userspace tasks are restricted to the active mask as usual. ++ */ ++ if (p->flags & PF_KTHREAD) { ++ cpumask_and(&valid_mask, p->cpus_ptr, cpu_online_mask); ++ /* ++ * Only KTHREAD_IS_PER_CPU gets to stay on a CPU that is on ++ * its way down, so do not hand a regular kthread back the ++ * CPU bind_zero() just moved it off. Mirrors is_cpu_allowed(). ++ */ ++ if (!kthread_is_per_cpu(p)) ++ cpumask_andnot(&valid_mask, &valid_mask, cpu_dying_mask); ++ } else ++ cpumask_and(&valid_mask, p->cpus_ptr, cpu_active_mask); ++ ++ if (unlikely(!cpumask_weight(&valid_mask))) { ++ if ((p->flags & PF_KTHREAD) && num_online_cpus()) ++ return cpumask_any(cpu_online_mask); ++ /* Defer console access while holding the task's PI lock. */ ++ printk_deferred(KERN_WARNING "SCHED: No cpumask for %s/%d weight %d\n", ++ p->comm, p->pid, cpumask_weight(p->cpus_ptr)); ++ return cpumask_any(p->cpus_ptr); ++ } ++ return cpumask_any(&valid_mask); ++} ++ ++/* ++ * For a task that's just being woken up we have a valuable balancing ++ * opportunity so choose the nearest cache most lightly loaded runqueue. ++ * Entered with rq locked and returns with the chosen runqueue locked. ++ */ ++static inline int select_best_cpu(struct task_struct *p) ++{ ++ unsigned int idlest = ~0U; ++ struct rq *rq = NULL; ++ int i; ++ ++ if (suitable_idle_cpus(p)) { ++ int cpu = task_cpu(p); ++ ++ if (unlikely(needs_other_cpu(p, cpu))) ++ cpu = valid_task_cpu(p); ++ rq = resched_best_idle(p, cpu); ++ if (likely(rq)) ++ return rq->cpu; ++ } ++ ++ for (i = 0; i < num_online_cpus(); i++) { ++ struct rq *other_rq = task_rq(p)->cpu_order[i]; ++ int entries; ++ ++ if (!other_rq->online) ++ continue; ++ if (needs_other_cpu(p, other_rq->cpu)) ++ continue; ++ entries = rq_load(other_rq); ++ if (entries >= idlest) ++ continue; ++ idlest = entries; ++ rq = other_rq; ++ } ++ if (unlikely(!rq)) { ++ /* ++ * The walk found nowhere to put @p. Its affinity may contain ++ * only offline CPUs (hotplug kthreads bound before their CPU ++ * is up), or the walk may simply not have reached an allowed ++ * one: cpu_order[] is built once at boot, so once any CPU is ++ * offline the num_online_cpus() bound above stops short of its ++ * tail and never examines what is there. ++ * ++ * Staying put is only an answer if this CPU is one @p is ++ * allowed to run on. Otherwise defer to valid_task_cpu(), ++ * which intersects the mask directly and so does not depend on ++ * that ordering at all. Never place a wakeup on a CPU the task ++ * is not allowed on, whether because it is offline or because ++ * it is not in the mask; a blocked task keeps a stale ++ * task_cpu() across an affinity change, so the latter is the ++ * common case here. ++ */ ++ if (unlikely(needs_other_cpu(p, task_cpu(p)))) ++ return valid_task_cpu(p); ++ return task_cpu(p); ++ } ++ return rq->cpu; ++} ++#else /* CONFIG_SMP */ ++ ++static inline bool ttwu_queue_wakelist(struct task_struct *p, int cpu, int wake_flags) ++{ ++ return false; ++} ++ ++static int valid_task_cpu(struct task_struct *p) ++{ ++ return 0; ++} ++ ++static inline int select_best_cpu(struct task_struct *p) ++{ ++ return 0; ++} ++ ++static struct rq *resched_best_idle(struct task_struct *p, int cpu) ++{ ++ return NULL; ++} ++#endif /* CONFIG_SMP */ ++ ++static void ttwu_queue(struct task_struct *p, int cpu, int wake_flags) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ ++ if (ttwu_queue_wakelist(p, cpu, wake_flags)) ++ return; ++ ++ rq_lock(rq); ++ update_rq_clock(rq); ++ ttwu_do_activate(rq, p, wake_flags); ++ rq_unlock(rq); ++} ++ ++/* ++ * Consider @state matched against @p, taking p->saved_state into account. ++ * ++ * The caller holds p::pi_lock if p != current or has preemption disabled ++ * when p == current. ++ * ++ * The rules of saved_state: ++ * ++ * The related locking code always holds p::pi_lock when updating ++ * p::saved_state, which means the code is fully serialized in both cases. ++ * ++ * For PREEMPT_RT, the lock wait and lock wakeups happen via TASK_RTLOCK_WAIT. ++ * No other bits set. This allows to distinguish all wakeup scenarios. ++ * ++ * For FREEZER, the wakeup happens via TASK_FROZEN. No other bits set. This ++ * allows us to prevent early wakeup of tasks before they can be run on ++ * asymmetric ISA architectures (eg ARMv9). ++ */ ++static __always_inline ++bool ttwu_state_match(struct task_struct *p, unsigned int state, int *success) ++{ ++ int match; ++ ++ if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)) { ++ WARN_ON_ONCE((state & TASK_RTLOCK_WAIT) && ++ state != TASK_RTLOCK_WAIT); ++ } ++ ++ *success = !!(match = __task_state_match(p, state)); ++ ++ /* ++ * Saved state preserves the task state across blocking on ++ * an RT lock or TASK_FREEZABLE tasks. If the state matches, ++ * set p::saved_state to TASK_RUNNING, but do not wake the task ++ * because it waits for a lock wakeup or __thaw_task(). Also ++ * indicate success because from the regular waker's point of ++ * view this has succeeded. ++ * ++ * After acquiring the lock the task will restore p::__state ++ * from p::saved_state which ensures that the regular ++ * wakeup is not lost. The restore will also set ++ * p::saved_state to TASK_RUNNING so any further tests will ++ * not result in false positives vs. @success ++ */ ++ if (match < 0) ++ p->saved_state = TASK_RUNNING; ++ ++ return match > 0; ++} ++ ++/*** ++ * try_to_wake_up - wake up a thread ++ * @p: the thread to be awakened ++ * @state: the mask of task states that can be woken ++ * @wake_flags: wake modifier flags (WF_*) ++ * ++ * Put it on the run-queue if it's not already there. The "current" ++ * thread is always on the run-queue (except when the actual ++ * re-schedule is in progress), and as such you're allowed to do ++ * the simpler "current->__state = TASK_RUNNING" to mark yourself ++ * runnable without the overhead of this. ++ * ++ * Return: %true if @p was woken up, %false if it was already running. ++ * or @state didn't match @p's state. ++ */ ++int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags) ++{ ++ unsigned long flags; ++ int cpu, success = 0; ++ ++ preempt_disable(); ++ if (p == current) { ++ /* ++ * We're waking current, this means 'p->on_rq' and 'task_cpu(p) ++ * == smp_processor_id()'. Together this means we can special ++ * case the whole 'p->on_rq && ttwu_runnable()' case below ++ * without taking any locks. ++ * ++ * In particular: ++ * - we rely on Program-Order guarantees for all the ordering, ++ * - we're serialized against set_special_state() by virtue of ++ * it disabling IRQs (this allows not taking ->pi_lock). ++ */ ++ if (!ttwu_state_match(p, state, &success)) ++ goto out; ++ ++ trace_sched_waking(p); ++ p->__state = TASK_RUNNING; ++ trace_sched_wakeup(p); ++ goto out; ++ } ++ ++ /* ++ * If we are going to wake up a thread waiting for CONDITION we ++ * need to ensure that CONDITION=1 done by the caller can not be ++ * reordered with p->__state check below. This pairs with smp_store_mb() ++ * in set_current_state() that the waiting thread does. ++ */ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ smp_mb__after_spinlock(); ++ if (!ttwu_state_match(p, state, &success)) ++ goto unlock; ++ ++ trace_sched_waking(p); ++ ++ /* ++ * Ensure we load p->on_rq _after_ p->__state, otherwise it would ++ * be possible to, falsely, observe p->on_rq == 0 and get stuck ++ * in smp_cond_load_acquire() below. ++ * ++ * sched_ttwu_pending() try_to_wake_up() ++ * STORE p->on_rq = 1 LOAD p->__state ++ * UNLOCK rq->lock ++ * ++ * __schedule() (switch to task 'p') ++ * LOCK rq->lock smp_rmb(); ++ * smp_mb__after_spinlock(); ++ * UNLOCK rq->lock ++ * ++ * [task p] ++ * STORE p->__state = UNINTERRUPTIBLE LOAD p->on_rq ++ * ++ * Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in ++ * __schedule(). See the comment for smp_mb__after_spinlock(). ++ */ ++ smp_rmb(); ++ if (READ_ONCE(p->on_rq) && ttwu_runnable(p, wake_flags)) ++ goto unlock; ++ ++#ifdef CONFIG_SMP ++ /* ++ * Ensure we load p->on_cpu _after_ p->on_rq, otherwise it would be ++ * possible to, falsely, observe p->on_cpu == 0. ++ * ++ * One must be running (->on_cpu == 1) in order to remove oneself ++ * from the runqueue. ++ * ++ * __schedule() (switch to task 'p') try_to_wake_up() ++ * STORE p->on_cpu = 1 LOAD p->on_rq ++ * UNLOCK rq->lock ++ * ++ * __schedule() (put 'p' to sleep) ++ * LOCK rq->lock smp_rmb(); ++ * smp_mb__after_spinlock(); ++ * STORE p->on_rq = 0 LOAD p->on_cpu ++ * ++ * Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in ++ * __schedule(). See the comment for smp_mb__after_spinlock(). ++ * ++ * Form a control-dep-acquire with p->on_rq == 0 above, to ensure ++ * schedule()'s deactivate_task() has 'happened' and p will no longer ++ * care about it's own p->__state. See the comment in __schedule(). ++ */ ++ smp_acquire__after_ctrl_dep(); ++ ++ /* ++ * We're doing the wakeup (@success == 1), they did a dequeue (p->on_rq ++ * == 0), which means we need to do an enqueue, change p->__state to ++ * TASK_WAKING such that we can unlock p->pi_lock before doing the ++ * enqueue, such as ttwu_queue_wakelist(). ++ */ ++ p->__state = TASK_WAKING; ++ ++ /* ++ * If the owning (remote) CPU is still in the middle of schedule() with ++ * this task as prev, considering queueing p on the remote CPUs wake_list ++ * which potentially sends an IPI instead of spinning on p->on_cpu to ++ * let the waker make forward progress. This is safe because IRQs are ++ * disabled and the IPI will deliver after on_cpu is cleared. ++ * ++ * Ensure we load task_cpu(p) after p->on_cpu: ++ * ++ * set_task_cpu(p, cpu); ++ * STORE task_cpu(p) = @cpu ++ * __schedule() (switch to task 'p') ++ * LOCK rq->lock ++ * smp_mb__after_spin_lock() smp_cond_load_acquire(&p->on_cpu) ++ * STORE p->on_cpu = 1 LOAD task_cpu(p) ++ * ++ * to ensure we observe the correct CPU on which the task is currently ++ * scheduling. ++ */ ++ if (smp_load_acquire(&p->on_cpu) && ++ ttwu_queue_wakelist(p, task_cpu(p), wake_flags | WF_ON_CPU)) ++ goto unlock; ++ ++ /* ++ * If the owning (remote) CPU is still in the middle of schedule() with ++ * this task as prev, wait until it's done referencing the task. ++ * ++ * Pairs with the smp_store_release() in finish_task(). ++ * ++ * This ensures that tasks getting woken will be fully ordered against ++ * their previous state and preserve Program Order. ++ */ ++ smp_cond_load_acquire(&p->on_cpu, !VAL); ++ ++ cpu = select_best_cpu(p); ++ if (task_cpu(p) != cpu) { ++ if (p->in_iowait) { ++ delayacct_blkio_end(p); ++ atomic_dec(&task_rq(p)->nr_iowait); ++ } ++ ++ wake_flags |= WF_MIGRATED; ++ psi_ttwu_dequeue(p); ++ set_task_cpu(p, cpu); ++ } ++ ++#else ++ cpu = task_cpu(p); ++#endif /* CONFIG_SMP */ ++ ++ ttwu_queue(p, cpu, wake_flags); ++unlock: ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++out: ++ if (success) ++ ttwu_stat(p, task_cpu(p), wake_flags); ++ preempt_enable(); ++ ++ return success; ++} ++ ++static bool __task_needs_rq_lock(struct task_struct *p) ++{ ++ unsigned int state = READ_ONCE(p->__state); ++ ++ /* ++ * Since pi->lock blocks try_to_wake_up(), we don't need rq->lock when ++ * the task is blocked. Make sure to check @state since ttwu() can drop ++ * locks at the end, see ttwu_queue_wakelist(). ++ */ ++ if (state == TASK_RUNNING || state == TASK_WAKING) ++ return true; ++ ++ /* ++ * Ensure we load p->on_rq after p->__state, otherwise it would be ++ * possible to, falsely, observe p->on_rq == 0. ++ * ++ * See try_to_wake_up() for a longer comment. ++ */ ++ smp_rmb(); ++ if (p->on_rq) ++ return true; ++ ++ /* ++ * Ensure the task has finished __schedule() and will not be referenced ++ * anymore. Again, see try_to_wake_up() for a longer comment. ++ */ ++ smp_rmb(); ++ smp_cond_load_acquire(&p->on_cpu, !VAL); ++ ++ return false; ++} ++ ++/** ++ * task_call_func - Invoke a function on task in fixed state ++ * @p: Process for which the function is to be invoked, can be @current. ++ * @func: Function to invoke. ++ * @arg: Argument to function. ++ * ++ * Fix the task in it's current state by avoiding wakeups and or rq operations ++ * and call @func(@arg) on it. This function can use task_is_runnable() and ++ * task_curr() to work out what the state is, if required. Given that @func ++ * can be invoked with a runqueue lock held, it had better be quite ++ * lightweight. ++ * ++ * Returns: ++ * Whatever @func returns ++ */ ++int task_call_func(struct task_struct *p, task_call_f func, void *arg) ++{ ++ struct rq_flags rf; ++ int ret; ++ ++ raw_spin_lock_irqsave(&p->pi_lock, rf.flags); ++ ++ if (__task_needs_rq_lock(p)) { ++ struct rq *rq = __task_rq_lock(p, &rf); ++ ++ /* ++ * At this point the task is pinned; either: ++ * - blocked and we're holding off wakeups (pi->lock) ++ * - woken, and we're holding off enqueue (rq->lock) ++ * - queued, and we're holding off schedule (rq->lock) ++ * - running, and we're holding off de-schedule (rq->lock) ++ * ++ * The called function (@func) can use: task_curr(), p->on_rq and ++ * p->__state to differentiate between these states. ++ */ ++ ret = func(p, arg); ++ ++ __task_rq_unlock(rq, p, &rf); ++ } else { ++ ret = func(p, arg); ++ } ++ ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags); ++ return ret; ++} ++ ++/** ++ * wake_up_process - Wake up a specific process ++ * @p: The process to be woken up. ++ * ++ * Attempt to wake up the nominated process and move it to the set of runnable ++ * processes. ++ * ++ * Return: 1 if the process was woken up, 0 if it was already running. ++ * ++ * This function executes a full memory barrier before accessing the task state. ++ */ ++int wake_up_process(struct task_struct *p) ++{ ++ return try_to_wake_up(p, TASK_NORMAL, 0); ++} ++EXPORT_SYMBOL(wake_up_process); ++ ++int wake_up_state(struct task_struct *p, unsigned int state) ++{ ++ return try_to_wake_up(p, state, 0); ++} ++ ++static void time_slice_expired(struct task_struct *p, struct rq *rq); ++ ++/* ++ * Perform scheduler related setup for a newly forked process p. ++ * p is forked by current. ++ */ ++int sched_fork(u64 __maybe_unused clone_flags, struct task_struct *p) ++{ ++ unsigned long flags; ++ ++#ifdef CONFIG_PREEMPT_NOTIFIERS ++ INIT_HLIST_HEAD(&p->preempt_notifiers); ++#endif ++ ++#ifdef CONFIG_COMPACTION ++ p->capture_control = NULL; ++#endif ++ ++#ifdef CONFIG_SMP ++ p->wake_entry.u_flags = CSD_TYPE_TTWU; ++#endif ++ /* A new task starts with no I/O history of its own. */ ++ muqss_iotime_task_init(p); ++ ++ /* ++ * We mark the process as NEW here. This guarantees that ++ * nobody will actually run it, and a signal or other external ++ * event cannot wake it up and insert it on the runqueue either. ++ */ ++ p->__state = TASK_NEW; ++ ++ /* ++ * The process state is set to the same value of the process executing ++ * do_fork() code. That is running. This guarantees that nobody will ++ * actually run it, and a signal or other external event cannot wake ++ * it up and insert it on the runqueue either. ++ */ ++ ++ /* Should be reset in fork.c but done here for ease of MuQSS patching */ ++ p->on_cpu = ++ p->on_rq = ++ p->utime = ++ p->stime = ++ p->sched_time = ++ p->stime_ns = ++ p->utime_ns = 0; ++ skiplist_node_init(&p->node); ++ ++ /* ++ * Revert to default priority/policy on fork if requested. ++ */ ++ if (unlikely(p->sched_reset_on_fork)) { ++ if (p->policy == SCHED_FIFO || p->policy == SCHED_RR || p-> policy == SCHED_ISO) { ++ /* ++ * __setscheduler() zeroes timer_slack_ns for rt tasks, ++ * so restore it when demoting back to SCHED_NORMAL, ++ * otherwise the child inherits zero slack forever. ++ */ ++ if (has_rt_policy(p)) ++ p->timer_slack_ns = p->default_timer_slack_ns; ++ p->policy = SCHED_NORMAL; ++ p->normal_prio = normal_prio(p); ++ } ++ ++ if (PRIO_TO_NICE(p->static_prio) < 0) { ++ p->static_prio = NICE_TO_PRIO(0); ++ p->normal_prio = p->static_prio; ++ } ++ ++ /* ++ * We don't need the reset flag anymore after the fork. It has ++ * fulfilled its duty: ++ */ ++ p->sched_reset_on_fork = 0; ++ } ++ ++ /* ++ * Silence PROVE_RCU. ++ */ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ rseq_sched_set_ids_changed(p); ++ set_task_cpu(p, smp_processor_id()); ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++ ++#ifdef CONFIG_SCHED_INFO ++ if (unlikely(sched_info_on())) ++ memset(&p->sched_info, 0, sizeof(p->sched_info)); ++#endif ++ init_task_preempt_count(p); ++ ++ return 0; ++} ++ ++void sched_post_fork(struct task_struct *p) ++{ ++} ++ ++#ifdef CONFIG_SCHEDSTATS ++ ++DEFINE_STATIC_KEY_FALSE(sched_schedstats); ++static bool __initdata __sched_schedstats = false; ++ ++static void set_schedstats(bool enabled) ++{ ++ if (enabled) ++ static_branch_enable(&sched_schedstats); ++ else ++ static_branch_disable(&sched_schedstats); ++} ++ ++void force_schedstat_enabled(void) ++{ ++ if (!schedstat_enabled()) { ++ pr_info("kernel profiling enabled schedstats, disable via kernel.sched_schedstats.\n"); ++ static_branch_enable(&sched_schedstats); ++ } ++} ++ ++static int __init setup_schedstats(char *str) ++{ ++ int ret = 0; ++ if (!str) ++ goto out; ++ ++ /* ++ * This code is called before jump labels have been set up, so we can't ++ * change the static branch directly just yet. Instead set a temporary ++ * variable so init_schedstats() can do it later. ++ */ ++ if (!strcmp(str, "enable")) { ++ __sched_schedstats = true; ++ ret = 1; ++ } else if (!strcmp(str, "disable")) { ++ __sched_schedstats = false; ++ ret = 1; ++ } ++out: ++ if (!ret) ++ pr_warn("Unable to parse schedstats=\n"); ++ ++ return ret; ++} ++__setup("schedstats=", setup_schedstats); ++ ++static void __init init_schedstats(void) ++{ ++ set_schedstats(__sched_schedstats); ++} ++ ++#ifdef CONFIG_SYSCTL ++static int sysctl_schedstats(const struct ctl_table *table, int write, void *buffer, ++ size_t *lenp, loff_t *ppos) ++{ ++ struct ctl_table t; ++ int err; ++ int state = static_branch_likely(&sched_schedstats); ++ ++ if (write && !capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ t = *table; ++ t.data = &state; ++ err = proc_dointvec_minmax(&t, write, buffer, lenp, ppos); ++ if (err < 0) ++ return err; ++ if (write) ++ set_schedstats(state); ++ return err; ++} ++#endif /* CONFIG_SYSCTL */ ++#else /* !CONFIG_SCHEDSTATS */ ++static inline void init_schedstats(void) {} ++#endif /* CONFIG_SCHEDSTATS */ ++ ++#ifdef CONFIG_SYSCTL ++/* ++ * MuQSS tunables used to live in kernel/sysctl.c. Mainline moved scheduler ++ * sysctls next to their implementation via register_sysctl_init(), so register ++ * ours the same way. CFS/rt/fair knobs are not present under MuQSS. ++ */ ++static const struct ctl_table muqss_sysctls[] = { ++ { ++ .procname = "rr_interval", ++ .data = &rr_interval, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ONE, ++ .extra2 = SYSCTL_ONE_THOUSAND, ++ }, ++ { ++ .procname = "interactive", ++ .data = &sched_interactive, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "iso_cpu", ++ .data = &sched_iso_cpu, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE_HUNDRED, ++ }, ++ { ++ .procname = "yield_type", ++ .data = &sched_yield_type, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_TWO, ++ }, ++#ifdef CONFIG_SCHEDSTATS ++ { ++ .procname = "sched_schedstats", ++ .data = NULL, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = sysctl_schedstats, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++#endif ++}; ++ ++static int __init muqss_sysctl_init(void) ++{ ++ register_sysctl_init("kernel", muqss_sysctls); ++ return 0; ++} ++late_initcall(muqss_sysctl_init); ++#endif /* CONFIG_SYSCTL */ ++ ++static void update_cpu_clock_switch(struct rq *rq, struct task_struct *p); ++ ++static void account_task_cpu(struct rq *rq, struct task_struct *p) ++{ ++ update_clocks(rq); ++ /* This isn't really a context switch but accounting is the same */ ++ update_cpu_clock_switch(rq, p); ++ p->last_ran = rq->niffies; ++} ++ ++bool sched_smp_initialized __read_mostly; ++ ++/* ++ * High-resolution timeslice expiry (MuQSS counterpart of mainline hrtick). ++ * ++ * Reprogramming a oneshot clockevent from set_rq_task() on every context ++ * switch under the rq lock races with the tick/timer softirq path on SMP ++ * and starves TIMER_SOFTIRQ (RCU "timer wakeup didn't happen"). Mirror ++ * mainline: HARD + LAZY_REARM setup, needs_rearm 5us threshold, and defer ++ * the actual start/cancel until hrexpiry_schedule_exit() after the pick. ++ */ ++#ifdef CONFIG_HIGH_RES_TIMERS ++ ++enum { ++ HREXPIRE_SCHED_NONE = 0, ++ HREXPIRE_SCHED_DEFER = BIT(1), ++ HREXPIRE_SCHED_START = BIT(2), ++ HREXPIRE_SCHED_REARM_HRTIMER = BIT(3), ++}; ++ ++static inline int hrexpiry_enabled(struct rq *rq) ++{ ++ /* ++ * 5.12 used hrtimer_is_hres_active(); 7.1 exposes the same idea as ++ * hrtimer_resolution != LOW_RES_NSEC once highres has switched on. ++ */ ++ return hrtimer_resolution != LOW_RES_NSEC; ++} ++ ++static inline bool hrexpiry_needs_rearm(struct hrtimer *timer, ktime_t expires) ++{ ++ /* ++ * Queued is false when not started or the callback is running. If ++ * already queued, only reprogram when the expiry moves substantially. ++ */ ++ return !hrtimer_is_queued(timer) || ++ abs(expires - hrtimer_get_expires(timer)) > 5000; ++} ++ ++static void hrexpiry_cond_restart(struct rq *rq) ++{ ++ struct hrtimer *timer = &rq->hrexpiry_timer; ++ ktime_t time = rq->hrexpiry_time; ++ ++ if (hrexpiry_needs_rearm(timer, time)) ++ hrtimer_start(timer, time, HRTIMER_MODE_ABS_PINNED_HARD); ++} ++ ++/* ++ * Remote start IPI — wake_up_new_task may shorten a parent on another CPU. ++ * Runs hardirq/IPI context; take the rq lock like mainline __hrtick_start. ++ */ ++static void __hrexpiry_start(void *arg) ++{ ++ struct rq *rq = arg; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(rq->lock, flags); ++ hrexpiry_cond_restart(rq); ++ raw_spin_unlock_irqrestore(rq->lock, flags); ++} ++ ++static inline void hrexpiry_clear(struct rq *rq) ++{ ++ if (!hrexpiry_enabled(rq)) ++ return; ++ ++ /* ++ * Inside __schedule() only drop a pending deferred start; the actual ++ * cancel happens once in hrexpiry_schedule_exit(). ++ */ ++ if (rq->hrexpiry_sched) { ++ rq->hrexpiry_sched &= ~HREXPIRE_SCHED_START; ++ return; ++ } ++ ++ hrtimer_try_to_cancel(&rq->hrexpiry_timer); ++} ++ ++/* ++ * High-resolution time_slice expiry. ++ * Runs from hardirq context with interrupts disabled. ++ */ ++static enum hrtimer_restart hrexpiry(struct hrtimer *timer) ++{ ++ struct rq *rq = container_of(timer, struct rq, hrexpiry_timer); ++ struct task_struct *p; ++ ++ /* This can happen during CPU hotplug / resume */ ++ if (unlikely(cpu_of(rq) != smp_processor_id())) ++ goto out; ++ ++ /* ++ * Local CPU only; no rq lock. Force a reschedule when the slice ++ * expires — __schedule() will pick the next deadline task. ++ */ ++ p = rq->curr; ++ p->time_slice = 0; ++ __set_tsk_resched(p); ++out: ++ return HRTIMER_NORESTART; ++} ++ ++/* ++ * Called with irqs disabled under the rq lock (set_rq_task / fork path). ++ * May target a remote rq — then arm via CSD like mainline hrtick_start. ++ */ ++static void hrexpiry_start(struct rq *rq, u64 delay) ++{ ++ s64 delta; ++ ++ if (!hrexpiry_enabled(rq)) ++ return; ++ ++ /* Slices < 10us are not useful and can DoS the timer subsystem. */ ++ delta = max_t(s64, delay, 10000LL); ++ ++ /* ++ * Mid-schedule: note the delay and let hrexpiry_schedule_exit() ++ * program the clockevent once. ++ */ ++ if (rq->hrexpiry_sched) { ++ rq->hrexpiry_sched |= HREXPIRE_SCHED_START; ++ rq->hrexpiry_delay = delta; ++ return; ++ } ++ ++ rq->hrexpiry_time = ktime_add_ns(ktime_get(), delta); ++ if (!hrexpiry_needs_rearm(&rq->hrexpiry_timer, rq->hrexpiry_time)) ++ return; ++ ++ if (rq == this_rq()) ++ hrtimer_start(&rq->hrexpiry_timer, rq->hrexpiry_time, ++ HRTIMER_MODE_ABS_PINNED_HARD); ++ else ++ smp_call_function_single_async(cpu_of(rq), &rq->hrexpiry_csd); ++} ++ ++static inline void hrexpiry_schedule_enter(struct rq *rq) ++{ ++ rq->hrexpiry_sched = HREXPIRE_SCHED_DEFER; ++ if (hrtimer_test_and_clear_rearm_deferred()) ++ rq->hrexpiry_sched |= HREXPIRE_SCHED_REARM_HRTIMER; ++} ++ ++static inline void hrexpiry_schedule_exit(struct rq *rq) ++{ ++ if (rq->hrexpiry_sched & HREXPIRE_SCHED_START) { ++ rq->hrexpiry_time = ktime_add_ns(ktime_get(), rq->hrexpiry_delay); ++ hrexpiry_cond_restart(rq); ++ } else if (rq->curr == rq->idle || rq->curr->policy == SCHED_FIFO) { ++ /* ++ * No slice timer needed. Local CPU, IRQs off: the HARD ++ * callback cannot be running, so cancel is safe. ++ */ ++ if (hrtimer_is_queued(&rq->hrexpiry_timer)) ++ hrtimer_cancel(&rq->hrexpiry_timer); ++ } ++ ++ if (rq->hrexpiry_sched & HREXPIRE_SCHED_REARM_HRTIMER) ++ __hrtimer_rearm_deferred(); ++ ++ rq->hrexpiry_sched = HREXPIRE_SCHED_NONE; ++} ++ ++static void init_rq_hrexpiry(struct rq *rq) ++{ ++ INIT_CSD(&rq->hrexpiry_csd, __hrexpiry_start, rq); ++ rq->hrexpiry_sched = HREXPIRE_SCHED_NONE; ++ hrtimer_setup(&rq->hrexpiry_timer, hrexpiry, CLOCK_MONOTONIC, ++ HRTIMER_MODE_REL_HARD | HRTIMER_MODE_LAZY_REARM); ++} ++ ++#else /* !CONFIG_HIGH_RES_TIMERS */ ++ ++static inline int hrexpiry_enabled(struct rq *rq) ++{ ++ return 0; ++} ++static inline void hrexpiry_clear(struct rq *rq) { } ++static inline void hrexpiry_start(struct rq *rq, u64 delay) { } ++static inline void hrexpiry_schedule_enter(struct rq *rq) { } ++static inline void hrexpiry_schedule_exit(struct rq *rq) { } ++static inline void init_rq_hrexpiry(struct rq *rq) { } ++ ++#endif /* CONFIG_HIGH_RES_TIMERS */ ++ ++static inline int rq_dither(struct rq *rq) ++{ ++ if (!hrexpiry_enabled(rq)) ++ return HALF_JIFFY_US; ++ return 0; ++} ++ ++/* ++ * wake_up_new_task - wake up a newly created task for the first time. ++ * ++ * This function will do some initial scheduler statistics housekeeping ++ * that must be done for every newly created context, then puts the task ++ * on the runqueue and wakes it. ++ */ ++void wake_up_new_task(struct task_struct *p) ++{ ++ struct task_struct *parent, *rq_curr; ++ struct rq *rq, *new_rq; ++ unsigned long flags; ++ ++ parent = p->parent; ++ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ p->__state = TASK_RUNNING; ++ /* Task_rq can't change yet on a new task */ ++ new_rq = rq = task_rq(p); ++ if (unlikely(needs_other_cpu(p, task_cpu(p)))) { ++ set_task_cpu(p, valid_task_cpu(p)); ++ new_rq = task_rq(p); ++ } ++ ++ double_rq_lock(rq, new_rq); ++ rq_curr = rq->curr; ++ ++ /* ++ * Make sure we do not leak PI boosting priority to the child. ++ */ ++ p->prio = rq_curr->normal_prio; ++ ++ trace_sched_wakeup_new(p); ++ ++ /* ++ * Share the timeslice between parent and child, thus the ++ * total amount of pending timeslices in the system doesn't change, ++ * resulting in more scheduling fairness. If it's negative, it won't ++ * matter since that's the same as being 0. rq->rq_deadline is only ++ * modified within schedule() so it is always equal to ++ * current->deadline. ++ */ ++ account_task_cpu(rq, rq_curr); ++ p->last_ran = rq_curr->last_ran; ++ if (likely(rq_curr->policy != SCHED_FIFO)) { ++ rq_curr->time_slice /= 2; ++ if (rq_curr->time_slice < RESCHED_US) { ++ /* ++ * Forking task has run out of timeslice. Reschedule it and ++ * start its child with a new time slice and deadline. The ++ * child will end up running first because its deadline will ++ * be slightly earlier. ++ */ ++ __set_tsk_resched(rq_curr); ++ time_slice_expired(p, new_rq); ++ if (suitable_idle_cpus(p)) ++ resched_best_idle(p, task_cpu(p)); ++ else if (unlikely(rq != new_rq)) ++ try_preempt(p, new_rq); ++ } else { ++ p->time_slice = rq_curr->time_slice; ++ if (rq_curr == parent && rq == new_rq && !suitable_idle_cpus(p)) { ++ /* ++ * The VM isn't cloned, so we're in a good position to ++ * do child-runs-first in anticipation of an exec. This ++ * usually avoids a lot of COW overhead. ++ */ ++ __set_tsk_resched(rq_curr); ++ } else { ++ /* ++ * Adjust the hrexpiry since rq_curr will keep ++ * running and its timeslice has been shortened. ++ */ ++ hrexpiry_start(rq, US_TO_NS(rq_curr->time_slice)); ++ try_preempt(p, new_rq); ++ } ++ } ++ } else { ++ time_slice_expired(p, new_rq); ++ try_preempt(p, new_rq); ++ } ++ activate_task(new_rq, p, 0); ++ double_rq_unlock(rq, new_rq); ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++} ++ ++#ifdef CONFIG_PREEMPT_NOTIFIERS ++ ++static DEFINE_STATIC_KEY_FALSE(preempt_notifier_key); ++ ++void preempt_notifier_inc(void) ++{ ++ static_branch_inc(&preempt_notifier_key); ++} ++EXPORT_SYMBOL_GPL(preempt_notifier_inc); ++ ++void preempt_notifier_dec(void) ++{ ++ static_branch_dec(&preempt_notifier_key); ++} ++EXPORT_SYMBOL_GPL(preempt_notifier_dec); ++ ++/** ++ * preempt_notifier_register - tell me when current is being preempted & rescheduled ++ * @notifier: notifier struct to register ++ */ ++void preempt_notifier_register(struct preempt_notifier *notifier) ++{ ++ if (!static_branch_unlikely(&preempt_notifier_key)) ++ WARN(1, "registering preempt_notifier while notifiers disabled\n"); ++ ++ hlist_add_head(¬ifier->link, ¤t->preempt_notifiers); ++} ++EXPORT_SYMBOL_GPL(preempt_notifier_register); ++ ++/** ++ * preempt_notifier_unregister - no longer interested in preemption notifications ++ * @notifier: notifier struct to unregister ++ * ++ * This is *not* safe to call from within a preemption notifier. ++ */ ++void preempt_notifier_unregister(struct preempt_notifier *notifier) ++{ ++ hlist_del(¬ifier->link); ++} ++EXPORT_SYMBOL_GPL(preempt_notifier_unregister); ++ ++static void __fire_sched_in_preempt_notifiers(struct task_struct *curr) ++{ ++ struct preempt_notifier *notifier; ++ ++ hlist_for_each_entry(notifier, &curr->preempt_notifiers, link) ++ notifier->ops->sched_in(notifier, raw_smp_processor_id()); ++} ++ ++static __always_inline void fire_sched_in_preempt_notifiers(struct task_struct *curr) ++{ ++ if (static_branch_unlikely(&preempt_notifier_key)) ++ __fire_sched_in_preempt_notifiers(curr); ++} ++ ++static void ++__fire_sched_out_preempt_notifiers(struct task_struct *curr, ++ struct task_struct *next) ++{ ++ struct preempt_notifier *notifier; ++ ++ hlist_for_each_entry(notifier, &curr->preempt_notifiers, link) ++ notifier->ops->sched_out(notifier, next); ++} ++ ++static __always_inline void ++fire_sched_out_preempt_notifiers(struct task_struct *curr, ++ struct task_struct *next) ++{ ++ if (static_branch_unlikely(&preempt_notifier_key)) ++ __fire_sched_out_preempt_notifiers(curr, next); ++} ++ ++#else /* !CONFIG_PREEMPT_NOTIFIERS */ ++ ++static inline void fire_sched_in_preempt_notifiers(struct task_struct *curr) ++{ ++} ++ ++static inline void ++fire_sched_out_preempt_notifiers(struct task_struct *curr, ++ struct task_struct *next) ++{ ++} ++ ++#endif /* CONFIG_PREEMPT_NOTIFIERS */ ++ ++static inline void prepare_task(struct task_struct *next) ++{ ++ /* ++ * Claim the task as running, we do this before switching to it ++ * such that any running task will have this set. ++ * ++ * See the ttwu() WF_ON_CPU case and its ordering comment. ++ */ ++ WRITE_ONCE(next->on_cpu, 1); ++} ++ ++static inline void finish_task(struct task_struct *prev) ++{ ++ /* ++ * This must be the very last reference to @prev from this CPU. After ++ * p->on_cpu is cleared, the task can be moved to a different CPU. We ++ * must ensure this doesn't happen until the switch is completely ++ * finished. ++ * ++ * In particular, the load of prev->__state in finish_task_switch() must ++ * happen before this. ++ * ++ * Pairs with the smp_cond_load_acquire() in try_to_wake_up() and ++ * __task_needs_rq_lock(). ++ */ ++ smp_store_release(&prev->on_cpu, 0); ++} ++ ++static inline void ++prepare_lock_switch(struct rq *rq, struct task_struct *next) ++{ ++ /* ++ * Since the runqueue lock will be released by the next ++ * task (which is an invalid locking op but in the case ++ * of the scheduler it's an obvious special-case), so we ++ * do an early lockdep release here: ++ */ ++ spin_release(&rq->lock->dep_map, _THIS_IP_); ++#ifdef CONFIG_DEBUG_SPINLOCK ++ /* this is a valid case when another task releases the spinlock */ ++ rq->lock->owner = next; ++#endif ++} ++ ++static inline void finish_lock_switch(struct rq *rq, struct task_struct *prev) ++{ ++ /* ++ * If we are tracking spinlock dependencies then we have to ++ * fix up the runqueue lock - which gets 'carried over' from ++ * prev into current: ++ */ ++ spin_acquire(&rq->lock->dep_map, 0, 0, _THIS_IP_); ++ ++#ifdef CONFIG_SMP ++ /* ++ * If prev was marked as migrating to another CPU in return_task, drop ++ * the local runqueue lock but leave interrupts disabled and grab the ++ * remote lock we're migrating it to before enabling them. ++ */ ++ if (unlikely(task_on_rq_migrating(prev))) { ++ unsigned int migrate; ++ ++ /* ++ * Program/cancel hrexpiry on this CPU before dropping its ++ * rq lock; after the unlock `rq` may become the remote one. ++ */ ++ hrexpiry_schedule_exit(rq); ++ sched_info_dequeue(rq, prev); ++ /* ++ * We move the ownership of prev to the new cpu now. Note that ++ * this does not lock ttwu out: pointing task_cpu() at wake_cpu ++ * below sends a concurrent ttwu_runnable() to the *new* ++ * runqueue's lock rather than the one dropped here, and ++ * __task_rq_lock() does not spin on task_on_rq_migrating() the ++ * way mainline's does. See the re-check before enqueueing. ++ * ++ * This bypasses set_task_cpu(), so any PSI state prev is still ++ * counted for has to be moved by hand, exactly as that does. ++ * psi_sched_switch() saw prev off the CPU as a sleep, since it ++ * is no longer queued, so a task in iowait is carrying ++ * TSK_IOWAIT here and enqueue_task() below would clear it on ++ * the new CPU that never counted it. ++ */ ++ migrate = psi_migrate_begin(prev); ++ task_thread_info(prev)->cpu = prev->wake_cpu; ++ psi_migrate_end(prev, migrate); ++ raw_spin_unlock(rq->lock); ++ ++ raw_spin_lock(&prev->pi_lock); ++ rq = __task_rq_lock(prev, NULL); ++ /* ++ * Complete the handover only while it is still ours to ++ * complete. ttwu() reaches prev here despite the comment ++ * above: ttwu_runnable() only accepts TASK_ON_RQ_QUEUED, so a ++ * wakeup that finds prev still TASK_ON_RQ_MIGRATING falls ++ * through it and enqueues prev itself. Once it has, prev can be ++ * picked and run on another CPU, and take_task() empties its ++ * skiplist node again. ++ * ++ * task_queued() only asks whether that node is linked, so at ++ * that point it reads "prev still needs enqueueing" when it ++ * means "prev is running elsewhere", and puts a task that is on ++ * a CPU back on a runqueue for a second CPU to pick up. One ++ * task then runs on two CPUs off one stack, which shows up ++ * downstream as skiplist corruption, PSI counting the task ++ * twice, and a scribbled kernel stack. ++ * ++ * on_rq is what actually tracks the handover - return_task() ++ * set TASK_ON_RQ_MIGRATING and whoever takes prev over clears ++ * it - and pi_lock, held here and taken by ttwu() before it ++ * does anything, serialises the two. ++ */ ++ if (likely(task_on_rq_migrating(prev))) { ++ enqueue_task(rq, prev, 0); ++ prev->on_rq = TASK_ON_RQ_QUEUED; ++ /* Wake up the CPU if it's not already running */ ++ resched_if_idle(rq); ++ } ++ raw_spin_unlock(&prev->pi_lock); ++ raw_spin_unlock_irq(rq->lock); ++ return; ++ } ++#endif ++ hrexpiry_schedule_exit(rq); ++ raw_spin_unlock_irq(rq->lock); ++} ++ ++#ifndef prepare_arch_switch ++# define prepare_arch_switch(next) do { } while (0) ++#endif ++#ifndef finish_arch_switch ++# define finish_arch_switch(prev) do { } while (0) ++#endif ++#ifndef finish_arch_post_lock_switch ++# define finish_arch_post_lock_switch() do { } while (0) ++#endif ++ ++static inline void kmap_local_sched_out(void) ++{ ++#ifdef CONFIG_KMAP_LOCAL ++ if (unlikely(current->kmap_ctrl.idx)) ++ __kmap_local_sched_out(); ++#endif ++} ++ ++static inline void kmap_local_sched_in(void) ++{ ++#ifdef CONFIG_KMAP_LOCAL ++ if (unlikely(current->kmap_ctrl.idx)) ++ __kmap_local_sched_in(); ++#endif ++} ++ ++/** ++ * prepare_task_switch - prepare to switch tasks ++ * @rq: the runqueue preparing to switch ++ * @next: the task we are going to switch to. ++ * ++ * This is called with the rq lock held and interrupts off. It must ++ * be paired with a subsequent finish_task_switch after the context ++ * switch. ++ * ++ * prepare_task_switch sets up locking and calls architecture specific ++ * hooks. ++ */ ++static inline void ++prepare_task_switch(struct rq *rq, struct task_struct *prev, ++ struct task_struct *next) ++{ ++ kcov_prepare_switch(prev); ++ sched_info_switch(rq, prev, next); ++ perf_event_task_sched_out(prev, next); ++ /* ++ * rseq_preempt(prev) became rseq_sched_switch_event(next): rseq is now ++ * told about the task being scheduled *in*. ++ */ ++ rseq_sched_switch_event(next); ++ fire_sched_out_preempt_notifiers(prev, next); ++ kmap_local_sched_out(); ++ prepare_task(next); ++ prepare_arch_switch(next); ++} ++ ++/** ++ * finish_task_switch - clean up after a task-switch ++ * @rq: runqueue associated with task-switch ++ * @prev: the thread we just switched away from. ++ * ++ * finish_task_switch must be called after the context switch, paired ++ * with a prepare_task_switch call before the context switch. ++ * finish_task_switch will reconcile locking set up by prepare_task_switch, ++ * and do any other architecture-specific cleanup actions. ++ * ++ * Note that we may have delayed dropping an mm in context_switch(). If ++ * so, we finish that here outside of the runqueue lock. (Doing it ++ * with the lock held can cause deadlocks; see schedule() for ++ * details.) ++ * ++ * The context switch have flipped the stack from under us and restored the ++ * local variables which were saved when this task called schedule() in the ++ * past. prev == current is still correct but we need to recalculate this_rq ++ * because prev may have moved to another CPU. ++ */ ++static void finish_task_switch(struct task_struct *prev) ++ __releases(rq->lock) ++{ ++ struct rq *rq = this_rq(); ++ struct mm_struct *mm = rq->prev_mm; ++ long prev_state; ++ ++ /* ++ * The previous task will have left us with a preempt_count of 2 ++ * because it left us after: ++ * ++ * schedule() ++ * preempt_disable(); // 1 ++ * __schedule() ++ * raw_spin_lock_irq(rq->lock) // 2 ++ * ++ * Also, see FORK_PREEMPT_COUNT. ++ */ ++ if (WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET, ++ "corrupted preempt_count: %s/%d/0x%x\n", ++ current->comm, current->pid, preempt_count())) ++ preempt_count_set(FORK_PREEMPT_COUNT); ++ ++ rq->prev_mm = NULL; ++ ++ /* ++ * A task struct has one reference for the use as "current". ++ * If a task dies, then it sets TASK_DEAD in tsk->__state and calls ++ * schedule one last time. The schedule call will never return, and ++ * the scheduled task must drop that reference. ++ * ++ * We must observe prev->__state before clearing prev->on_cpu (in ++ * finish_task), otherwise a concurrent wakeup can get prev ++ * running on another CPU and we could rave with its RUNNING -> DEAD ++ * transition, resulting in a double drop. ++ */ ++ prev_state = prev->__state; ++ vtime_task_switch(prev); ++ perf_event_task_sched_in(prev, current); ++ finish_task(prev); ++ finish_lock_switch(rq, prev); ++ finish_arch_post_lock_switch(); ++ kcov_finish_switch(current); ++ /* ++ * kmap_local_sched_out() is invoked with rq::lock held and ++ * interrupts disabled. There is no requirement for that, but the ++ * sched out code does not have an interrupt enabled section. ++ * Restoring the maps on sched in does not require interrupts being ++ * disabled either. ++ */ ++ kmap_local_sched_in(); ++ ++ /* ++ * Any cached block-layer timestamp (plug->cur_ktime) is stale now, ++ * invalidate it. ++ */ ++ blk_plug_invalidate_ts(); ++ ++ fire_sched_in_preempt_notifiers(current); ++ /* ++ * When switching through a kernel thread, the loop in ++ * membarrier_{private,global}_expedited() may have observed that ++ * kernel thread and not issued an IPI. It is therefore possible to ++ * schedule between user->kernel->user threads without passing though ++ * switch_mm(). Membarrier requires a barrier after storing to ++ * rq->curr, before returning to userspace, so provide them here: ++ * ++ * - a full memory barrier for {PRIVATE,GLOBAL}_EXPEDITED, implicitly ++ * provided by mmdrop_lazy_tlb(), ++ * - a sync_core for SYNC_CORE. ++ */ ++ if (mm) { ++ membarrier_mm_sync_core_before_usermode(mm); ++ mmdrop_lazy_tlb_sched(mm); ++ } ++ if (unlikely(prev_state == TASK_DEAD)) { ++ /* ++ * Remove function-return probe instances associated with this ++ * task and put them back on the free list. ++ */ ++ kprobe_flush_task(prev); ++ ++ /* ++ * Unlink from css_set after the final switch so rmdir can ++ * complete. Must precede put_task_struct_rcu_user(). ++ */ ++ cgroup_task_dead(prev); ++ ++ /* Task is done with its stack. */ ++ put_task_stack(prev); ++ ++ put_task_struct_rcu_user(prev); ++ } ++} ++ ++/** ++ * schedule_tail - first thing a freshly forked thread must call. ++ * @prev: the thread we just switched away from. ++ */ ++asmlinkage __visible void schedule_tail(struct task_struct *prev) ++{ ++ /* ++ * New tasks start with FORK_PREEMPT_COUNT, see there and ++ * finish_task_switch() for details. ++ * ++ * finish_task_switch() will drop rq->lock() and lower preempt_count ++ * and the preempt_enable() will end up enabling preemption (on ++ * PREEMPT_COUNT kernels). ++ */ ++ ++ finish_task_switch(prev); ++ preempt_enable(); ++ ++ if (current->set_child_tid) ++ put_user(task_pid_vnr(current), current->set_child_tid); ++ ++ calculate_sigpending(); ++} ++ ++/* ++ * context_switch - switch to the new MM and the new thread's register state. ++ */ ++static __always_inline void ++context_switch(struct rq *rq, struct task_struct *prev, ++ struct task_struct *next) ++{ ++ prepare_task_switch(rq, prev, next); ++ ++ /* ++ * For paravirt, this is coupled with an exit in switch_to to ++ * combine the page table reload and the switch backend into ++ * one hypercall. ++ */ ++ arch_start_context_switch(prev); ++ ++ /* ++ * kernel -> kernel lazy + transfer active ++ * user -> kernel lazy + mmgrab_lazy_tlb() active ++ * ++ * kernel -> user switch + mmdrop_lazy_tlb() active ++ * user -> user switch ++ */ ++ if (!next->mm) { // to kernel ++ enter_lazy_tlb(prev->active_mm, next); ++ ++ next->active_mm = prev->active_mm; ++ if (prev->mm) // from user ++ mmgrab_lazy_tlb(prev->active_mm); ++ else ++ prev->active_mm = NULL; ++ } else { // to user ++ membarrier_switch_mm(rq, prev->active_mm, next->mm); ++ /* ++ * sys_membarrier() requires an smp_mb() between setting ++ * rq->curr / membarrier_switch_mm() and returning to userspace. ++ * ++ * The below provides this either through switch_mm(), or in ++ * case 'prev->active_mm == next->mm' through ++ * finish_task_switch()'s mmdrop_lazy_tlb(). ++ */ ++ switch_mm_irqs_off(prev->active_mm, next->mm, next); ++ lru_gen_use_mm(next->mm); ++ ++ if (!prev->mm) { // from kernel ++ /* will mmdrop_lazy_tlb() in finish_task_switch(). */ ++ rq->prev_mm = prev->active_mm; ++ prev->active_mm = NULL; ++ } ++ } ++ prepare_lock_switch(rq, next); ++ ++ /* Here we just switch the register state and the stack. */ ++ switch_to(prev, next, prev); ++ barrier(); ++ ++ finish_task_switch(prev); ++} ++ ++/* ++ * nr_running, nr_uninterruptible and nr_context_switches: ++ * ++ * externally visible scheduler statistics: current number of runnable ++ * threads, total number of context switches performed since bootup. ++ */ ++unsigned int nr_running(void) ++{ ++ unsigned long i, sum = 0; ++ ++ for_each_online_cpu(i) ++ sum += cpu_rq(i)->nr_running; ++ ++ return sum; ++} ++ ++static unsigned long nr_uninterruptible(void) ++{ ++ unsigned long i, sum = 0; ++ ++ for_each_online_cpu(i) ++ sum += cpu_rq(i)->nr_uninterruptible; ++ ++ return sum; ++} ++ ++/* ++ * Check if only the current task is running on the CPU. ++ * ++ * Caution: this function does not check that the caller has disabled ++ * preemption, thus the result might have a time-of-check-to-time-of-use ++ * race. The caller is responsible to use it correctly, for example: ++ * ++ * - from a non-preemptible section (of course) ++ * ++ * - from a thread that is bound to a single CPU ++ * ++ * - in a loop with very short iterations (e.g. a polling loop) ++ */ ++bool single_task_running(void) ++{ ++ if (rq_load(raw_rq()) == 1) ++ return true; ++ else ++ return false; ++} ++EXPORT_SYMBOL(single_task_running); ++ ++unsigned long long nr_context_switches(void) ++{ ++ int cpu; ++ unsigned long long sum = 0; ++ ++ for_each_possible_cpu(cpu) ++ sum += cpu_rq(cpu)->nr_switches; ++ ++ return sum; ++} ++ ++/* ++ * Consumers of these two interfaces, like for example the cpufreq menu ++ * governor are using nonsensical data. Boosting frequency for a CPU that has ++ * IO-wait which might not even end up running the task when it does become ++ * runnable. ++ */ ++ ++unsigned int nr_iowait_cpu(int cpu) ++{ ++ return atomic_read(&cpu_rq(cpu)->nr_iowait); ++} ++ ++/* ++ * IO-wait accounting, and how it's mostly bollocks (on SMP). ++ * ++ * The idea behind IO-wait account is to account the idle time that we could ++ * have spend running if it were not for IO. That is, if we were to improve the ++ * storage performance, we'd have a proportional reduction in IO-wait time. ++ * ++ * This all works nicely on UP, where, when a task blocks on IO, we account ++ * idle time as IO-wait, because if the storage were faster, it could've been ++ * running and we'd not be idle. ++ * ++ * This has been extended to SMP, by doing the same for each CPU. This however ++ * is broken. ++ * ++ * Imagine for instance the case where two tasks block on one CPU, only the one ++ * CPU will have IO-wait accounted, while the other has regular idle. Even ++ * though, if the storage were faster, both could've ran at the same time, ++ * utilising both CPUs. ++ * ++ * This means, that when looking globally, the current IO-wait accounting on ++ * SMP is a lower bound, by reason of under accounting. ++ * ++ * Worse, since the numbers are provided per CPU, they are sometimes ++ * interpreted per CPU, and that is nonsensical. A blocked task isn't strictly ++ * associated with any one particular CPU, it can wake to another CPU than it ++ * blocked on. This means the per CPU IO-wait number is meaningless. ++ * ++ * Task CPU affinities can make all that even more 'interesting'. ++ */ ++ ++unsigned int nr_iowait(void) ++{ ++ unsigned long cpu, sum = 0; ++ ++ for_each_possible_cpu(cpu) ++ sum += nr_iowait_cpu(cpu); ++ ++ return sum; ++} ++ ++static unsigned long nr_active(void) ++{ ++ return nr_running() + nr_uninterruptible(); ++} ++ ++/* Variables and functions for calc_load */ ++static unsigned long calc_load_update; ++unsigned long avenrun[3]; ++EXPORT_SYMBOL(avenrun); ++ ++/** ++ * get_avenrun - get the load average array ++ * @loads: pointer to dest load array ++ * @offset: offset to add ++ * @shift: shift count to shift the result left ++ * ++ * These values are estimates at best, so no need for locking. ++ */ ++void get_avenrun(unsigned long *loads, unsigned long offset, int shift) ++{ ++ loads[0] = (avenrun[0] + offset) << shift; ++ loads[1] = (avenrun[1] + offset) << shift; ++ loads[2] = (avenrun[2] + offset) << shift; ++} ++ ++/* ++ * calc_load - update the avenrun load estimates every LOAD_FREQ seconds. ++ */ ++void calc_global_load(void) ++{ ++ long active; ++ ++ if (time_before(jiffies, READ_ONCE(calc_load_update))) ++ return; ++ active = nr_active() * FIXED_1; ++ ++ avenrun[0] = calc_load(avenrun[0], EXP_1, active); ++ avenrun[1] = calc_load(avenrun[1], EXP_5, active); ++ avenrun[2] = calc_load(avenrun[2], EXP_15, active); ++ ++ calc_load_update = jiffies + LOAD_FREQ; ++} ++ ++/** ++ * fixed_power_int - compute: x^n, in O(log n) time ++ * ++ * @x: base of the power ++ * @frac_bits: fractional bits of @x ++ * @n: power to raise @x to. ++ * ++ * By exploiting the relation between the definition of the natural power ++ * function: x^n := x*x*...*x (x multiplied by itself for n times), and ++ * the binary encoding of numbers used by computers: n := \Sum n_i * 2^i, ++ * (where: n_i \elem {0, 1}, the binary vector representing n), ++ * we find: x^n := x^(\Sum n_i * 2^i) := \Prod x^(n_i * 2^i), which is ++ * of course trivially computable in O(log_2 n), the length of our binary ++ * vector. ++ */ ++static unsigned long ++fixed_power_int(unsigned long x, unsigned int frac_bits, unsigned int n) ++{ ++ unsigned long result = 1UL << frac_bits; ++ ++ if (n) { ++ for (;;) { ++ if (n & 1) { ++ result *= x; ++ result += 1UL << (frac_bits - 1); ++ result >>= frac_bits; ++ } ++ n >>= 1; ++ if (!n) ++ break; ++ x *= x; ++ x += 1UL << (frac_bits - 1); ++ x >>= frac_bits; ++ } ++ } ++ ++ return result; ++} ++ ++/* ++ * a1 = a0 * e + a * (1 - e) ++ * ++ * a2 = a1 * e + a * (1 - e) ++ * = (a0 * e + a * (1 - e)) * e + a * (1 - e) ++ * = a0 * e^2 + a * (1 - e) * (1 + e) ++ * ++ * a3 = a2 * e + a * (1 - e) ++ * = (a0 * e^2 + a * (1 - e) * (1 + e)) * e + a * (1 - e) ++ * = a0 * e^3 + a * (1 - e) * (1 + e + e^2) ++ * ++ * ... ++ * ++ * an = a0 * e^n + a * (1 - e) * (1 + e + ... + e^n-1) [1] ++ * = a0 * e^n + a * (1 - e) * (1 - e^n)/(1 - e) ++ * = a0 * e^n + a * (1 - e^n) ++ * ++ * [1] application of the geometric series: ++ * ++ * n 1 - x^(n+1) ++ * S_n := \Sum x^i = ------------- ++ * i=0 1 - x ++ */ ++unsigned long ++calc_load_n(unsigned long load, unsigned long exp, ++ unsigned long active, unsigned int n) ++{ ++ return calc_load(load, fixed_power_int(exp, FSHIFT, n), active); ++} ++ ++DEFINE_PER_CPU(struct kernel_stat, kstat); ++DEFINE_PER_CPU(struct kernel_cpustat, kernel_cpustat) = { ++#ifdef CONFIG_NO_HZ_COMMON ++ .idle_sleeptime_seq = SEQCNT_ZERO(kernel_cpustat.idle_sleeptime_seq) ++#endif ++}; ++ ++EXPORT_PER_CPU_SYMBOL(kstat); ++EXPORT_PER_CPU_SYMBOL(kernel_cpustat); ++ ++#ifdef CONFIG_PARAVIRT ++static inline u64 steal_ticks(u64 steal) ++{ ++ if (unlikely(steal > NSEC_PER_SEC)) ++ return div_u64(steal, TICK_NSEC); ++ ++ return __iter_div_u64_rem(steal, TICK_NSEC, &steal); ++} ++#endif ++ ++#ifndef nsecs_to_cputime ++# define nsecs_to_cputime(__nsecs) nsecs_to_jiffies(__nsecs) ++#endif ++ ++/* ++ * On each tick, add the number of nanoseconds to the unbanked variables and ++ * once one tick's worth has accumulated, account it allowing for accurate ++ * sub-tick accounting and totals. Use the TICK_APPROX_NS to match the way we ++ * deduct nanoseconds. ++ */ ++static void pc_idle_time(struct rq *rq, struct task_struct *idle, unsigned long ns) ++{ ++ u64 *cpustat = kcpustat_this_cpu->cpustat; ++ unsigned long ticks; ++ ++ if (atomic_read(&rq->nr_iowait) > 0) { ++ rq->iowait_ns += ns; ++ if (rq->iowait_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->iowait_ns); ++ cpustat[CPUTIME_IOWAIT] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->iowait_ns %= JIFFY_NS; ++ } ++ } else { ++ rq->idle_ns += ns; ++ if (rq->idle_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->idle_ns); ++ cpustat[CPUTIME_IDLE] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->idle_ns %= JIFFY_NS; ++ } ++ } ++ acct_update_integrals(idle); ++} ++ ++static void pc_system_time(struct rq *rq, struct task_struct *p, ++ int hardirq_offset, unsigned long ns) ++{ ++ u64 *cpustat = kcpustat_this_cpu->cpustat; ++ unsigned long ticks; ++ ++ p->stime_ns += ns; ++ if (p->stime_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(p->stime_ns); ++ p->stime_ns %= JIFFY_NS; ++ p->stime += (__force u64)TICK_APPROX_NS * ticks; ++ account_group_system_time(p, TICK_APPROX_NS * ticks); ++ } ++ p->sched_time += ns; ++ account_group_exec_runtime(p, ns); ++ ++ if (hardirq_count() - hardirq_offset) { ++ rq->irq_ns += ns; ++ if (rq->irq_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->irq_ns); ++ cpustat[CPUTIME_IRQ] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->irq_ns %= JIFFY_NS; ++ } ++ } else if (in_serving_softirq() || this_cpu_ksoftirqd() == p) { ++ /* ++ * ksoftirqd time does not get accounted in cpu_softirq_time, so ++ * it has to be handled separately here. Naming it explicitly ++ * also catches it between batches, where it has reenabled bh ++ * and in_serving_softirq() no longer holds - which is where the ++ * context switch that lands us here happens. ++ */ ++ rq->softirq_ns += ns; ++ if (rq->softirq_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->softirq_ns); ++ cpustat[CPUTIME_SOFTIRQ] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->softirq_ns %= JIFFY_NS; ++ } ++ } else { ++ rq->system_ns += ns; ++ if (rq->system_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->system_ns); ++ cpustat[CPUTIME_SYSTEM] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->system_ns %= JIFFY_NS; ++ } ++ } ++ acct_update_integrals(p); ++} ++ ++static void pc_user_time(struct rq *rq, struct task_struct *p, unsigned long ns) ++{ ++ u64 *cpustat = kcpustat_this_cpu->cpustat; ++ unsigned long ticks; ++ ++ p->utime_ns += ns; ++ if (p->utime_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(p->utime_ns); ++ p->utime_ns %= JIFFY_NS; ++ p->utime += (__force u64)TICK_APPROX_NS * ticks; ++ account_group_user_time(p, TICK_APPROX_NS * ticks); ++ } ++ p->sched_time += ns; ++ account_group_exec_runtime(p, ns); ++ ++ if (task_nice(p) > 0 || idleprio_task(p)) { ++ rq->nice_ns += ns; ++ if (rq->nice_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->nice_ns); ++ cpustat[CPUTIME_NICE] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->nice_ns %= JIFFY_NS; ++ } ++ } else { ++ rq->user_ns += ns; ++ if (rq->user_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->user_ns); ++ cpustat[CPUTIME_USER] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->user_ns %= JIFFY_NS; ++ } ++ } ++ acct_update_integrals(p); ++} ++ ++/* ++ * This is called on clock ticks. ++ * Bank in p->sched_time the ns elapsed since the last tick or switch. ++ * CPU scheduler quota accounting is also performed here in microseconds. ++ */ ++static void update_cpu_clock_tick(struct rq *rq, struct task_struct *p) ++{ ++ s64 account_ns = rq->niffies - p->last_ran; ++ struct task_struct *idle = rq->idle; ++ ++ /* Accurate tick timekeeping */ ++ if (user_mode(get_irq_regs())) ++ pc_user_time(rq, p, account_ns); ++ else if (p != idle || (irq_count() != HARDIRQ_OFFSET)) { ++ pc_system_time(rq, p, HARDIRQ_OFFSET, account_ns); ++ } else ++ pc_idle_time(rq, idle, account_ns); ++ ++ /* time_slice accounting is done in usecs to avoid overflow on 32bit */ ++ if (p->policy != SCHED_FIFO && p != idle) ++ p->time_slice -= NS_TO_US(account_ns); ++ ++ p->last_ran = rq->niffies; ++} ++ ++/* ++ * This is called on context switches. ++ * Bank in p->sched_time the ns elapsed since the last tick or switch. ++ * CPU scheduler quota accounting is also performed here in microseconds. ++ */ ++static void update_cpu_clock_switch(struct rq *rq, struct task_struct *p) ++{ ++ s64 account_ns = rq->niffies - p->last_ran; ++ struct task_struct *idle = rq->idle; ++ ++ /* ++ * Accurate subtick timekeeping. There is no interrupt frame to sample ++ * here the way update_cpu_clock_tick() samples one: the entry code has ++ * already restored the previous value by the time irqentry_exit() ++ * reaches preempt_schedule_irq(), and a task that called schedule() ++ * itself never had a frame at all. What can be said for certain is that ++ * a task which never executes user code cannot have spent this interval ++ * in userspace, so charge all of those as system time. Without this the ++ * whole of every kernel thread's runtime that lands between two ticks ++ * is booked as user time, which is how irq threads, kworkers and ++ * ksoftirqd end up carrying utime they cannot possibly have accrued. ++ * ++ * A user task that blocks in a syscall genuinely does split its ++ * interval between the two modes, and knowing where it crossed over ++ * needs timestamped user/kernel transitions - vtime - that MuQSS ++ * deliberately does not take. Those keep the historical assumption. ++ */ ++ if (p == idle) ++ pc_idle_time(rq, idle, account_ns); ++ else if (is_user_task(p)) ++ pc_user_time(rq, p, account_ns); ++ else ++ pc_system_time(rq, p, 0, account_ns); ++ ++ /* time_slice accounting is done in usecs to avoid overflow on 32bit */ ++ if (p->policy != SCHED_FIFO && p != idle) ++ p->time_slice -= NS_TO_US(account_ns); ++} ++ ++#ifdef CONFIG_MUQSS_IOTIME ++/* ++ * The CPU time current has consumed so far, for measuring how long a stretch ++ * of work took the thread doing it. ++ * ++ * ->sched_time alone is not enough. It is banked at ticks and at context ++ * switches, and a kworker's switches happen at the ends of a whole batch of ++ * work items, so the delta across any one of them is usually a flat zero. The ++ * unbanked remainder has to be added in, and that means reading a clock: ++ * sched_clock_cpu() rather than rq->niffies, which is only refreshed under ++ * the rq lock and would be stale by up to a tick here. ++ * ++ * niffies is monotonised forward of the raw clock, so last_ran can be ahead ++ * of what we read and the remainder can come out negative. Drop it in that ++ * case; it is bounded by the skew between the two and the banked figure is ++ * still right. ++ * ++ * Interrupts are off across the read so the tick cannot land between taking ++ * ->sched_time and taking ->last_ran and have the interval counted in both. ++ * That also pins us to this CPU, which is what makes the subtraction a ++ * same-clock one. ++ */ ++u64 muqss_task_runtime_live(void) ++{ ++ struct task_struct *p = current; ++ unsigned long flags; ++ s64 remainder; ++ u64 ns, ran; ++ ++ local_irq_save(flags); ++ ns = p->sched_time; ++ ran = p->last_ran; ++ remainder = sched_clock_cpu(smp_processor_id()) - ran; ++ local_irq_restore(flags); ++ ++ if (likely(remainder > 0)) ++ ns += remainder; ++ ++ return ns; ++} ++#endif ++ ++/* ++ * Return any ns on the sched_clock that have not yet been accounted in ++ * @p in case that task is currently running. ++ * ++ * Called with task_rq_lock(p) held. ++ */ ++static inline u64 do_task_delta_exec(struct task_struct *p, struct rq *rq) ++{ ++ u64 ns = 0; ++ ++ /* ++ * Must be ->curr _and_ ->on_rq. If dequeued, we would ++ * project cycles that may never be accounted to this ++ * thread, breaking clock_gettime(). ++ */ ++ if (p == rq->curr && task_on_rq_queued(p)) { ++ update_clocks(rq); ++ ns = rq->niffies - p->last_ran; ++ } ++ ++ return ns; ++} ++ ++/* ++ * Return accounted runtime for the task. ++ * Return separately the current's pending runtime that have not been ++ * accounted yet. ++ */ ++unsigned long long task_sched_runtime(struct task_struct *p) ++{ ++ struct rq_flags rf; ++ struct rq *rq; ++ u64 ns; ++ ++#if defined(CONFIG_64BIT) && defined(CONFIG_SMP) ++ /* ++ * 64-bit doesn't need locks to atomically read a 64-bit value. ++ * So we have a optimisation chance when the task's delta_exec is 0. ++ * Reading ->on_cpu is racy, but this is ok. ++ * ++ * If we race with it leaving CPU, we'll take a lock. So we're correct. ++ * If we race with it entering CPU, unaccounted time is 0. This is ++ * indistinguishable from the read occurring a few cycles earlier. ++ * If we see ->on_cpu without ->on_rq, the task is leaving, and has ++ * been accounted, so we're correct here as well. ++ */ ++ if (!p->on_cpu || !task_on_rq_queued(p)) ++ return tsk_seruntime(p); ++#endif ++ ++ rq = task_rq_lock(p, &rf); ++ ns = p->sched_time + do_task_delta_exec(p, rq); ++ task_rq_unlock(rq, p, &rf); ++ ++ return ns; ++} ++ ++/* ++ * Functions to test for when SCHED_ISO tasks have used their allocated ++ * quota as real time scheduling and convert them back to SCHED_NORMAL. All ++ * data is modified only by the local runqueue during sched_tick with ++ * interrupts disabled. ++ */ ++ ++/* ++ * Test if SCHED_ISO tasks have run longer than their alloted period as RT ++ * tasks and set the refractory flag if necessary. There is 10% hysteresis ++ * for unsetting the flag. 115/128 is ~90/100 as a fast shift instead of a ++ * slow division. ++ */ ++static inline void iso_tick(struct rq *rq) ++{ ++ rq->iso_ticks = rq->iso_ticks * (ISO_PERIOD - 1) / ISO_PERIOD; ++ rq->iso_ticks += 100; ++ if (rq->iso_ticks > ISO_PERIOD * sched_iso_cpu) { ++ rq->iso_refractory = true; ++ if (unlikely(rq->iso_ticks > ISO_PERIOD * 100)) ++ rq->iso_ticks = ISO_PERIOD * 100; ++ } ++} ++ ++/* No SCHED_ISO task was running so decrease rq->iso_ticks */ ++static inline void no_iso_tick(struct rq *rq, int ticks) ++{ ++ if (rq->iso_ticks > 0 || rq->iso_refractory) { ++ rq->iso_ticks = rq->iso_ticks * (ISO_PERIOD - ticks) / ISO_PERIOD; ++ if (rq->iso_ticks < ISO_PERIOD * (sched_iso_cpu * 115 / 128)) { ++ rq->iso_refractory = false; ++ if (unlikely(rq->iso_ticks < 0)) ++ rq->iso_ticks = 0; ++ } ++ } ++} ++ ++/* This manages tasks that have run out of timeslice during a sched_tick */ ++static void task_running_tick(struct rq *rq) ++{ ++ struct task_struct *p = rq->curr; ++ ++ /* ++ * If a SCHED_ISO task is running we increment the iso_ticks. In ++ * order to prevent SCHED_ISO tasks from causing starvation in the ++ * presence of true RT tasks we account those as iso_ticks as well. ++ */ ++ if (rt_task(p) || task_running_iso(p)) ++ iso_tick(rq); ++ else ++ no_iso_tick(rq, 1); ++ ++ /* SCHED_FIFO tasks never run out of timeslice. */ ++ if (p->policy == SCHED_FIFO) ++ return; ++ ++ if (iso_task(p)) { ++ if (task_running_iso(p)) { ++ if (rq->iso_refractory) { ++ /* ++ * SCHED_ISO task is running as RT and limit ++ * has been hit. Force it to reschedule as ++ * SCHED_NORMAL by zeroing its time_slice ++ */ ++ p->time_slice = 0; ++ } ++ } else if (!rq->iso_refractory) { ++ /* Can now run again ISO. Reschedule to pick up prio */ ++ goto out_resched; ++ } ++ } ++ ++ /* ++ * Tasks that were scheduled in the first half of a tick are not ++ * allowed to run into the 2nd half of the next tick if they will ++ * run out of time slice in the interim. Otherwise, if they have ++ * less than RESCHED_US ÎŒs of time slice left they will be rescheduled. ++ * Dither is used as a backup for when hrexpiry is disabled or high res ++ * timers not configured in. ++ */ ++ if (p->time_slice - rq->dither >= RESCHED_US) ++ return; ++out_resched: ++ rq_lock(rq); ++ __set_tsk_resched(p); ++ rq_unlock(rq); ++} ++ ++static inline void task_tick(struct rq *rq) ++{ ++ if (!rq_idle(rq)) ++ task_running_tick(rq); ++ else if (rq->last_jiffy > rq->last_scheduler_tick) ++ no_iso_tick(rq, rq->last_jiffy - rq->last_scheduler_tick); ++} ++ ++#ifdef CONFIG_NO_HZ_FULL ++/* ++ * We can stop the timer tick any time highres timers are active since ++ * we rely entirely on highres timeouts for task expiry rescheduling. ++ */ ++static void sched_stop_tick(struct rq *rq, int cpu) ++{ ++ if (!hrexpiry_enabled(rq)) ++ return; ++ if (!tick_nohz_full_enabled()) ++ return; ++ if (!tick_nohz_full_cpu(cpu)) ++ return; ++ tick_nohz_dep_clear_cpu(cpu, TICK_DEP_BIT_SCHED); ++} ++ ++static inline void sched_start_tick(struct rq *rq, int cpu) ++{ ++ tick_nohz_dep_set_cpu(cpu, TICK_DEP_BIT_SCHED); ++} ++ ++struct tick_work { ++ int cpu; ++ atomic_t state; ++ struct delayed_work work; ++}; ++/* Values for ->state, see diagram below. */ ++#define TICK_SCHED_REMOTE_OFFLINE 0 ++#define TICK_SCHED_REMOTE_OFFLINING 1 ++#define TICK_SCHED_REMOTE_RUNNING 2 ++ ++/* ++ * State diagram for ->state: ++ * ++ * ++ * TICK_SCHED_REMOTE_OFFLINE ++ * | ^ ++ * | | ++ * | | sched_tick_remote() ++ * | | ++ * | | ++ * +--TICK_SCHED_REMOTE_OFFLINING ++ * | ^ ++ * | | ++ * sched_tick_start() | | sched_tick_stop() ++ * | | ++ * V | ++ * TICK_SCHED_REMOTE_RUNNING ++ * ++ * ++ * Other transitions get WARN_ON_ONCE(), except that sched_tick_remote() ++ * and sched_tick_start() are happy to leave the state in RUNNING. ++ */ ++ ++static struct tick_work __percpu *tick_work_cpu; ++ ++static void sched_tick_remote(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct tick_work *twork = container_of(dwork, struct tick_work, work); ++ int cpu = twork->cpu; ++ struct rq *rq = cpu_rq(cpu); ++ struct task_struct *curr; ++ u64 delta; ++ int os; ++ ++ /* ++ * Handle the tick only if it appears the remote CPU is running in full ++ * dynticks mode. The check is racy by nature, but missing a tick or ++ * having one too much is no big deal because the scheduler tick updates ++ * statistics and checks timeslices in a time-independent way, regardless ++ * of when exactly it is running. ++ */ ++ if (!tick_nohz_tick_stopped_cpu(cpu)) ++ goto out_requeue; ++ ++ rq_lock_irq(rq); ++ if (cpu_is_offline(cpu)) ++ goto out_unlock; ++ ++ curr = rq->curr; ++ update_rq_clock(rq); ++ ++ if (!is_idle_task(curr)) { ++ /* ++ * Make sure the next tick runs within a reasonable ++ * amount of time. ++ */ ++ delta = rq_clock_task(rq) - curr->last_ran; ++ WARN_ON_ONCE(delta > (u64)NSEC_PER_SEC * 3); ++ } ++ /* ++ * task_tick() takes the rq lock itself when it needs to force a ++ * reschedule so we must drop it here, keeping interrupts disabled ++ * to match the context it is called in from sched_tick(). ++ */ ++ rq_unlock(rq); ++ task_tick(rq); ++ local_irq_enable(); ++ goto out_requeue; ++ ++out_unlock: ++ rq_unlock_irq(rq, NULL); ++ ++out_requeue: ++ ++ /* ++ * Run the remote tick once per second (1Hz). This arbitrary ++ * frequency is large enough to avoid overload but short enough ++ * to keep scheduler internal stats reasonably up to date. But ++ * first update state to reflect hotplug activity if required. ++ */ ++ os = atomic_fetch_add_unless(&twork->state, -1, TICK_SCHED_REMOTE_RUNNING); ++ WARN_ON_ONCE(os == TICK_SCHED_REMOTE_OFFLINE); ++ if (os == TICK_SCHED_REMOTE_RUNNING) ++ queue_delayed_work(system_unbound_wq, dwork, HZ); ++} ++ ++static void sched_tick_start(int cpu) ++{ ++ struct tick_work *twork; ++ int os; ++ ++ if (housekeeping_cpu(cpu, HK_TYPE_TICK)) ++ return; ++ ++ WARN_ON_ONCE(!tick_work_cpu); ++ ++ twork = per_cpu_ptr(tick_work_cpu, cpu); ++ os = atomic_xchg(&twork->state, TICK_SCHED_REMOTE_RUNNING); ++ WARN_ON_ONCE(os == TICK_SCHED_REMOTE_RUNNING); ++ if (os == TICK_SCHED_REMOTE_OFFLINE) { ++ twork->cpu = cpu; ++ INIT_DELAYED_WORK(&twork->work, sched_tick_remote); ++ queue_delayed_work(system_unbound_wq, &twork->work, HZ); ++ } ++} ++ ++#ifdef CONFIG_HOTPLUG_CPU ++static void sched_tick_stop(int cpu) ++{ ++ struct tick_work *twork; ++ int os; ++ ++ if (housekeeping_cpu(cpu, HK_TYPE_TICK)) ++ return; ++ ++ WARN_ON_ONCE(!tick_work_cpu); ++ ++ twork = per_cpu_ptr(tick_work_cpu, cpu); ++ /* There cannot be competing actions, but don't rely on stop-machine. */ ++ os = atomic_xchg(&twork->state, TICK_SCHED_REMOTE_OFFLINING); ++ WARN_ON_ONCE(os != TICK_SCHED_REMOTE_RUNNING); ++ /* Don't cancel, as this would mess up the state machine. */ ++} ++#endif /* CONFIG_HOTPLUG_CPU */ ++ ++int __init sched_tick_offload_init(void) ++{ ++ tick_work_cpu = alloc_percpu(struct tick_work); ++ BUG_ON(!tick_work_cpu); ++ return 0; ++} ++ ++#else /* !CONFIG_NO_HZ_FULL */ ++static inline void sched_stop_tick(struct rq *rq, int cpu) {} ++static inline void sched_start_tick(struct rq *rq, int cpu) {} ++static inline void sched_tick_start(int cpu) { } ++static inline void sched_tick_stop(int cpu) { } ++#endif ++ ++/* ++ * This function gets called by the timer code, with HZ frequency. ++ * We call it with interrupts disabled. ++ */ ++void sched_tick(void) ++{ ++ int cpu __maybe_unused = smp_processor_id(); ++ struct rq *rq = cpu_rq(cpu); ++ ++ arch_scale_freq_tick(); ++ sched_clock_tick(); ++ update_clocks(rq); ++ update_load_avg(rq, 0); ++ update_cpu_clock_tick(rq, rq->curr); ++ task_tick(rq); ++ rq->last_scheduler_tick = rq->last_jiffy; ++ rq->last_tick = rq->clock; ++ perf_event_task_tick(); ++ sched_stop_tick(rq, cpu); ++} ++ ++#if defined(CONFIG_PREEMPTION) && (defined(CONFIG_DEBUG_PREEMPT) || \ ++ defined(CONFIG_TRACE_PREEMPT_TOGGLE)) ++/* ++ * If the value passed in is equal to the current preempt count ++ * then we just disabled preemption. Start timing the latency. ++ */ ++static inline void preempt_latency_start(int val) ++{ ++ if (preempt_count() == val) { ++ unsigned long ip = get_lock_parent_ip(); ++#ifdef CONFIG_DEBUG_PREEMPT ++ current->preempt_disable_ip = ip; ++#endif ++ trace_preempt_off(CALLER_ADDR0, ip); ++ } ++} ++ ++void preempt_count_add(int val) ++{ ++#ifdef CONFIG_DEBUG_PREEMPT ++ /* ++ * Underflow? ++ */ ++ if (DEBUG_LOCKS_WARN_ON((preempt_count() < 0))) ++ return; ++#endif ++ __preempt_count_add(val); ++#ifdef CONFIG_DEBUG_PREEMPT ++ /* ++ * Spinlock count overflowing soon? ++ */ ++ DEBUG_LOCKS_WARN_ON((preempt_count() & PREEMPT_MASK) >= ++ PREEMPT_MASK - 10); ++#endif ++ preempt_latency_start(val); ++} ++EXPORT_SYMBOL(preempt_count_add); ++NOKPROBE_SYMBOL(preempt_count_add); ++ ++/* ++ * If the value passed in equals to the current preempt count ++ * then we just enabled preemption. Stop timing the latency. ++ */ ++static inline void preempt_latency_stop(int val) ++{ ++ if (preempt_count() == val) ++ trace_preempt_on(CALLER_ADDR0, get_lock_parent_ip()); ++} ++ ++void preempt_count_sub(int val) ++{ ++#ifdef CONFIG_DEBUG_PREEMPT ++ /* ++ * Underflow? ++ */ ++ if (DEBUG_LOCKS_WARN_ON(val > preempt_count())) ++ return; ++ /* ++ * Is the spinlock portion underflowing? ++ */ ++ if (DEBUG_LOCKS_WARN_ON((val < PREEMPT_MASK) && ++ !(preempt_count() & PREEMPT_MASK))) ++ return; ++#endif ++ ++ preempt_latency_stop(val); ++ __preempt_count_sub(val); ++} ++EXPORT_SYMBOL(preempt_count_sub); ++NOKPROBE_SYMBOL(preempt_count_sub); ++ ++#else ++static inline void preempt_latency_start(int val) { } ++static inline void preempt_latency_stop(int val) { } ++#endif ++ ++static inline unsigned long get_preempt_disable_ip(struct task_struct *p) ++{ ++#ifdef CONFIG_DEBUG_PREEMPT ++ return p->preempt_disable_ip; ++#else ++ return 0; ++#endif ++} ++ ++/* ++ * The time_slice is only refilled when it is empty and that is when we set a ++ * new deadline. Make sure update_clocks has been called recently to update ++ * rq->niffies. ++ */ ++static void time_slice_expired(struct task_struct *p, struct rq *rq) ++{ ++ p->time_slice = timeslice(); ++ /* ++ * This assignment is absolute, so the charge has to be folded in here ++ * rather than left to enqueue_task(), or expiry would reset the task ++ * to an uncharged baseline. ++ */ ++ p->deadline = rq->niffies + task_deadline_diff(p) + ++ consume_task_penalty(p); ++#ifdef CONFIG_SMT_NICE ++ if (!p->mm) ++ p->smt_bias = 0; ++ else if (rt_task(p)) ++ p->smt_bias = 1 << 30; ++ else if (task_running_iso(p)) ++ p->smt_bias = 1 << 29; ++ else if (idleprio_task(p)) { ++ if (task_running_idle(p)) ++ p->smt_bias = 0; ++ else ++ p->smt_bias = 1; ++ } else if (--p->smt_bias < 1) ++ p->smt_bias = MAX_PRIO - p->static_prio; ++#endif ++} ++ ++/* ++ * Timeslices below RESCHED_US are considered as good as expired as there's no ++ * point rescheduling when there's so little time left. SCHED_BATCH tasks ++ * have been flagged be not latency sensitive and likely to be fully CPU ++ * bound so every time they're rescheduled they have their time_slice ++ * refilled, but get a new later deadline to have little effect on ++ * SCHED_NORMAL tasks. ++ ++ */ ++static inline void check_deadline(struct task_struct *p, struct rq *rq) ++{ ++ if (p->time_slice < RESCHED_US || batch_task(p)) ++ time_slice_expired(p, rq); ++} ++ ++/* ++ * Task selection with skiplists is a simple matter of picking off the first ++ * task in the sorted list, an O(1) operation. The lookup is amortised O(1) ++ * being bound to the number of processors. ++ * ++ * Runqueues are selectively locked based on their unlocked data and then ++ * unlocked if not needed. At most 3 locks will be held at any time and are ++ * released as soon as they're no longer needed. All balancing between CPUs ++ * is thus done here in an extremely simple first come best fit manner. ++ * ++ * This iterates over runqueues in cache locality order. In interactive mode ++ * it iterates over all CPUs and finds the task with the best key/deadline. ++ * In non-interactive mode it will only take a task if it's from the current ++ * runqueue or a runqueue with more tasks than the current one with a better ++ * key/deadline. ++ */ ++#ifdef CONFIG_SMP ++static inline struct task_struct ++*earliest_deadline_task(struct rq *rq, int cpu, struct task_struct *idle) ++{ ++ struct rq *locked = NULL, *chosen = NULL; ++ struct task_struct *edt = idle; ++ int i, best_entries = 0; ++ u64 best_key = ~0ULL; ++ ++ for (i = 0; i < total_runqueues; i++) { ++ skiplist *sl = rq->sl_order[i]; ++ struct rq *other_rq = NULL; ++ skiplist_node *next; ++ int entries; ++ ++ entries = READ_ONCE(sl->entries); ++ /* ++ * Check for queued entres lockless first. The local runqueue ++ * is locked so entries will always be accurate. ++ */ ++ if (!sched_interactive) { ++ /* ++ * Don't reschedule balance across nodes unless the CPU ++ * is idle. Non-interactive needs the rq: cpu is read ++ * before the entries filter. ++ */ ++ other_rq = rq_order(rq, i); ++ if (edt != idle && rq->cpu_locality[other_rq->cpu] > LOCALITY_SMP) ++ break; ++ if (entries <= best_entries) ++ continue; ++ } else if (!entries) ++ continue; ++ ++ /* if (i) implies other_rq != rq */ ++ if (i) { ++ /* Check for best id queued lockless first */ ++ if (READ_ONCE(sl->best_key) >= best_key) ++ continue; ++ ++ other_rq = rq_order(rq, i); ++ if (unlikely(!trylock_rq(rq, other_rq))) ++ continue; ++ ++ /* Need to reevaluate entries after locking */ ++ entries = sl->entries; ++ if (unlikely(!entries)) { ++ unlock_rq(other_rq); ++ continue; ++ } ++ } ++ ++ next = sl->header; ++ /* ++ * In interactive mode we check beyond the best entry on other ++ * runqueues if we can't get the best for smt or affinity ++ * reasons. ++ */ ++ while ((next = next->next[0]) != sl->header) { ++ struct task_struct *p; ++ u64 key = next->key; ++ ++ /* Reevaluate key after locking */ ++ if (key >= best_key) ++ break; ++ ++ p = container_of(next, struct task_struct, node); ++ if (!smt_schedule(p, rq)) { ++ if (i && !sched_interactive) ++ break; ++ continue; ++ } ++ ++ if (sched_other_cpu(p, cpu)) { ++ if (sched_interactive || !i) ++ continue; ++ break; ++ } ++ /* Make sure affinity is ok */ ++ if (i) { ++ /* From this point on p is the best so far */ ++ if (locked) ++ unlock_rq(locked); ++ chosen = locked = other_rq; ++ } ++ best_entries = entries; ++ best_key = key; ++ edt = p; ++ break; ++ } ++ /* rq->preempting is a hint only as the state may have changed ++ * since it was set with the resched call but if we have met ++ * the condition we can break out here. */ ++ if (edt == rq->preempting) ++ break; ++ if (i && other_rq != chosen) ++ unlock_rq(other_rq); ++ } ++ ++ if (likely(edt != idle)) ++ take_task(rq, cpu, edt); ++ ++ if (locked) ++ unlock_rq(locked); ++ ++ rq->preempting = NULL; ++ ++ return edt; ++} ++#else /* CONFIG_SMP */ ++static inline struct task_struct ++*earliest_deadline_task(struct rq *rq, int cpu, struct task_struct *idle) ++{ ++ struct task_struct *edt; ++ ++ if (unlikely(!rq->sl->entries)) ++ return idle; ++ edt = container_of(rq->node->next[0], struct task_struct, node); ++ take_task(rq, cpu, edt); ++ return edt; ++} ++#endif /* CONFIG_SMP */ ++ ++/* ++ * Print scheduling while atomic bug: ++ */ ++static noinline void __schedule_bug(struct task_struct *prev) ++{ ++ /* Save this before calling printk(), since that will clobber it */ ++ unsigned long preempt_disable_ip = get_preempt_disable_ip(current); ++ ++ if (oops_in_progress) ++ return; ++ ++ printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n", ++ prev->comm, prev->pid, preempt_count()); ++ ++ debug_show_held_locks(prev); ++ print_modules(); ++ if (irqs_disabled()) ++ print_irqtrace_events(prev); ++ if (IS_ENABLED(CONFIG_DEBUG_PREEMPT) ++ && in_atomic_preempt_off()) { ++ pr_err("Preemption disabled at:"); ++ print_ip_sym(KERN_ERR, preempt_disable_ip); ++ } ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++} ++ ++/* ++ * Various schedule()-time debugging checks and statistics: ++ */ ++static inline void schedule_debug(struct task_struct *prev, bool preempt) ++{ ++#ifdef CONFIG_SCHED_STACK_END_CHECK ++ if (task_stack_end_corrupted(prev)) ++ panic("corrupted stack end detected inside scheduler\n"); ++ ++ if (task_scs_end_corrupted(prev)) ++ panic("corrupted shadow stack detected inside scheduler\n"); ++#endif ++ ++#ifdef CONFIG_DEBUG_ATOMIC_SLEEP ++ if (!preempt && prev->__state && prev->non_block_count) { ++ printk(KERN_ERR "BUG: scheduling in a non-blocking section: %s/%d/%i\n", ++ prev->comm, prev->pid, prev->non_block_count); ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++ } ++#endif ++ ++ if (unlikely(in_atomic_preempt_off())) { ++ __schedule_bug(prev); ++ preempt_count_set(PREEMPT_DISABLED); ++ } ++ rcu_sleep_check(); ++ SCHED_WARN_ON(ct_state() == CT_STATE_USER); ++ ++ profile_hit(SCHED_PROFILING, __builtin_return_address(0)); ++ ++ schedstat_inc(this_rq()->sched_count); ++} ++ ++/* ++ * The currently running task's information is all stored in rq local data ++ * which is only modified by the local CPU. ++ */ ++static inline void set_rq_task(struct rq *rq, struct task_struct *p) ++{ ++ if (p == rq->idle || p->policy == SCHED_FIFO) ++ hrexpiry_clear(rq); ++ else ++ hrexpiry_start(rq, US_TO_NS(p->time_slice)); ++ if (rq->clock - rq->last_tick > HALF_JIFFY_NS) ++ rq->dither = 0; ++ else ++ rq->dither = rq_dither(rq); ++ ++ rq->rq_deadline = p->deadline; ++ rq->rq_prio = p->prio; ++#ifdef CONFIG_SMT_NICE ++ rq->rq_mm = p->mm; ++ rq->rq_smt_bias = p->smt_bias; ++#endif ++} ++ ++#ifdef CONFIG_SMT_NICE ++static void check_no_siblings(struct rq __maybe_unused *this_rq) {} ++static void wake_no_siblings(struct rq __maybe_unused *this_rq) {} ++static void (*check_siblings)(struct rq *this_rq) = &check_no_siblings; ++static void (*wake_siblings)(struct rq *this_rq) = &wake_no_siblings; ++ ++/* Iterate over smt siblings when we've scheduled a process on cpu and decide ++ * whether they should continue running or be descheduled. */ ++static void check_smt_siblings(struct rq *this_rq) ++{ ++ int other_cpu; ++ ++ for_each_cpu(other_cpu, &this_rq->thread_mask) { ++ struct task_struct *p; ++ struct rq *rq; ++ ++ rq = cpu_rq(other_cpu); ++ if (rq_idle(rq)) ++ continue; ++ p = rq->curr; ++ if (!smt_schedule(p, this_rq)) ++ resched_curr(rq); ++ } ++} ++ ++static void wake_smt_siblings(struct rq *this_rq) ++{ ++ int other_cpu; ++ ++ for_each_cpu(other_cpu, &this_rq->thread_mask) { ++ struct rq *rq; ++ ++ rq = cpu_rq(other_cpu); ++ if (rq_idle(rq)) ++ resched_idle(rq); ++ } ++} ++#else ++static void check_siblings(struct rq __maybe_unused *this_rq) {} ++static void wake_siblings(struct rq __maybe_unused *this_rq) {} ++#endif ++ ++/* ++ * schedule() is the main scheduler function. ++ * ++ * The main means of driving the scheduler and thus entering this function are: ++ * ++ * 1. Explicit blocking: mutex, semaphore, waitqueue, etc. ++ * ++ * 2. TIF_NEED_RESCHED flag is checked on interrupt and userspace return ++ * paths. For example, see arch/x86/entry_64.S. ++ * ++ * To drive preemption between tasks, the scheduler sets the flag in timer ++ * interrupt handler sched_tick(). ++ * ++ * 3. Wakeups don't really cause entry into schedule(). They add a ++ * task to the run-queue and that's it. ++ * ++ * Now, if the new task added to the run-queue preempts the current ++ * task, then the wakeup sets TIF_NEED_RESCHED and schedule() gets ++ * called on the nearest possible occasion: ++ * ++ * - If the kernel is preemptible (CONFIG_PREEMPTION=y): ++ * ++ * - in syscall or exception context, at the next outmost ++ * preempt_enable(). (this might be as soon as the wake_up()'s ++ * spin_unlock()!) ++ * ++ * - in IRQ context, return from interrupt-handler to ++ * preemptible context ++ * ++ * - If the kernel is not preemptible (CONFIG_PREEMPTION is not set) ++ * then at the next: ++ * ++ * - cond_resched() call ++ * - explicit schedule() call ++ * - return from syscall or exception to user-space ++ * - return from interrupt-handler to user-space ++ * ++ * WARNING: must be called with preemption disabled! ++ */ ++#define SM_IDLE (-1) ++#define SM_NONE 0 ++#define SM_PREEMPT 1 ++#define SM_RTLOCK_WAIT 2 ++ ++static void __sched notrace __schedule(int sched_mode) ++{ ++ struct task_struct *prev, *next, *idle; ++ unsigned long *switch_count; ++ unsigned long prev_state; ++ bool deactivate = false; ++ struct rq *rq; ++ u64 niffies; ++ int cpu; ++ /* ++ * On PREEMPT_RT, SM_RTLOCK_WAIT is noted as a preemption by ++ * schedule_debug() and RCU. Task-state changes still treat ++ * only SM_PREEMPT as preemption so a sleeping lock wait can ++ * deactivate. ++ */ ++ bool preempt = sched_mode > SM_NONE; ++ ++ cpu = smp_processor_id(); ++ rq = cpu_rq(cpu); ++ prev = rq->curr; ++ idle = rq->idle; ++ ++ schedule_debug(prev, preempt); ++ ++ klp_sched_try_switch(prev); ++ ++ local_irq_disable(); ++ rcu_note_context_switch(preempt); ++ ++ /* ++ * Make sure that signal_pending_state()->signal_pending() below ++ * can't be reordered with __set_current_state(TASK_INTERRUPTIBLE) ++ * done by the caller to avoid the race with signal_wake_up(): ++ * ++ * __set_current_state(@state) signal_wake_up() ++ * schedule() set_tsk_thread_flag(p, TIF_SIGPENDING) ++ * wake_up_state(p, state) ++ * LOCK rq->lock LOCK p->pi_state ++ * smp_mb__after_spinlock() smp_mb__after_spinlock() ++ * if (signal_pending_state()) if (p->__state & @state) ++ * ++ * Also, the membarrier system call requires a full memory barrier ++ * after coming from user-space, before storing to rq->curr. ++ */ ++ rq_lock(rq); ++ smp_mb__after_spinlock(); ++#ifdef CONFIG_SMP ++ if (rq->preempt) { ++ /* ++ * Make sure resched_curr hasn't triggered a preemption ++ * locklessly on a task that has since scheduled away. Spurious ++ * wakeup of idle is okay though. ++ */ ++ if (unlikely(sched_mode == SM_PREEMPT && prev != idle && ++ !test_tsk_need_resched(prev))) { ++ rq->preempt = NULL; ++ clear_preempt_need_resched(); ++ rq_unlock_irq(rq, NULL); ++ return; ++ } ++ rq->preempt = NULL; ++ } ++ migrate_disable_switch(rq, prev); ++#endif ++ ++ /* ++ * Defer hrexpiry start/cancel until we leave __schedule so we do not ++ * thrash the oneshot clockevent under the rq lock (mainline hrtick). ++ */ ++ hrexpiry_schedule_enter(rq); ++ ++ switch_count = &prev->nivcsw; ++ ++ /* Task state changes only consider SM_PREEMPT as preemption */ ++ preempt = sched_mode == SM_PREEMPT; ++ ++ /* ++ * We must load prev->__state once (task_struct::state is volatile), such ++ * that: ++ * ++ * - we form a control dependency vs deactivate_task() below. ++ * - ptrace_{,un}freeze_traced() can change ->state underneath us. ++ */ ++ prev_state = prev->__state; ++ if (!preempt && prev_state) { ++ if (signal_pending_state(prev_state, prev)) { ++ prev->__state = TASK_RUNNING; ++ } else { ++ prev->sched_contributes_to_load = ++ (prev_state & TASK_UNINTERRUPTIBLE) && ++ !(prev_state & TASK_NOLOAD) && ++ !(prev_state & TASK_FROZEN); ++ ++ if (prev->sched_contributes_to_load) ++ rq->nr_uninterruptible++; ++ ++ /* ++ * __schedule() ttwu() ++ * prev_state = prev->__state; if (p->on_rq && ...) ++ * if (prev_state) goto out; ++ * p->on_rq = 0; smp_acquire__after_ctrl_dep(); ++ * p->__state = TASK_WAKING ++ * ++ * Where __schedule() and ttwu() have matching control dependencies. ++ * ++ * After this, schedule() must not care about p->__state any more. ++ */ ++ deactivate = true; ++ ++ if (prev->in_iowait) { ++ atomic_inc(&rq->nr_iowait); ++ delayacct_blkio_start(); ++ } ++ } ++ switch_count = &prev->nvcsw; ++ } ++ ++ /* ++ * Store the niffy value here for use by the next task's last_ran ++ * below to avoid losing niffies due to update_clocks being called ++ * again after this point. ++ */ ++ update_clocks(rq); ++ niffies = rq->niffies; ++ update_cpu_clock_switch(rq, prev); ++ ++ clear_tsk_need_resched(prev); ++ clear_preempt_need_resched(); ++ ++ if (idle != prev) { ++ check_deadline(prev, rq); ++ return_task(prev, rq, cpu, deactivate); ++ } ++ ++ next = earliest_deadline_task(rq, cpu, idle); ++ if (likely(next->prio != PRIO_LIMIT)) ++ clear_cpuidle_map(cpu); ++ else { ++#ifdef CONFIG_SMP ++ if (prev != idle) ++ rq->idle_jiffy = jiffies; ++#endif ++ set_cpuidle_map(cpu); ++ update_load_avg(rq, 0); ++ } ++ ++ set_rq_task(rq, next); ++ next->last_ran = niffies; ++ ++ if (likely(prev != next)) { ++ /* ++ * Don't reschedule an idle task or deactivated tasks ++ */ ++ if (prev == idle) ++ inc_nr_running(rq); ++ else if (!deactivate) ++ resched_suitable_idle(prev); ++ /* ++ * The task on the CPU is not on the skiplist, so it holds a ++ * count of its own on top of the queued ones. nr_running only ++ * changes when that slot is created or destroyed - i.e. on the ++ * idle transitions above and below - but rt_nr_running also ++ * depends on *which* task holds it, so hand it over on every ++ * switch. Leaving that to the idle transitions leaked a count ++ * on every rt -> non-rt switch and underflowed on the reverse. ++ */ ++ if (prev != idle && rt_task(prev)) ++ rq->rt_nr_running--; ++ if (next != idle && rt_task(next)) ++ rq->rt_nr_running++; ++ if (unlikely(next == idle)) { ++ dec_nr_running(rq); ++ wake_siblings(rq); ++ } else ++ check_siblings(rq); ++ rq->nr_switches++; ++ /* ++ * RCU users of rcu_dereference(rq->curr) may not see ++ * changes to task_struct made by pick_next_task(). ++ */ ++ RCU_INIT_POINTER(rq->curr, next); ++ /* ++ * The membarrier system call requires each architecture ++ * to have a full memory barrier after updating ++ * rq->curr, before returning to user-space. ++ * ++ * Here are the schemes providing that barrier on the ++ * various architectures: ++ * - mm ? switch_mm() : mmdrop() for x86, s390, sparc, PowerPC. ++ * switch_mm() rely on membarrier_arch_switch_mm() on PowerPC. ++ * - finish_lock_switch() for weakly-ordered ++ * architectures where spin_unlock is a full barrier, ++ * - switch_to() for arm64 (weakly-ordered, spin_unlock ++ * is a RELEASE barrier), ++ */ ++ ++*switch_count; ++ ++ psi_sched_switch(prev, next, !task_on_rq_queued(prev)); ++ ++ trace_sched_switch(preempt, prev, next, prev->__state); ++ context_switch(rq, prev, next); /* unlocks the rq via finish_lock_switch */ ++ } else { ++ check_siblings(rq); ++ hrexpiry_schedule_exit(rq); ++ rq_unlock(rq); ++ local_irq_enable(); ++ } ++} ++ ++void __noreturn do_task_dead(void) ++{ ++ /* Causes final put_task_struct in finish_task_switch(). */ ++ set_special_state(TASK_DEAD); ++ ++ /* Tell freezer to ignore us: */ ++ current->flags |= PF_NOFREEZE; ++ __schedule(SM_NONE); ++ BUG(); ++ ++ /* Avoid "noreturn function does return" - but don't continue if BUG() is a NOP: */ ++ for (;;) ++ cpu_relax(); ++} ++ ++static inline void sched_submit_work(struct task_struct *tsk) ++{ ++ unsigned int task_flags; ++ ++ if (!tsk->__state) ++ return; ++ ++ task_flags = tsk->flags; ++ /* ++ * If a worker went to sleep, notify and ask workqueue whether ++ * it wants to wake up a task to maintain concurrency. ++ * As this function is called inside the schedule() context, ++ * we disable preemption to avoid it calling schedule() again ++ * in the possible wakeup of a kworker and because wq_worker_sleeping() ++ * requires it. ++ */ ++ if (task_flags & (PF_WQ_WORKER | PF_IO_WORKER)) { ++ preempt_disable(); ++ if (task_flags & PF_WQ_WORKER) ++ wq_worker_sleeping(tsk); ++ else ++ io_wq_worker_sleeping(tsk); ++ preempt_enable_no_resched(); ++ } ++ ++ if (tsk->pi_blocked_on) ++ return; ++ ++ /* ++ * If we are going to sleep and we have plugged IO queued, ++ * make sure to submit it to avoid deadlocks. ++ */ ++ blk_flush_plug(tsk->plug, true); ++} ++ ++static inline void sched_update_worker(struct task_struct *tsk) ++{ ++ if (tsk->flags & (PF_WQ_WORKER | PF_IO_WORKER)) { ++ if (tsk->flags & PF_WQ_WORKER) ++ wq_worker_running(tsk); ++ else ++ io_wq_worker_running(tsk); ++ } ++} ++ ++static __always_inline void __schedule_loop(int sched_mode) ++{ ++ do { ++ preempt_disable(); ++ __schedule(sched_mode); ++ sched_preempt_enable_no_resched(); ++ } while (need_resched()); ++} ++ ++asmlinkage __visible void __sched schedule(void) ++{ ++ struct task_struct *tsk = current; ++ ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(!tsk->sched_rt_mutex); ++#endif ++ sched_submit_work(tsk); ++ __schedule_loop(SM_NONE); ++ sched_update_worker(tsk); ++} ++ ++EXPORT_SYMBOL(schedule); ++ ++/* ++ * synchronize_rcu_tasks() makes sure that no task is stuck in preempted ++ * state (have scheduled out non-voluntarily) by making sure that all ++ * tasks have either left the run queue or have gone into user space. ++ * As idle tasks do not do either, they must not ever be preempted ++ * (schedule out non-voluntarily). ++ * ++ * schedule_idle() is similar to schedule_preempt_disable() except that it ++ * never enables preemption because it does not call sched_submit_work(). ++ */ ++void __sched schedule_idle(void) ++{ ++ /* ++ * As this skips calling sched_submit_work(), which the idle task does ++ * regardless because that function is a nop when the task is in a ++ * TASK_RUNNING state, make sure this isn't used someplace that the ++ * current task can be in any other state. Note, idle is always in the ++ * TASK_RUNNING state. ++ */ ++ WARN_ON_ONCE(current->__state); ++ do { ++ __schedule(SM_IDLE); ++ } while (need_resched()); ++} ++ ++#if defined(CONFIG_CONTEXT_TRACKING) && !defined(CONFIG_HAVE_CONTEXT_TRACKING_OFFSTACK) ++asmlinkage __visible void __sched schedule_user(void) ++{ ++ /* ++ * If we come here after a random call to set_need_resched(), ++ * or we have been woken up remotely but the IPI has not yet arrived, ++ * we haven't yet exited the RCU idle mode. Do it here manually until ++ * we find a better solution. ++ * ++ * NB: There are buggy callers of this function. Ideally we ++ * should warn if prev_state != IN_USER, but that will trigger ++ * too frequently to make sense yet. ++ */ ++ enum ctx_state prev_state = exception_enter(); ++ schedule(); ++ exception_exit(prev_state); ++} ++#endif ++ ++/** ++ * schedule_preempt_disabled - called with preemption disabled ++ * ++ * Returns with preemption disabled. Note: preempt_count must be 1 ++ */ ++void __sched schedule_preempt_disabled(void) ++{ ++ sched_preempt_enable_no_resched(); ++ schedule(); ++ preempt_disable(); ++} ++ ++#ifdef CONFIG_PREEMPT_RT ++void __sched notrace schedule_rtlock(void) ++{ ++ __schedule_loop(SM_RTLOCK_WAIT); ++} ++NOKPROBE_SYMBOL(schedule_rtlock); ++#endif ++ ++static void __sched notrace preempt_schedule_common(void) ++{ ++ do { ++ /* ++ * Because the function tracer can trace preempt_count_sub() ++ * and it also uses preempt_enable/disable_notrace(), if ++ * NEED_RESCHED is set, the preempt_enable_notrace() called ++ * by the function tracer will call this function again and ++ * cause infinite recursion. ++ * ++ * Preemption must be disabled here before the function ++ * tracer can trace. Break up preempt_disable() into two ++ * calls. One to disable preemption without fear of being ++ * traced. The other to still record the preemption latency, ++ * which can also be traced by the function tracer. ++ */ ++ preempt_disable_notrace(); ++ preempt_latency_start(1); ++ __schedule(SM_PREEMPT); ++ preempt_latency_stop(1); ++ preempt_enable_no_resched_notrace(); ++ ++ /* ++ * Check again in case we missed a preemption opportunity ++ * between schedule and now. ++ */ ++ } while (need_resched()); ++} ++ ++#ifdef CONFIG_PREEMPTION ++/* ++ * This is the entry point to schedule() from in-kernel preemption ++ * off of preempt_enable. ++ */ ++asmlinkage __visible void __sched notrace preempt_schedule(void) ++{ ++ /* ++ * If there is a non-zero preempt_count or interrupts are disabled, ++ * we do not want to preempt the current task. Just return.. ++ */ ++ if (likely(!preemptible())) ++ return; ++ ++ preempt_schedule_common(); ++} ++NOKPROBE_SYMBOL(preempt_schedule); ++EXPORT_SYMBOL(preempt_schedule); ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL ++# ifndef preempt_schedule_dynamic_enabled ++# define preempt_schedule_dynamic_enabled preempt_schedule ++# define preempt_schedule_dynamic_disabled NULL ++# endif ++DEFINE_STATIC_CALL(preempt_schedule, preempt_schedule_dynamic_enabled); ++EXPORT_STATIC_CALL_TRAMP(preempt_schedule); ++# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) ++static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_schedule); ++void __sched notrace dynamic_preempt_schedule(void) ++{ ++ if (!static_branch_unlikely(&sk_dynamic_preempt_schedule)) ++ return; ++ preempt_schedule(); ++} ++NOKPROBE_SYMBOL(dynamic_preempt_schedule); ++EXPORT_SYMBOL(dynamic_preempt_schedule); ++# endif ++#endif ++ ++ ++/** ++ * preempt_schedule_notrace - preempt_schedule called by tracing ++ * ++ * The tracing infrastructure uses preempt_enable_notrace to prevent ++ * recursion and tracing preempt enabling caused by the tracing ++ * infrastructure itself. But as tracing can happen in areas coming ++ * from userspace or just about to enter userspace, a preempt enable ++ * can occur before user_exit() is called. This will cause the scheduler ++ * to be called when the system is still in usermode. ++ * ++ * To prevent this, the preempt_enable_notrace will use this function ++ * instead of preempt_schedule() to exit user context if needed before ++ * calling the scheduler. ++ */ ++asmlinkage __visible void __sched notrace preempt_schedule_notrace(void) ++{ ++ enum ctx_state prev_ctx; ++ ++ if (likely(!preemptible())) ++ return; ++ ++ do { ++ /* ++ * Because the function tracer can trace preempt_count_sub() ++ * and it also uses preempt_enable/disable_notrace(), if ++ * NEED_RESCHED is set, the preempt_enable_notrace() called ++ * by the function tracer will call this function again and ++ * cause infinite recursion. ++ * ++ * Preemption must be disabled here before the function ++ * tracer can trace. Break up preempt_disable() into two ++ * calls. One to disable preemption without fear of being ++ * traced. The other to still record the preemption latency, ++ * which can also be traced by the function tracer. ++ */ ++ preempt_disable_notrace(); ++ preempt_latency_start(1); ++ /* ++ * Needs preempt disabled in case user_exit() is traced ++ * and the tracer calls preempt_enable_notrace() causing ++ * an infinite recursion. ++ */ ++ prev_ctx = exception_enter(); ++ __schedule(SM_PREEMPT); ++ exception_exit(prev_ctx); ++ ++ preempt_latency_stop(1); ++ preempt_enable_no_resched_notrace(); ++ } while (need_resched()); ++} ++EXPORT_SYMBOL_GPL(preempt_schedule_notrace); ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++# if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) ++# ifndef preempt_schedule_notrace_dynamic_enabled ++# define preempt_schedule_notrace_dynamic_enabled preempt_schedule_notrace ++# define preempt_schedule_notrace_dynamic_disabled NULL ++# endif ++DEFINE_STATIC_CALL(preempt_schedule_notrace, preempt_schedule_notrace_dynamic_enabled); ++EXPORT_STATIC_CALL_TRAMP(preempt_schedule_notrace); ++# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) ++static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_schedule_notrace); ++void __sched notrace dynamic_preempt_schedule_notrace(void) ++{ ++ if (!static_branch_unlikely(&sk_dynamic_preempt_schedule_notrace)) ++ return; ++ preempt_schedule_notrace(); ++} ++NOKPROBE_SYMBOL(dynamic_preempt_schedule_notrace); ++EXPORT_SYMBOL(dynamic_preempt_schedule_notrace); ++# endif ++#endif ++ ++#endif /* CONFIG_PREEMPTION */ ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++/* ++ * These aliases sit beside __cond_resched() in core.c, which MuQSS.c defines ++ * much further down; declare them here so sched_dynamic_update() can see them. ++ */ ++# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL ++# define cond_resched_dynamic_enabled __cond_resched ++# define cond_resched_dynamic_disabled ((void *)&__static_call_return0) ++# define might_resched_dynamic_enabled __cond_resched ++# define might_resched_dynamic_disabled ((void *)&__static_call_return0) ++# endif ++#endif ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++ ++# ifdef CONFIG_GENERIC_IRQ_ENTRY ++# include ++# endif ++ ++/* ++ * SC:cond_resched ++ * SC:might_resched ++ * SC:preempt_schedule ++ * SC:preempt_schedule_notrace ++ * SC:irqentry_exit_cond_resched ++ * ++ * ++ * NONE: ++ * cond_resched <- __cond_resched ++ * might_resched <- RET0 ++ * preempt_schedule <- NOP ++ * preempt_schedule_notrace <- NOP ++ * irqentry_exit_cond_resched <- NOP ++ * dynamic_preempt_lazy <- false ++ * ++ * VOLUNTARY: ++ * cond_resched <- __cond_resched ++ * might_resched <- __cond_resched ++ * preempt_schedule <- NOP ++ * preempt_schedule_notrace <- NOP ++ * irqentry_exit_cond_resched <- NOP ++ * dynamic_preempt_lazy <- false ++ * ++ * FULL: ++ * cond_resched <- RET0 ++ * might_resched <- RET0 ++ * preempt_schedule <- preempt_schedule ++ * preempt_schedule_notrace <- preempt_schedule_notrace ++ * irqentry_exit_cond_resched <- irqentry_exit_cond_resched ++ * dynamic_preempt_lazy <- false ++ * ++ * LAZY: ++ * cond_resched <- RET0 ++ * might_resched <- RET0 ++ * preempt_schedule <- preempt_schedule ++ * preempt_schedule_notrace <- preempt_schedule_notrace ++ * irqentry_exit_cond_resched <- irqentry_exit_cond_resched ++ * dynamic_preempt_lazy <- true ++ */ ++ ++enum { ++ preempt_dynamic_undefined = -1, ++ preempt_dynamic_none, ++ preempt_dynamic_voluntary, ++ preempt_dynamic_full, ++ preempt_dynamic_lazy, ++}; ++ ++int preempt_dynamic_mode = preempt_dynamic_undefined; ++ ++int sched_dynamic_mode(const char *str) ++{ ++# if !(defined(CONFIG_PREEMPT_RT) || defined(CONFIG_ARCH_HAS_PREEMPT_LAZY)) ++ if (!strcmp(str, "none")) ++ return preempt_dynamic_none; ++ ++ if (!strcmp(str, "voluntary")) ++ return preempt_dynamic_voluntary; ++# endif ++ ++ if (!strcmp(str, "full")) ++ return preempt_dynamic_full; ++ ++# ifdef CONFIG_ARCH_HAS_PREEMPT_LAZY ++ if (!strcmp(str, "lazy")) ++ return preempt_dynamic_lazy; ++# endif ++ ++ return -EINVAL; ++} ++ ++# define preempt_dynamic_key_enable(f) static_key_enable(&sk_dynamic_##f.key) ++# define preempt_dynamic_key_disable(f) static_key_disable(&sk_dynamic_##f.key) ++ ++# if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) ++# define preempt_dynamic_enable(f) static_call_update(f, f##_dynamic_enabled) ++# define preempt_dynamic_disable(f) static_call_update(f, f##_dynamic_disabled) ++# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) ++# define preempt_dynamic_enable(f) preempt_dynamic_key_enable(f) ++# define preempt_dynamic_disable(f) preempt_dynamic_key_disable(f) ++# else ++# error "Unsupported PREEMPT_DYNAMIC mechanism" ++# endif ++ ++static DEFINE_MUTEX(sched_dynamic_mutex); ++ ++static void __sched_dynamic_update(int mode) ++{ ++ /* ++ * Avoid {NONE,VOLUNTARY} -> FULL transitions from ever ending up in ++ * the ZERO state, which is invalid. ++ */ ++ preempt_dynamic_enable(cond_resched); ++ preempt_dynamic_enable(might_resched); ++ preempt_dynamic_enable(preempt_schedule); ++ preempt_dynamic_enable(preempt_schedule_notrace); ++ preempt_dynamic_enable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_disable(preempt_lazy); ++ ++ switch (mode) { ++ case preempt_dynamic_none: ++ preempt_dynamic_enable(cond_resched); ++ preempt_dynamic_disable(might_resched); ++ preempt_dynamic_disable(preempt_schedule); ++ preempt_dynamic_disable(preempt_schedule_notrace); ++ preempt_dynamic_disable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_disable(preempt_lazy); ++ if (mode != preempt_dynamic_mode) ++ pr_info("Dynamic Preempt: none\n"); ++ break; ++ ++ case preempt_dynamic_voluntary: ++ preempt_dynamic_enable(cond_resched); ++ preempt_dynamic_enable(might_resched); ++ preempt_dynamic_disable(preempt_schedule); ++ preempt_dynamic_disable(preempt_schedule_notrace); ++ preempt_dynamic_disable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_disable(preempt_lazy); ++ if (mode != preempt_dynamic_mode) ++ pr_info("Dynamic Preempt: voluntary\n"); ++ break; ++ ++ case preempt_dynamic_full: ++ preempt_dynamic_disable(cond_resched); ++ preempt_dynamic_disable(might_resched); ++ preempt_dynamic_enable(preempt_schedule); ++ preempt_dynamic_enable(preempt_schedule_notrace); ++ preempt_dynamic_enable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_disable(preempt_lazy); ++ if (mode != preempt_dynamic_mode) ++ pr_info("Dynamic Preempt: full\n"); ++ break; ++ ++ case preempt_dynamic_lazy: ++ preempt_dynamic_disable(cond_resched); ++ preempt_dynamic_disable(might_resched); ++ preempt_dynamic_enable(preempt_schedule); ++ preempt_dynamic_enable(preempt_schedule_notrace); ++ preempt_dynamic_enable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_enable(preempt_lazy); ++ if (mode != preempt_dynamic_mode) ++ pr_info("Dynamic Preempt: lazy\n"); ++ break; ++ } ++ ++ WRITE_ONCE(preempt_dynamic_mode, mode); ++} ++ ++void sched_dynamic_update(int mode) ++{ ++ mutex_lock(&sched_dynamic_mutex); ++ __sched_dynamic_update(mode); ++ mutex_unlock(&sched_dynamic_mutex); ++} ++ ++static int __init setup_preempt_mode(char *str) ++{ ++ int mode = sched_dynamic_mode(str); ++ if (mode < 0) { ++ pr_warn("Dynamic Preempt: unsupported mode: %s\n", str); ++ return 0; ++ } ++ ++ sched_dynamic_update(mode); ++ return 1; ++} ++__setup("preempt=", setup_preempt_mode); ++ ++static void __init preempt_dynamic_init(void) ++{ ++ if (preempt_dynamic_mode == preempt_dynamic_undefined) { ++ if (IS_ENABLED(CONFIG_PREEMPT_NONE)) { ++ sched_dynamic_update(preempt_dynamic_none); ++ } else if (IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY)) { ++ sched_dynamic_update(preempt_dynamic_voluntary); ++ } else if (IS_ENABLED(CONFIG_PREEMPT_LAZY)) { ++ sched_dynamic_update(preempt_dynamic_lazy); ++ } else { ++ /* Default static call setting, nothing to do */ ++ WARN_ON_ONCE(!IS_ENABLED(CONFIG_PREEMPT)); ++ preempt_dynamic_mode = preempt_dynamic_full; ++ pr_info("Dynamic Preempt: full\n"); ++ } ++ } ++} ++ ++# define PREEMPT_MODEL_ACCESSOR(mode) \ ++ bool preempt_model_##mode(void) \ ++ { \ ++ int mode = READ_ONCE(preempt_dynamic_mode); \ ++ WARN_ON_ONCE(mode == preempt_dynamic_undefined); \ ++ return mode == preempt_dynamic_##mode; \ ++ } \ ++ EXPORT_SYMBOL_GPL(preempt_model_##mode) ++ ++PREEMPT_MODEL_ACCESSOR(none); ++PREEMPT_MODEL_ACCESSOR(voluntary); ++PREEMPT_MODEL_ACCESSOR(full); ++PREEMPT_MODEL_ACCESSOR(lazy); ++ ++#else /* !CONFIG_PREEMPT_DYNAMIC: */ ++ ++#define preempt_dynamic_mode -1 ++ ++static inline void preempt_dynamic_init(void) { } ++ ++#endif /* CONFIG_PREEMPT_DYNAMIC */ ++ ++const char *preempt_modes[] = { ++ "none", "voluntary", "full", "lazy", NULL, ++}; ++/* ++ * This is the entry point to schedule() from kernel preemption ++ * off of irq context. ++ * Note, that this is called and return with irqs disabled. This will ++ * protect us against recursive calling from irq. ++ */ ++asmlinkage __visible void __sched preempt_schedule_irq(void) ++{ ++ enum ctx_state prev_state; ++ ++ /* Catch callers which need to be fixed */ ++ BUG_ON(preempt_count() || !irqs_disabled()); ++ ++ prev_state = exception_enter(); ++ ++ do { ++ preempt_disable(); ++ local_irq_enable(); ++ __schedule(SM_PREEMPT); ++ local_irq_disable(); ++ sched_preempt_enable_no_resched(); ++ } while (need_resched()); ++ ++ exception_exit(prev_state); ++} ++ ++int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags, ++ void *key) ++{ ++ WARN_ON_ONCE(IS_ENABLED(CONFIG_SCHED_DEBUG) && wake_flags & ~WF_SYNC); ++ return try_to_wake_up(curr->private, mode, wake_flags); ++} ++EXPORT_SYMBOL(default_wake_function); ++ ++#ifdef CONFIG_RT_MUTEXES ++ ++static inline int __rt_effective_prio(struct task_struct *pi_task, int prio) ++{ ++ if (pi_task) ++ prio = min(prio, pi_task->prio); ++ ++ return prio; ++} ++ ++static inline int rt_effective_prio(struct task_struct *p, int prio) ++{ ++ struct task_struct *pi_task = rt_mutex_get_top_task(p); ++ ++ return __rt_effective_prio(pi_task, prio); ++} ++ ++/* ++ * rt_mutex_setprio - set the current priority of a task ++ * @p: task to boost ++ * @pi_task: donor task ++ * ++ * This function changes the 'effective' priority of a task. It does ++ * not touch ->normal_prio like __setscheduler(). ++ * ++ * Used by the rt_mutex code to implement priority inheritance ++ * logic. Call site only calls if the priority of the task changed. ++ */ ++void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task) ++{ ++ int prio, oldprio; ++ struct rq *rq; ++ ++ /* XXX used to be waiter->prio, not waiter->task->prio */ ++ prio = __rt_effective_prio(pi_task, p->normal_prio); ++ ++ /* ++ * If nothing changed; bail early. ++ */ ++ if (p->pi_top_task == pi_task && prio == p->prio) ++ return; ++ ++ rq = __task_rq_lock(p, NULL); ++ update_rq_clock(rq); ++ /* ++ * Set under pi_lock && rq->lock, such that the value can be used under ++ * either lock. ++ * ++ * Note that there is loads of tricky to make this pointer cache work ++ * right. rt_mutex_slowunlock()+rt_mutex_postunlock() work together to ++ * ensure a task is de-boosted (pi_task is set to NULL) before the ++ * task is allowed to run again (and can exit). This ensures the pointer ++ * points to a blocked task -- which guarantees the task is present. ++ */ ++ p->pi_top_task = pi_task; ++ ++ /* ++ * For FIFO/RR we only need to set prio, if that matches we're done. ++ */ ++ if (prio == p->prio) ++ goto out_unlock; ++ ++ /* ++ * Idle task boosting is a nono in general. There is one ++ * exception, when PREEMPT_RT and NOHZ is active: ++ * ++ * The idle task calls get_next_timer_interrupt() and holds ++ * the timer wheel base->lock on the CPU and another CPU wants ++ * to access the timer (probably to cancel it). We can safely ++ * ignore the boosting request, as the idle CPU runs this code ++ * with interrupts disabled and will complete the lock ++ * protected section without being interrupted. So there is no ++ * real need to boost. ++ */ ++ if (unlikely(p == rq->idle)) { ++ WARN_ON(p != rq->curr); ++ WARN_ON(p->pi_blocked_on); ++ goto out_unlock; ++ } ++ ++ trace_sched_pi_setprio(p, pi_task); ++ oldprio = p->prio; ++ p->prio = prio; ++ if (task_running(rq, p)){ ++ rt_running_reprio(rq, oldprio, prio); ++ if (prio > oldprio) ++ resched_task(p); ++ } else if (task_queued(p)) { ++ dequeue_task(rq, p, DEQUEUE_SAVE); ++ enqueue_task(rq, p, ENQUEUE_RESTORE); ++ if (prio < oldprio) ++ try_preempt(p, rq); ++ } ++out_unlock: ++ /* Avoid rq from going away on us: */ ++ preempt_disable(); ++ __task_rq_unlock(rq, p, NULL); ++ ++ preempt_enable(); ++} ++#else ++static inline int rt_effective_prio(struct task_struct *p, int prio) ++{ ++ return prio; ++} ++#endif ++ ++/* ++ * Adjust the deadline for when the priority is to change, before it's ++ * changed. ++ */ ++static inline void adjust_deadline(struct task_struct *p, int new_prio) ++{ ++ p->deadline += static_deadline_diff(new_prio) - task_deadline_diff(p); ++} ++ ++void set_user_nice(struct task_struct *p, long nice) ++{ ++ int new_static, old_static; ++ struct rq_flags rf; ++ struct rq *rq; ++ ++ if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE) ++ return; ++ new_static = NICE_TO_PRIO(nice); ++ /* ++ * We have to be careful, if called from sys_setpriority(), ++ * the task might be in the middle of scheduling on another CPU. ++ */ ++ rq = task_rq_lock(p, &rf); ++ update_rq_clock(rq); ++ ++ /* ++ * The RT priorities are set via sched_setscheduler(), but we still ++ * allow the 'normal' nice value to be set - but as expected ++ * it won't have any effect on scheduling until the task is ++ * not SCHED_NORMAL/SCHED_BATCH: ++ */ ++ if (has_rt_policy(p)) { ++ p->static_prio = new_static; ++ goto out_unlock; ++ } ++ ++ adjust_deadline(p, new_static); ++ old_static = p->static_prio; ++ p->static_prio = new_static; ++ p->prio = effective_prio(p); ++ ++ if (task_queued(p)) { ++ dequeue_task(rq, p, DEQUEUE_SAVE); ++ enqueue_task(rq, p, ENQUEUE_RESTORE); ++ if (new_static < old_static) ++ try_preempt(p, rq); ++ } else if (task_running(rq, p)) { ++ set_rq_task(rq, p); ++ if (old_static < new_static) ++ resched_task(p); ++ } ++out_unlock: ++ task_rq_unlock(rq, p, &rf); ++} ++EXPORT_SYMBOL(set_user_nice); ++ ++/* ++ * can_nice - check if a task can reduce its nice value ++ * @p: task ++ * @nice: nice value ++ */ ++int can_nice(const struct task_struct *p, const int nice) ++{ ++ /* Convert nice value [19,-20] to rlimit style value [1,40] */ ++ int nice_rlim = nice_to_rlimit(nice); ++ ++ return (nice_rlim <= task_rlimit(p, RLIMIT_NICE) || ++ capable(CAP_SYS_NICE)); ++} ++ ++#ifdef __ARCH_WANT_SYS_NICE ++ ++/* ++ * sys_nice - change the priority of the current process. ++ * @increment: priority increment ++ * ++ * sys_setpriority is a more generic, but much slower function that ++ * does similar things. ++ */ ++SYSCALL_DEFINE1(nice, int, increment) ++{ ++ long nice, retval; ++ ++ /* ++ * Setpriority might change our priority at the same moment. ++ * We don't have to worry. Conceptually one call occurs first ++ * and we have a single winner. ++ */ ++ ++ increment = clamp(increment, -NICE_WIDTH, NICE_WIDTH); ++ nice = task_nice(current) + increment; ++ ++ nice = clamp_val(nice, MIN_NICE, MAX_NICE); ++ if (increment < 0 && !can_nice(current, nice)) ++ return -EPERM; ++ ++ retval = security_task_setnice(current, nice); ++ if (retval) ++ return retval; ++ ++ set_user_nice(current, nice); ++ return 0; ++} ++ ++#endif ++ ++/** ++ * task_prio - return the priority value of a given task. ++ * @p: the task in question. ++ * ++ * Return: The priority value as seen by users in /proc. ++ * ++ * sched policy return value kernel prio user prio/nice ++ * ++ * normal, batch, [1 ... 41] 101 0/[-20 ... 19] ++ * idle [42 ... 81] 102 0/[-20 ... 19] ++ * iso [0 ... 41] 100 0/[-20 ... 19] ++ * fifo, rr [-2 ... -100] [98 ... 0] [1 ... 99] ++ */ ++int task_prio(const struct task_struct *p) ++{ ++ int delta, prio = p->prio - MAX_RT_PRIO; ++ ++ /* rt tasks and iso tasks */ ++ if (prio <= 0) ++ goto out; ++ ++ /* Convert to ms to avoid overflows */ ++ delta = NS_TO_MS(p->deadline - task_rq(p)->niffies); ++ if (unlikely(delta < 0)) ++ delta = 0; ++ delta = delta * 40 / ms_longest_deadline_diff(); ++ if (delta <= 80) ++ prio += delta; ++ if (idleprio_task(p)) ++ prio += 40; ++out: ++ return prio; ++} ++ ++#ifdef CONFIG_SMP ++static inline bool rt_rq_is_runnable(struct rq *rt_rq) ++{ ++ return rt_rq->rt_nr_running; ++} ++ ++/* ++ * This function computes an effective utilization for the given CPU, to be ++ * used for frequency selection given the linear relation: f = u * f_max. ++ * ++ * The scheduler tracks the following metrics: ++ * ++ * cpu_util_{cfs,rt,dl,irq}() ++ * cpu_bw_dl() ++ * ++ * Where the cfs,rt and dl util numbers are tracked with the same metric and ++ * synchronized windows and are thus directly comparable. ++ * ++ * The cfs,rt,dl utilization are the running times measured with rq->clock_task ++ * which excludes things like IRQ and steal-time. These latter are then accrued ++ * in the irq utilization. ++ * ++ * The DL bandwidth number otoh is not a measured metric but a value computed ++ * based on the task model parameters and gives the minimal utilization ++ * required to meet deadlines. ++ */ ++unsigned long effective_cpu_util(int cpu, unsigned long util_cfs, ++ unsigned long *min, unsigned long *max) ++{ ++ unsigned long util, irq, scale; ++ struct rq *rq = cpu_rq(cpu); ++ ++ scale = arch_scale_cpu_capacity(cpu); ++ ++ /* ++ * Early check to see if IRQ/steal time saturates the CPU, can be ++ * because of inaccuracies in how we track these. ++ */ ++ irq = cpu_util_irq(rq); ++ if (unlikely(irq >= scale)) { ++ if (min) ++ *min = scale; ++ if (max) ++ *max = scale; ++ return scale; ++ } ++ ++ /* ++ * MuQSS has no utilisation clamping and no deadline bandwidth, so the ++ * usable range is simply the whole capacity. A runnable realtime task ++ * asks for the maximum. ++ */ ++ if (min) ++ *min = rt_rq_is_runnable(rq) ? scale : 0; ++ if (max) ++ *max = scale; ++ ++ util = util_cfs + cpu_util_rt(rq); ++ util = scale_irq_capacity(util, irq, scale); ++ ++ return min_t(unsigned long, scale, util); ++} ++ ++/* ++ * Available idle CPU capacity, used by the energy model. MuQSS does not ++ * implement energy aware scheduling, but the interface is still called. ++ */ ++unsigned long sched_cpu_util(int cpu) ++{ ++ unsigned long min, max; ++ ++ return effective_cpu_util(cpu, cpu_util_cfs(cpu_rq(cpu)), &min, &max); ++} ++#endif /* CONFIG_SMP */ ++ ++/** ++ * idle_cpu - is a given CPU idle currently? ++ * @cpu: the processor in question. ++ * ++ * Return: 1 if the CPU is currently idle. 0 otherwise. ++ */ ++int idle_cpu(int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ ++ if (rq->curr != rq->idle) ++ return 0; ++ ++ if (rq->nr_running) ++ return 0; ++ ++#ifdef CONFIG_SMP ++ if (rq->ttwu_pending) ++ return 0; ++#endif ++ ++ return 1; ++} ++ ++/** ++ * available_idle_cpu - is a given CPU idle for enqueuing work. ++ * @cpu: the CPU in question. ++ * ++ * Return: 1 if the CPU is currently idle. 0 otherwise. ++ */ ++bool available_idle_cpu(int cpu) ++{ ++ if (!idle_cpu(cpu)) ++ return 0; ++ ++ if (vcpu_is_preempted(cpu)) ++ return 0; ++ ++ return 1; ++} ++ ++/** ++ * idle_task - return the idle task for a given CPU. ++ * @cpu: the processor in question. ++ * ++ * Return: The idle task for the CPU @cpu. ++ */ ++struct task_struct *idle_task(int cpu) ++{ ++ return cpu_rq(cpu)->idle; ++} ++ ++/** ++ * find_process_by_pid - find a process with a matching PID value. ++ * @pid: the pid in question. ++ * ++ * The task of @pid, if found. %NULL otherwise. ++ */ ++static inline struct task_struct *find_process_by_pid(pid_t pid) ++{ ++ return pid ? find_task_by_vpid(pid) : current; ++} ++ ++/* Actually do priority change: must hold rq lock. */ ++static void __setscheduler(struct task_struct *p, struct rq *rq, int policy, ++ int prio, const struct sched_attr *attr, ++ bool keep_boost) ++{ ++ int oldrtprio, oldprio; ++ ++ /* ++ * If params can't change scheduling class changes aren't allowed ++ * either. ++ */ ++ if (attr->sched_flags & SCHED_FLAG_KEEP_PARAMS) ++ return; ++ ++ p->policy = policy; ++ oldrtprio = p->rt_priority; ++ p->rt_priority = prio; ++ ++ /* rt-policy tasks do not have a timerslack */ ++ if (has_rt_policy(p)) { ++ p->timer_slack_ns = 0; ++ } else if (p->timer_slack_ns == 0) { ++ /* when switching back to non-rt policy, restore timerslack */ ++ p->timer_slack_ns = p->default_timer_slack_ns; ++ } ++ ++ p->normal_prio = normal_prio(p); ++ oldprio = p->prio; ++ /* ++ * Keep a potential priority boosting if called from ++ * sched_setscheduler(). ++ */ ++ p->prio = normal_prio(p); ++ if (keep_boost) ++ p->prio = rt_effective_prio(p, p->prio); ++ ++ if (task_running(rq, p)) { ++ rt_running_reprio(rq, oldprio, p->prio); ++ set_rq_task(rq, p); ++ resched_task(p); ++ } else if (task_queued(p)) { ++ dequeue_task(rq, p, DEQUEUE_SAVE); ++ enqueue_task(rq, p, ENQUEUE_RESTORE); ++ if (p->prio < oldprio || p->rt_priority > oldrtprio) ++ try_preempt(p, rq); ++ } ++} ++ ++/* ++ * Check the target process has a UID that matches the current process's ++ */ ++static bool check_same_owner(struct task_struct *p) ++{ ++ const struct cred *cred = current_cred(), *pcred; ++ bool match; ++ ++ rcu_read_lock(); ++ pcred = __task_cred(p); ++ match = (uid_eq(cred->euid, pcred->euid) || ++ uid_eq(cred->euid, pcred->uid)); ++ rcu_read_unlock(); ++ return match; ++} ++ ++static int __sched_setscheduler(struct task_struct *p, ++ const struct sched_attr *attr, ++ bool user, bool pi) ++{ ++ int retval, policy = attr->sched_policy, oldpolicy = -1, priority = attr->sched_priority; ++ unsigned long rlim_rtprio = 0; ++ struct rq_flags rf; ++ int reset_on_fork; ++ struct rq *rq; ++ ++ /* The pi code expects interrupts enabled */ ++ BUG_ON(pi && in_interrupt()); ++ ++ if (is_rt_policy(policy) && !capable(CAP_SYS_NICE)) { ++ unsigned long lflags; ++ ++ if (!lock_task_sighand(p, &lflags)) ++ return -ESRCH; ++ rlim_rtprio = task_rlimit(p, RLIMIT_RTPRIO); ++ unlock_task_sighand(p, &lflags); ++ if (rlim_rtprio) ++ goto recheck; ++ /* ++ * If the caller requested an RT policy without having the ++ * necessary rights, we downgrade the policy to SCHED_ISO. ++ * We also set the parameter to zero to pass the checks. ++ */ ++ policy = SCHED_ISO; ++ priority = 0; ++ } ++recheck: ++ /* Double check policy once rq lock held */ ++ if (policy < 0) { ++ reset_on_fork = p->sched_reset_on_fork; ++ policy = oldpolicy = p->policy; ++ } else { ++ reset_on_fork = !!(policy & SCHED_RESET_ON_FORK); ++ policy &= ~SCHED_RESET_ON_FORK; ++ ++ if (!SCHED_RANGE(policy)) ++ return -EINVAL; ++ } ++ ++ if (attr->sched_flags & ~(SCHED_FLAG_ALL | SCHED_FLAG_SUGOV)) ++ return -EINVAL; ++ ++ /* ++ * Valid priorities for SCHED_FIFO and SCHED_RR are ++ * 1..MAX_RT_PRIO-1, valid priority for SCHED_NORMAL and ++ * SCHED_BATCH is 0. ++ */ ++ if (priority > MAX_RT_PRIO-1) ++ return -EINVAL; ++ if (is_rt_policy(policy) != (priority != 0)) ++ return -EINVAL; ++ ++ /* ++ * Allow unprivileged RT tasks to decrease priority: ++ */ ++ if (user && !capable(CAP_SYS_NICE)) { ++ if (is_rt_policy(policy)) { ++ unsigned long rlim_rtprio = ++ task_rlimit(p, RLIMIT_RTPRIO); ++ ++ /* Can't set/change the rt policy */ ++ if (policy != p->policy && !rlim_rtprio) ++ return -EPERM; ++ ++ /* Can't increase priority */ ++ if (priority > p->rt_priority && ++ priority > rlim_rtprio) ++ return -EPERM; ++ } else { ++ switch (p->policy) { ++ /* ++ * Can only downgrade policies but not back to ++ * SCHED_NORMAL ++ */ ++ case SCHED_ISO: ++ if (policy == SCHED_ISO) ++ goto out; ++ if (policy != SCHED_NORMAL) ++ return -EPERM; ++ break; ++ case SCHED_BATCH: ++ if (policy == SCHED_BATCH) ++ goto out; ++ if (policy != SCHED_IDLEPRIO) ++ return -EPERM; ++ break; ++ case SCHED_IDLEPRIO: ++ if (policy == SCHED_IDLEPRIO) ++ goto out; ++ return -EPERM; ++ default: ++ break; ++ } ++ } ++ ++ /* Can't change other user's priorities */ ++ if (!check_same_owner(p)) ++ return -EPERM; ++ ++ /* Normal users shall not reset the sched_reset_on_fork flag: */ ++ if (p->sched_reset_on_fork && !reset_on_fork) ++ return -EPERM; ++ } ++ ++ if (user) { ++ retval = security_task_setscheduler(p); ++ if (retval) ++ return retval; ++ } ++ ++ if (pi) ++ cpuset_lock(); ++ ++ /* ++ * Make sure no PI-waiters arrive (or leave) while we are ++ * changing the priority of the task: ++ * ++ * To be able to change p->policy safely, the runqueue lock must be ++ * held. ++ */ ++ rq = task_rq_lock(p, &rf); ++ update_rq_clock(rq); ++ ++ /* ++ * Changing the policy of the stop threads its a very bad idea: ++ */ ++ if (p == rq->stop) { ++ retval = -EINVAL; ++ goto unlock; ++ } ++ ++ /* ++ * If not changing anything there's no need to proceed further, ++ * but store a possible modification of reset_on_fork. ++ */ ++ if (unlikely(policy == p->policy && (!is_rt_policy(policy) || ++ priority == p->rt_priority))) { ++ p->sched_reset_on_fork = reset_on_fork; ++ retval = 0; ++ goto unlock; ++ } ++ ++ /* Re-check policy now with rq lock held */ ++ if (unlikely(oldpolicy != -1 && oldpolicy != p->policy)) { ++ policy = oldpolicy = -1; ++ task_rq_unlock(rq, p, &rf); ++ if (pi) ++ cpuset_unlock(); ++ goto recheck; ++ } ++ p->sched_reset_on_fork = reset_on_fork; ++ ++ __setscheduler(p, rq, policy, priority, attr, pi); ++ ++ /* Avoid rq from going away on us: */ ++ preempt_disable(); ++ task_rq_unlock(rq, p, &rf); ++ ++ if (pi) { ++ cpuset_unlock(); ++ rt_mutex_adjust_pi(p); ++ } ++ preempt_enable(); ++out: ++ return 0; ++ ++unlock: ++ task_rq_unlock(rq, p, &rf); ++ if (pi) ++ cpuset_unlock(); ++ return retval; ++} ++ ++static int _sched_setscheduler(struct task_struct *p, int policy, ++ const struct sched_param *param, bool check) ++{ ++ struct sched_attr attr = { ++ .sched_policy = policy, ++ .sched_priority = param->sched_priority, ++ .sched_nice = PRIO_TO_NICE(p->static_prio), ++ }; ++ ++ return __sched_setscheduler(p, &attr, check, true); ++} ++/** ++ * sched_setscheduler - change the scheduling policy and/or RT priority of a thread. ++ * @p: the task in question. ++ * @policy: new policy. ++ * @param: structure containing the new RT priority. ++ * ++ * Use sched_set_fifo(), read its comment. ++ * ++ * Return: 0 on success. An error code otherwise. ++ * ++ * NOTE that the task may be already dead. ++ */ ++int sched_setscheduler(struct task_struct *p, int policy, ++ const struct sched_param *param) ++{ ++ return _sched_setscheduler(p, policy, param, true); ++} ++ ++ ++int sched_setattr(struct task_struct *p, const struct sched_attr *attr) ++{ ++ return __sched_setscheduler(p, attr, true, true); ++} ++ ++int sched_setattr_nocheck(struct task_struct *p, const struct sched_attr *attr) ++{ ++ return __sched_setscheduler(p, attr, false, true); ++} ++ ++/** ++ * sched_setscheduler_nocheck - change the scheduling policy and/or RT priority of a thread from kernelspace. ++ * @p: the task in question. ++ * @policy: new policy. ++ * @param: structure containing the new RT priority. ++ * ++ * Just like sched_setscheduler, only don't bother checking if the ++ * current context has permission. For example, this is needed in ++ * stop_machine(): we create temporary high priority worker threads, ++ * but our caller might not have that capability. ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++int sched_setscheduler_nocheck(struct task_struct *p, int policy, ++ const struct sched_param *param) ++{ ++ return _sched_setscheduler(p, policy, param, false); ++} ++ ++/* ++ * SCHED_FIFO is a broken scheduler model; that is, it is fundamentally ++ * incapable of resource management, which is the one thing an OS really should ++ * be doing. ++ * ++ * This is of course the reason it is limited to privileged users only. ++ * ++ * Worse still; it is fundamentally impossible to compose static priority ++ * workloads. You cannot take two correctly working static prio workloads ++ * and smash them together and still expect them to work. ++ * ++ * For this reason 'all' FIFO tasks the kernel creates are basically at: ++ * ++ * MAX_RT_PRIO / 2 ++ * ++ * The administrator _MUST_ configure the system, the kernel simply doesn't ++ * know enough information to make a sensible choice. ++ */ ++void sched_set_fifo(struct task_struct *p) ++{ ++ struct sched_param sp = { .sched_priority = MAX_RT_PRIO / 2 }; ++ WARN_ON_ONCE(sched_setscheduler_nocheck(p, SCHED_FIFO, &sp) != 0); ++} ++EXPORT_SYMBOL_GPL(sched_set_fifo); ++ ++/* ++ * For when you don't much care about FIFO, but want to be above SCHED_NORMAL. ++ */ ++void sched_set_fifo_low(struct task_struct *p) ++{ ++ struct sched_param sp = { .sched_priority = 1 }; ++ WARN_ON_ONCE(sched_setscheduler_nocheck(p, SCHED_FIFO, &sp) != 0); ++} ++EXPORT_SYMBOL_GPL(sched_set_fifo_low); ++ ++void sched_set_normal(struct task_struct *p, int nice) ++{ ++ struct sched_attr attr = { ++ .sched_policy = SCHED_NORMAL, ++ .sched_nice = nice, ++ }; ++ WARN_ON_ONCE(sched_setattr_nocheck(p, &attr) != 0); ++} ++EXPORT_SYMBOL_GPL(sched_set_normal); ++ ++static int ++do_sched_setscheduler(pid_t pid, int policy, struct sched_param __user *param) ++{ ++ struct sched_param lparam; ++ struct task_struct *p; ++ int retval; ++ ++ if (!param || pid < 0) ++ return -EINVAL; ++ if (copy_from_user(&lparam, param, sizeof(struct sched_param))) ++ return -EFAULT; ++ ++ rcu_read_lock(); ++ retval = -ESRCH; ++ p = find_process_by_pid(pid); ++ if (likely(p)) ++ get_task_struct(p); ++ rcu_read_unlock(); ++ ++ if (likely(p)) { ++ retval = sched_setscheduler(p, policy, &lparam); ++ put_task_struct(p); ++ } ++ ++ return retval; ++} ++ ++/* ++ * Mimics kernel/events/core.c perf_copy_attr(). ++ */ ++static int sched_copy_attr(struct sched_attr __user *uattr, ++ struct sched_attr *attr) ++{ ++ u32 size; ++ int ret; ++ ++ /* Zero the full structure, so that a short copy will be nice: */ ++ memset(attr, 0, sizeof(*attr)); ++ ++ ret = get_user(size, &uattr->size); ++ if (ret) ++ return ret; ++ ++ /* ABI compatibility quirk: */ ++ if (!size) ++ size = SCHED_ATTR_SIZE_VER0; ++ ++ if (size < SCHED_ATTR_SIZE_VER0 || size > PAGE_SIZE) ++ goto err_size; ++ ++ ret = copy_struct_from_user(attr, sizeof(*attr), uattr, size); ++ if (ret) { ++ if (ret == -E2BIG) ++ goto err_size; ++ return ret; ++ } ++ ++ /* ++ * XXX: Do we want to be lenient like existing syscalls; or do we want ++ * to be strict and return an error on out-of-bounds values? ++ */ ++ attr->sched_nice = clamp(attr->sched_nice, -20, 19); ++ ++ /* sched/core.c uses zero here but we already know ret is zero */ ++ return 0; ++ ++err_size: ++ put_user(sizeof(*attr), &uattr->size); ++ return -E2BIG; ++} ++ ++/* ++ * sched_setparam() passes in -1 for its policy, to let the functions ++ * it calls know not to change it. ++ */ ++#define SETPARAM_POLICY -1 ++ ++/** ++ * sys_sched_setscheduler - set/change the scheduler policy and RT priority ++ * @pid: the pid in question. ++ * @policy: new policy. ++ * @param: structure containing the new RT priority. ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++SYSCALL_DEFINE3(sched_setscheduler, pid_t, pid, int, policy, struct sched_param __user *, param) ++{ ++ if (policy < 0) ++ return -EINVAL; ++ ++ return do_sched_setscheduler(pid, policy, param); ++} ++ ++/** ++ * sys_sched_setparam - set/change the RT priority of a thread ++ * @pid: the pid in question. ++ * @param: structure containing the new RT priority. ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++SYSCALL_DEFINE2(sched_setparam, pid_t, pid, struct sched_param __user *, param) ++{ ++ return do_sched_setscheduler(pid, SETPARAM_POLICY, param); ++} ++ ++/** ++ * sys_sched_setattr - same as above, but with extended sched_attr ++ * @pid: the pid in question. ++ * @uattr: structure containing the extended parameters. ++ */ ++SYSCALL_DEFINE3(sched_setattr, pid_t, pid, struct sched_attr __user *, uattr, ++ unsigned int, flags) ++{ ++ struct sched_attr attr; ++ struct task_struct *p; ++ int retval; ++ ++ if (!uattr || pid < 0 || flags) ++ return -EINVAL; ++ ++ retval = sched_copy_attr(uattr, &attr); ++ if (retval) ++ return retval; ++ ++ if ((int)attr.sched_policy < 0) ++ return -EINVAL; ++ if (attr.sched_flags & SCHED_FLAG_KEEP_POLICY) ++ attr.sched_policy = SETPARAM_POLICY; ++ ++ rcu_read_lock(); ++ retval = -ESRCH; ++ p = find_process_by_pid(pid); ++ if (likely(p)) ++ get_task_struct(p); ++ rcu_read_unlock(); ++ ++ if (likely(p)) { ++ retval = sched_setattr(p, &attr); ++ put_task_struct(p); ++ } ++ ++ return retval; ++} ++ ++/** ++ * sys_sched_getscheduler - get the policy (scheduling class) of a thread ++ * @pid: the pid in question. ++ * ++ * Return: On success, the policy of the thread. Otherwise, a negative error ++ * code. ++ */ ++SYSCALL_DEFINE1(sched_getscheduler, pid_t, pid) ++{ ++ struct task_struct *p; ++ int retval = -EINVAL; ++ ++ if (pid < 0) ++ goto out_nounlock; ++ ++ retval = -ESRCH; ++ rcu_read_lock(); ++ p = find_process_by_pid(pid); ++ if (p) { ++ retval = security_task_getscheduler(p); ++ if (!retval) ++ retval = p->policy; ++ } ++ rcu_read_unlock(); ++ ++out_nounlock: ++ return retval; ++} ++ ++/** ++ * sys_sched_getscheduler - get the RT priority of a thread ++ * @pid: the pid in question. ++ * @param: structure containing the RT priority. ++ * ++ * Return: On success, 0 and the RT priority is in @param. Otherwise, an error ++ * code. ++ */ ++SYSCALL_DEFINE2(sched_getparam, pid_t, pid, struct sched_param __user *, param) ++{ ++ struct sched_param lp = { .sched_priority = 0 }; ++ struct task_struct *p; ++ int retval = -EINVAL; ++ ++ if (!param || pid < 0) ++ goto out_nounlock; ++ ++ rcu_read_lock(); ++ p = find_process_by_pid(pid); ++ retval = -ESRCH; ++ if (!p) ++ goto out_unlock; ++ ++ retval = security_task_getscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ if (has_rt_policy(p)) ++ lp.sched_priority = p->rt_priority; ++ rcu_read_unlock(); ++ ++ /* ++ * This one might sleep, we cannot do it with a spinlock held ... ++ */ ++ retval = copy_to_user(param, &lp, sizeof(*param)) ? -EFAULT : 0; ++ ++out_nounlock: ++ return retval; ++ ++out_unlock: ++ rcu_read_unlock(); ++ return retval; ++} ++ ++/* ++ * Copy the kernel size attribute structure (which might be larger ++ * than what user-space knows about) to user-space. ++ * ++ * Note that all cases are valid: user-space buffer can be larger or ++ * smaller than the kernel-space buffer. The usual case is that both ++ * have the same size. ++ */ ++static int ++sched_attr_copy_to_user(struct sched_attr __user *uattr, ++ struct sched_attr *kattr, ++ unsigned int usize) ++{ ++ unsigned int ksize = sizeof(*kattr); ++ ++ if (!access_ok(uattr, usize)) ++ return -EFAULT; ++ ++ /* ++ * sched_getattr() ABI forwards and backwards compatibility: ++ * ++ * If usize == ksize then we just copy everything to user-space and all is good. ++ * ++ * If usize < ksize then we only copy as much as user-space has space for, ++ * this keeps ABI compatibility as well. We skip the rest. ++ * ++ * If usize > ksize then user-space is using a newer version of the ABI, ++ * which part the kernel doesn't know about. Just ignore it - tooling can ++ * detect the kernel's knowledge of attributes from the attr->size value ++ * which is set to ksize in this case. ++ */ ++ kattr->size = min(usize, ksize); ++ ++ if (copy_to_user(uattr, kattr, kattr->size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++/** ++ * sys_sched_getattr - similar to sched_getparam, but with sched_attr ++ * @pid: the pid in question. ++ * @uattr: structure containing the extended parameters. ++ * @usize: sizeof(attr) for fwd/bwd comp. ++ * @flags: for future extension. ++ */ ++SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr, ++ unsigned int, usize, unsigned int, flags) ++{ ++ struct sched_attr kattr = { }; ++ struct task_struct *p; ++ int retval; ++ ++ if (!uattr || pid < 0 || usize > PAGE_SIZE || ++ usize < SCHED_ATTR_SIZE_VER0 || flags) ++ return -EINVAL; ++ ++ rcu_read_lock(); ++ p = find_process_by_pid(pid); ++ retval = -ESRCH; ++ if (!p) ++ goto out_unlock; ++ ++ retval = security_task_getscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ kattr.sched_policy = p->policy; ++ if (rt_task(p)) ++ kattr.sched_priority = p->rt_priority; ++ else ++ kattr.sched_nice = task_nice(p); ++ ++ rcu_read_unlock(); ++ ++ return sched_attr_copy_to_user(uattr, &kattr, usize); ++ ++out_unlock: ++ rcu_read_unlock(); ++ return retval; ++} ++ ++long sched_setaffinity(pid_t pid, const struct cpumask *in_mask) ++{ ++ cpumask_var_t cpus_allowed, new_mask; ++ struct task_struct *p; ++ int retval; ++ ++ rcu_read_lock(); ++ ++ p = find_process_by_pid(pid); ++ if (!p) { ++ rcu_read_unlock(); ++ return -ESRCH; ++ } ++ ++ /* Prevent p going away */ ++ get_task_struct(p); ++ rcu_read_unlock(); ++ ++ if (p->flags & PF_NO_SETAFFINITY) { ++ retval = -EINVAL; ++ goto out_put_task; ++ } ++ if (!alloc_cpumask_var(&cpus_allowed, GFP_KERNEL)) { ++ retval = -ENOMEM; ++ goto out_put_task; ++ } ++ if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) { ++ retval = -ENOMEM; ++ goto out_free_cpus_allowed; ++ } ++ retval = -EPERM; ++ if (!check_same_owner(p)) { ++ rcu_read_lock(); ++ if (!ns_capable(__task_cred(p)->user_ns, CAP_SYS_NICE)) { ++ rcu_read_unlock(); ++ goto out_unlock; ++ } ++ rcu_read_unlock(); ++ } ++ ++ retval = security_task_setscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ cpuset_cpus_allowed(p, cpus_allowed); ++ cpumask_and(new_mask, in_mask, cpus_allowed); ++again: ++ retval = __set_cpus_allowed_ptr(p, new_mask, SCA_CHECK | SCA_USER); ++ ++ if (!retval) { ++ cpuset_cpus_allowed(p, cpus_allowed); ++ if (!cpumask_subset(new_mask, cpus_allowed)) { ++ /* ++ * We must have raced with a concurrent cpuset ++ * update. Just reset the cpus_allowed to the ++ * cpuset's cpus_allowed ++ */ ++ cpumask_copy(new_mask, cpus_allowed); ++ goto again; ++ } ++ } ++out_unlock: ++ free_cpumask_var(new_mask); ++out_free_cpus_allowed: ++ free_cpumask_var(cpus_allowed); ++out_put_task: ++ put_task_struct(p); ++ return retval; ++} ++ ++static int get_user_cpu_mask(unsigned long __user *user_mask_ptr, unsigned len, ++ cpumask_t *new_mask) ++{ ++ if (len < cpumask_size()) ++ cpumask_clear(new_mask); ++ else if (len > cpumask_size()) ++ len = cpumask_size(); ++ ++ return copy_from_user(new_mask, user_mask_ptr, len) ? -EFAULT : 0; ++} ++ ++ ++/** ++ * sys_sched_setaffinity - set the CPU affinity of a process ++ * @pid: pid of the process ++ * @len: length in bytes of the bitmask pointed to by user_mask_ptr ++ * @user_mask_ptr: user-space pointer to the new CPU mask ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++SYSCALL_DEFINE3(sched_setaffinity, pid_t, pid, unsigned int, len, ++ unsigned long __user *, user_mask_ptr) ++{ ++ cpumask_var_t new_mask; ++ int retval; ++ ++ if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ retval = get_user_cpu_mask(user_mask_ptr, len, new_mask); ++ if (retval == 0) ++ retval = sched_setaffinity(pid, new_mask); ++ free_cpumask_var(new_mask); ++ return retval; ++} ++ ++long sched_getaffinity(pid_t pid, cpumask_t *mask) ++{ ++ struct task_struct *p; ++ unsigned long flags; ++ int retval; ++ ++ cpus_read_lock(); ++ rcu_read_lock(); ++ ++ retval = -ESRCH; ++ p = find_process_by_pid(pid); ++ if (!p) ++ goto out_unlock; ++ ++ retval = security_task_getscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ cpumask_and(mask, &p->cpus_mask, cpu_active_mask); ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++ ++out_unlock: ++ rcu_read_unlock(); ++ cpus_read_unlock(); ++ ++ return retval; ++} ++ ++/** ++ * sys_sched_getaffinity - get the CPU affinity of a process ++ * @pid: pid of the process ++ * @len: length in bytes of the bitmask pointed to by user_mask_ptr ++ * @user_mask_ptr: user-space pointer to hold the current CPU mask ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++SYSCALL_DEFINE3(sched_getaffinity, pid_t, pid, unsigned int, len, ++ unsigned long __user *, user_mask_ptr) ++{ ++ int ret; ++ cpumask_var_t mask; ++ ++ if ((len * BITS_PER_BYTE) < nr_cpu_ids) ++ return -EINVAL; ++ if (len & (sizeof(unsigned long)-1)) ++ return -EINVAL; ++ ++ if (!alloc_cpumask_var(&mask, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ ret = sched_getaffinity(pid, mask); ++ if (ret == 0) { ++ unsigned int retlen = min(len, cpumask_size()); ++ ++ if (copy_to_user(user_mask_ptr, mask, retlen)) ++ ret = -EFAULT; ++ else ++ ret = retlen; ++ } ++ free_cpumask_var(mask); ++ ++ return ret; ++} ++ ++static void do_sched_yield(void) ++{ ++ struct rq_flags rf; ++ struct rq *rq; ++ ++ if (!sched_yield_type) ++ return; ++ ++ rq = this_rq_lock_irq(&rf); ++ ++ if (sched_yield_type > 1) ++ time_slice_expired(current, rq); ++ schedstat_inc(rq->yld_count); ++ ++ preempt_disable(); ++ rq_unlock_irq(rq, &rf); ++ sched_preempt_enable_no_resched(); ++ ++ schedule(); ++} ++ ++/** ++ * sys_sched_yield - yield the current processor to other threads. ++ * ++ * This function yields the current CPU to other tasks. If there are no ++ * other threads running on this CPU then this function will return. ++ * ++ * Return: 0. ++ */ ++SYSCALL_DEFINE0(sched_yield) ++{ ++ do_sched_yield(); ++ return 0; ++} ++ ++#if !defined(CONFIG_PREEMPTION) || defined(CONFIG_PREEMPT_DYNAMIC) ++int __sched __cond_resched(void) ++{ ++ if (should_resched(0)) { ++ preempt_schedule_common(); ++ return 1; ++ } ++#ifndef CONFIG_PREEMPT_RCU ++ rcu_all_qs(); ++#endif ++ return 0; ++} ++EXPORT_SYMBOL(__cond_resched); ++#endif ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL ++DEFINE_STATIC_CALL_RET0(cond_resched, __cond_resched); ++EXPORT_STATIC_CALL_TRAMP(cond_resched); ++ ++DEFINE_STATIC_CALL_RET0(might_resched, __cond_resched); ++EXPORT_STATIC_CALL_TRAMP(might_resched); ++# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) ++static DEFINE_STATIC_KEY_FALSE(sk_dynamic_cond_resched); ++int __sched dynamic_cond_resched(void) ++{ ++ if (!static_branch_unlikely(&sk_dynamic_cond_resched)) ++ return 0; ++ return __cond_resched(); ++} ++EXPORT_SYMBOL(dynamic_cond_resched); ++ ++static DEFINE_STATIC_KEY_FALSE(sk_dynamic_might_resched); ++int __sched dynamic_might_resched(void) ++{ ++ if (!static_branch_unlikely(&sk_dynamic_might_resched)) ++ return 0; ++ return __cond_resched(); ++} ++EXPORT_SYMBOL(dynamic_might_resched); ++# endif ++#endif /* CONFIG_PREEMPT_DYNAMIC */ ++ ++/* ++ * __cond_resched_lock() - if a reschedule is pending, drop the given lock, ++ * call schedule, and on return reacquire the lock. ++ * ++ * This works OK both with and without CONFIG_PREEMPTION. We do strange low-level ++ * operations here to prevent schedule() from being called twice (once via ++ * spin_unlock(), once by hand). ++ */ ++int __cond_resched_lock(spinlock_t *lock) ++{ ++ int resched = should_resched(PREEMPT_LOCK_OFFSET); ++ int ret = 0; ++ ++ lockdep_assert_held(lock); ++ ++ if (spin_needbreak(lock) || resched) { ++ spin_unlock(lock); ++ if (resched) ++ preempt_schedule_common(); ++ else ++ cpu_relax(); ++ ret = 1; ++ spin_lock(lock); ++ } ++ return ret; ++} ++EXPORT_SYMBOL(__cond_resched_lock); ++ ++int __cond_resched_rwlock_read(rwlock_t *lock) ++{ ++ int resched = should_resched(PREEMPT_LOCK_OFFSET); ++ int ret = 0; ++ ++ lockdep_assert_held_read(lock); ++ ++ if (rwlock_needbreak(lock) || resched) { ++ read_unlock(lock); ++ if (resched) ++ preempt_schedule_common(); ++ else ++ cpu_relax(); ++ ret = 1; ++ read_lock(lock); ++ } ++ return ret; ++} ++EXPORT_SYMBOL(__cond_resched_rwlock_read); ++ ++int __cond_resched_rwlock_write(rwlock_t *lock) ++{ ++ int resched = should_resched(PREEMPT_LOCK_OFFSET); ++ int ret = 0; ++ ++ lockdep_assert_held_write(lock); ++ ++ if (rwlock_needbreak(lock) || resched) { ++ write_unlock(lock); ++ if (resched) ++ preempt_schedule_common(); ++ else ++ cpu_relax(); ++ ret = 1; ++ write_lock(lock); ++ } ++ return ret; ++} ++EXPORT_SYMBOL(__cond_resched_rwlock_write); ++ ++/** ++ * yield - yield the current processor to other threads. ++ * ++ * Do not ever use this function, there's a 99% chance you're doing it wrong. ++ * ++ * The scheduler is at all times free to pick the calling task as the most ++ * eligible task to run, if removing the yield() call from your code breaks ++ * it, it's already broken. ++ * ++ * Typical broken usage is: ++ * ++ * while (!event) ++ * yield(); ++ * ++ * where one assumes that yield() will let 'the other' process run that will ++ * make event true. If the current task is a SCHED_FIFO task that will never ++ * happen. Never use yield() as a progress guarantee!! ++ * ++ * If you want to use yield() to wait for something, use wait_event(). ++ * If you want to use yield() to be 'nice' for others, use cond_resched(). ++ * If you still want to use yield(), do not! ++ */ ++void __sched yield(void) ++{ ++ set_current_state(TASK_RUNNING); ++ do_sched_yield(); ++} ++EXPORT_SYMBOL(yield); ++ ++/** ++ * yield_to - yield the current processor to another thread in ++ * your thread group, or accelerate that thread toward the ++ * processor it's on. ++ * @p: target task ++ * @preempt: whether task preemption is allowed or not ++ * ++ * It's the caller's job to ensure that the target task struct ++ * can't go away on us before we can do any checks. ++ * ++ * Return: ++ * true (>0) if we indeed boosted the target task. ++ * false (0) if we failed to boost the target. ++ * -ESRCH if there's no task to yield to. ++ */ ++int __sched yield_to(struct task_struct *p, bool preempt) ++{ ++ struct task_struct *rq_p; ++ struct rq *rq, *p_rq; ++ unsigned long flags; ++ int yielded = 0; ++ ++ local_irq_save(flags); ++ rq = this_rq(); ++ ++again: ++ p_rq = task_rq(p); ++ /* ++ * If we're the only runnable task on the rq and target rq also ++ * has only one task, there's absolutely no point in yielding. ++ */ ++ if (task_running(p_rq, p) || p->__state) { ++ yielded = -ESRCH; ++ goto out_irq; ++ } ++ ++ double_rq_lock(rq, p_rq); ++ if (unlikely(task_rq(p) != p_rq)) { ++ double_rq_unlock(rq, p_rq); ++ goto again; ++ } ++ ++ yielded = 1; ++ schedstat_inc(rq->yld_count); ++ rq_p = rq->curr; ++ if (p->deadline > rq_p->deadline) ++ p->deadline = rq_p->deadline; ++ p->time_slice += rq_p->time_slice; ++ if (p->time_slice > timeslice()) ++ p->time_slice = timeslice(); ++ time_slice_expired(rq_p, rq); ++ if (preempt && rq != p_rq) ++ resched_task(p_rq->curr); ++ double_rq_unlock(rq, p_rq); ++out_irq: ++ local_irq_restore(flags); ++ ++ if (yielded > 0) ++ schedule(); ++ return yielded; ++} ++EXPORT_SYMBOL_GPL(yield_to); ++ ++int io_schedule_prepare(void) ++{ ++ int old_iowait = current->in_iowait; ++ ++ current->in_iowait = 1; ++ blk_flush_plug(current->plug, true); ++ ++ return old_iowait; ++} ++ ++void io_schedule_finish(int token) ++{ ++ current->in_iowait = token; ++} ++ ++/* ++ * This task is about to go to sleep on IO. Increment rq->nr_iowait so ++ * that process accounting knows that this is a task in IO wait state. ++ * ++ * But don't do that if it is a deliberate, throttling IO wait (this task ++ * has set its backing_dev_info: the queue against which it should throttle) ++ */ ++ ++long __sched io_schedule_timeout(long timeout) ++{ ++ int token; ++ long ret; ++ ++ token = io_schedule_prepare(); ++ ret = schedule_timeout(timeout); ++ io_schedule_finish(token); ++ ++ return ret; ++} ++EXPORT_SYMBOL(io_schedule_timeout); ++ ++void __sched io_schedule(void) ++{ ++ int token; ++ ++ token = io_schedule_prepare(); ++ schedule(); ++ io_schedule_finish(token); ++} ++EXPORT_SYMBOL(io_schedule); ++ ++/** ++ * sys_sched_get_priority_max - return maximum RT priority. ++ * @policy: scheduling class. ++ * ++ * Return: On success, this syscall returns the maximum ++ * rt_priority that can be used by a given scheduling class. ++ * On failure, a negative error code is returned. ++ */ ++SYSCALL_DEFINE1(sched_get_priority_max, int, policy) ++{ ++ int ret = -EINVAL; ++ ++ switch (policy) { ++ case SCHED_FIFO: ++ case SCHED_RR: ++ ret = MAX_RT_PRIO-1; ++ break; ++ case SCHED_NORMAL: ++ case SCHED_BATCH: ++ case SCHED_ISO: ++ case SCHED_IDLEPRIO: ++ ret = 0; ++ break; ++ } ++ return ret; ++} ++ ++/** ++ * sys_sched_get_priority_min - return minimum RT priority. ++ * @policy: scheduling class. ++ * ++ * Return: On success, this syscall returns the minimum ++ * rt_priority that can be used by a given scheduling class. ++ * On failure, a negative error code is returned. ++ */ ++SYSCALL_DEFINE1(sched_get_priority_min, int, policy) ++{ ++ int ret = -EINVAL; ++ ++ switch (policy) { ++ case SCHED_FIFO: ++ case SCHED_RR: ++ ret = 1; ++ break; ++ case SCHED_NORMAL: ++ case SCHED_BATCH: ++ case SCHED_ISO: ++ case SCHED_IDLEPRIO: ++ ret = 0; ++ break; ++ } ++ return ret; ++} ++ ++static int sched_rr_get_interval(pid_t pid, struct timespec64 *t) ++{ ++ struct task_struct *p; ++ unsigned int time_slice; ++ struct rq_flags rf; ++ struct rq *rq; ++ int retval; ++ ++ if (pid < 0) ++ return -EINVAL; ++ ++ retval = -ESRCH; ++ rcu_read_lock(); ++ p = find_process_by_pid(pid); ++ if (!p) ++ goto out_unlock; ++ ++ retval = security_task_getscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ rq = task_rq_lock(p, &rf); ++ time_slice = p->policy == SCHED_FIFO ? 0 : MS_TO_NS(task_timeslice(p)); ++ task_rq_unlock(rq, p, &rf); ++ ++ rcu_read_unlock(); ++ *t = ns_to_timespec64(time_slice); ++ return 0; ++ ++out_unlock: ++ rcu_read_unlock(); ++ return retval; ++} ++ ++/** ++ * sys_sched_rr_get_interval - return the default timeslice of a process. ++ * @pid: pid of the process. ++ * @interval: userspace pointer to the timeslice value. ++ * ++ * this syscall writes the default timeslice value of a given process ++ * into the user-space timespec buffer. A value of '0' means infinity. ++ * ++ * Return: On success, 0 and the timeslice is in @interval. Otherwise, ++ * an error code. ++ */ ++SYSCALL_DEFINE2(sched_rr_get_interval, pid_t, pid, ++ struct __kernel_timespec __user *, interval) ++{ ++ struct timespec64 t; ++ int retval = sched_rr_get_interval(pid, &t); ++ ++ if (retval == 0) ++ retval = put_timespec64(&t, interval); ++ ++ return retval; ++} ++ ++#ifdef CONFIG_COMPAT_32BIT_TIME ++SYSCALL_DEFINE2(sched_rr_get_interval_time32, pid_t, pid, ++ struct old_timespec32 __user *, interval) ++{ ++ struct timespec64 t; ++ int retval = sched_rr_get_interval(pid, &t); ++ ++ if (retval == 0) ++ retval = put_old_timespec32(&t, interval); ++ return retval; ++} ++#endif ++ ++void sched_show_task(struct task_struct *p) ++{ ++ unsigned long free = 0; ++ int ppid; ++ ++ if (!try_get_task_stack(p)) ++ return; ++ ++ printk(KERN_INFO "%-15.15s %c", p->comm, task_state_to_char(p)); ++ ++ if (p->__state == TASK_RUNNING) ++ printk(KERN_CONT " running task "); ++#ifdef CONFIG_DEBUG_STACK_USAGE ++ free = stack_not_used(p); ++#endif ++ ppid = 0; ++ rcu_read_lock(); ++ if (pid_alive(p)) ++ ppid = task_pid_nr(rcu_dereference(p->real_parent)); ++ rcu_read_unlock(); ++ pr_cont(" stack:%5lu pid:%5d ppid:%6d flags:0x%08lx\n", ++ free, task_pid_nr(p), ppid, ++ (unsigned long)task_thread_info(p)->flags); ++ ++ print_worker_info(KERN_INFO, p); ++ print_stop_info(KERN_INFO, p); ++ show_stack(p, NULL, KERN_INFO); ++ put_task_stack(p); ++} ++EXPORT_SYMBOL_GPL(sched_show_task); ++ ++static inline bool ++state_filter_match(unsigned long state_filter, struct task_struct *p) ++{ ++ /* no filter, everything matches */ ++ if (!state_filter) ++ return true; ++ ++ /* filter, but doesn't match */ ++ if (!(p->__state & state_filter)) ++ return false; ++ ++ /* ++ * When looking for TASK_UNINTERRUPTIBLE skip TASK_IDLE (allows ++ * TASK_KILLABLE). ++ */ ++ if (state_filter == TASK_UNINTERRUPTIBLE && p->__state == TASK_IDLE) ++ return false; ++ ++ return true; ++} ++ ++void show_state_filter(unsigned int state_filter) ++{ ++ struct task_struct *g, *p; ++ ++ rcu_read_lock(); ++ for_each_process_thread(g, p) { ++ /* ++ * reset the NMI-timeout, listing all files on a slow ++ * console might take a lot of time: ++ * Also, reset softlockup watchdogs on all CPUs, because ++ * another CPU might be blocked waiting for us to process ++ * an IPI. ++ */ ++ touch_nmi_watchdog(); ++ touch_all_softlockup_watchdogs(); ++ if (state_filter_match(state_filter, p)) ++ sched_show_task(p); ++ } ++ ++ rcu_read_unlock(); ++ /* ++ * Only show locks if all tasks are dumped: ++ */ ++ if (!state_filter) ++ debug_show_all_locks(); ++} ++ ++void dump_cpu_task(int cpu) ++{ ++ pr_info("Task dump for CPU %d:\n", cpu); ++ sched_show_task(cpu_curr(cpu)); ++} ++ ++#ifdef CONFIG_SMP ++void set_cpus_allowed_common(struct task_struct *p, struct affinity_context *ctx) ++{ ++ if (ctx->flags & (SCA_MIGRATE_ENABLE | SCA_MIGRATE_DISABLE)) { ++ p->cpus_ptr = ctx->new_mask; ++ return; ++ } ++ ++ cpumask_copy(&p->cpus_mask, ctx->new_mask); ++ p->nr_cpus_allowed = cpumask_weight(ctx->new_mask); ++} ++ ++void ++__do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx) ++{ ++ struct rq *rq = task_rq(p); ++ ++ lockdep_assert_held(&p->pi_lock); ++ ++ /* ++ * Keep cpus_mask and nr_cpus_allowed in lockstep. sched_other_cpu() ++ * relies on nr_cpus_allowed == 1 to allow hotplug kthreads bound to ++ * offline CPUs to still be picked on an online runqueue. ++ */ ++ set_cpus_allowed_common(p, ctx); ++ ++ if (task_queued(p)) { ++ /* ++ * Because __kthread_bind() calls this on blocked tasks without ++ * holding rq->lock. ++ */ ++ lockdep_assert_held(rq->lock); ++ } ++} ++ ++static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf, ++ struct task_struct *p, int new_cpu); ++ ++/* ++ * Calling do_set_cpus_allowed from outside the scheduler code should not be ++ * called on a running or queued task. We should be holding pi_lock. ++ */ ++void do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx) ++{ ++ __do_set_cpus_allowed(p, ctx); ++ ++ /* ++ * A mask forced by cpuset or kthread_bind is a change of what the ++ * task wants, so an override bind_zero() had to make must not be ++ * restored over the top of it later. What was saved is left in place ++ * rather than freed, which here would be under pi_lock. ++ */ ++ if (ctx->flags & SCA_USER) ++ p->zerobound = false; ++ ++ if (needs_other_cpu(p, task_cpu(p))) { ++ struct rq *rq; ++ ++ rq = __task_rq_lock(p, NULL); ++ /* ++ * A blocked task is on no runqueue, so task_cpu() only says ++ * which rq lock protects it and which CPU PSI counts its state ++ * on. It is left exactly as it is. ++ * ++ * Moving it would leave the TSK_IOWAIT it is carrying counted ++ * on the CPU it came from, and the wakeup would then clear it ++ * on a CPU that never counted it and underflow that counter. ++ * Nor may the destination be recorded in wake_cpu: that is not ++ * a hint, it is the bit that arms the lazy migration handshake. ++ * return_task() reads wake_cpu != task_cpu() as "set_task_cpu() ++ * moved this running task, hand it over in ++ * finish_lock_switch()", so stamping it here arms a migration ++ * no one arranged. Widening the mask again before the task ++ * wakes is enough to leave it armed, since ttwu only calls ++ * set_task_cpu() - the one place that resyncs wake_cpu - when ++ * it picks a different CPU, and a wakeup back onto task_cpu() ++ * therefore keeps the stale value until the next deschedule ++ * migrates the task out from under the switch. ++ * ++ * Nothing needs either. select_best_cpu() honours the new mask ++ * when the task wakes and set_task_cpu() takes the PSI state ++ * with it, which is why affine_move_task() leaves a blocked ++ * task alone here as well. ++ */ ++ if (task_running(rq, p)) { ++ /* ++ * A running task is not on any skiplist, so ++ * set_task_cpu() only tags wake_cpu for it; resched_task() ++ * gets it off the CPU so finish_lock_switch() can ++ * complete the move. ++ */ ++ set_task_cpu(p, valid_task_cpu(p)); ++ resched_task(p); ++ } else if (task_queued(p)) { ++ /* ++ * A queued task's skiplist node is linked into this ++ * rq's list, so its CPU must not be rewritten ++ * underneath it: task_rq(p) would then name a runqueue ++ * the node is not in, and the next dequeue_task() would ++ * try to unlink it from there and trip ++ * skiplist_delete()'s "m < 0" while leaving the node ++ * linked in the list it really is on. Unlink it first, ++ * exactly as every other migration of a queued task ++ * does. ++ */ ++ rq = move_queued_task(rq, NULL, p, valid_task_cpu(p)); ++ } ++ __task_rq_unlock(rq, p, NULL); ++ } ++} ++ ++/* migrate_disable()/migrate_enable() are inline in now. */ ++#endif ++ ++/** ++ * init_idle - set up an idle thread for a given CPU ++ * @idle: task in question ++ * @cpu: cpu the idle task belongs to ++ * ++ * NOTE: this function does not set the idle thread's NEED_RESCHED ++ * flag, to make booting more robust. ++ */ ++void init_idle(struct task_struct *idle, int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&idle->pi_lock, flags); ++ raw_spin_lock(rq->lock); ++ idle->last_ran = rq->niffies; ++ time_slice_expired(idle, rq); ++ idle->__state = TASK_RUNNING; ++ /* Setting prio to illegal value shouldn't matter when never queued */ ++ idle->prio = PRIO_LIMIT; ++ idle->flags |= PF_IDLE; ++ ++ scs_task_reset(idle); ++ kasan_unpoison_task_stack(idle); ++ ++#ifdef CONFIG_SMP ++ /* ++ * It's possible that init_idle() gets called multiple times on a task, ++ * in that case do_set_cpus_allowed() will not do the right thing. ++ * ++ * And since this is boot we can forgo the serialisation. ++ */ ++ { ++ struct affinity_context ac = { .new_mask = cpumask_of(cpu) }; ++ ++ set_cpus_allowed_common(idle, &ac); ++ } ++#ifdef CONFIG_SMT_NICE ++ idle->smt_bias = 0; ++#endif ++#endif ++ set_rq_task(rq, idle); ++ ++ /* Silence PROVE_RCU */ ++ rcu_read_lock(); ++ set_task_cpu(idle, cpu); ++ rcu_read_unlock(); ++ ++ rq->idle = idle; ++ rcu_assign_pointer(rq->curr, idle); ++ idle->on_rq = TASK_ON_RQ_QUEUED; ++ raw_spin_unlock(rq->lock); ++ raw_spin_unlock_irqrestore(&idle->pi_lock, flags); ++ ++ /* Set the preempt count _outside_ the spinlocks! */ ++ init_idle_preempt_count(idle, cpu); ++ ++ ftrace_graph_init_idle_task(idle, cpu); ++ vtime_init_idle(idle, cpu); ++#ifdef CONFIG_SMP ++ sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu); ++#endif ++} ++ ++int cpuset_cpumask_can_shrink(const struct cpumask __maybe_unused *cur, ++ const struct cpumask __maybe_unused *trial) ++{ ++ return 1; ++} ++ ++int task_can_attach(struct task_struct *p) ++{ ++ int ret = 0; ++ ++ /* ++ * Kthreads which disallow setaffinity shouldn't be moved ++ * to a new cpuset; we don't want to change their CPU ++ * affinity and isolating such threads by their set of ++ * allowed nodes is unnecessary. Thus, cpusets are not ++ * applicable for such threads. This prevents checking for ++ * success of set_cpus_allowed_ptr() on all attached tasks ++ * before cpus_mask may be changed. ++ */ ++ if (p->flags & PF_NO_SETAFFINITY) ++ ret = -EINVAL; ++ ++ return ret; ++} ++ ++void resched_cpu(int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ ++ rq_lock_irqsave(rq, &rf); ++ if (cpu_online(cpu) || cpu == smp_processor_id()) ++ resched_curr(rq); ++ rq_unlock_irqrestore(rq, &rf); ++} ++ ++#ifdef CONFIG_SMP ++#ifdef CONFIG_NO_HZ_COMMON ++void nohz_balance_enter_idle(int cpu) {} ++ ++/* ++ * In the semi idle case, use the nearest busy CPU for migrating timers ++ * from an idle CPU. This is good for power-savings. ++ * ++ * We don't do similar optimization for completely idle system, as ++ * selecting an idle CPU will add more delays to the timers than intended ++ * (as that CPU's timer base may not be uptodate wrt jiffies etc). ++ */ ++int get_nohz_timer_target(void) ++{ ++ int i, cpu = smp_processor_id(), default_cpu = -1; ++ struct sched_domain *sd; ++ ++ if (housekeeping_cpu(cpu, HK_TYPE_TIMER)) { ++ if (!idle_cpu(cpu)) ++ return cpu; ++ default_cpu = cpu; ++ } ++ ++ rcu_read_lock(); ++ for_each_domain(cpu, sd) { ++ for_each_cpu_and(i, sched_domain_span(sd), ++ housekeeping_cpumask(HK_TYPE_TIMER)) { ++ if (cpu == i) ++ continue; ++ ++ if (!idle_cpu(i)) { ++ cpu = i; ++ goto unlock; ++ } ++ } ++ } ++ ++ if (default_cpu == -1) ++ default_cpu = housekeeping_any_cpu(HK_TYPE_TIMER); ++ cpu = default_cpu; ++unlock: ++ rcu_read_unlock(); ++ return cpu; ++} ++ ++/* ++ * When add_timer_on() enqueues a timer into the timer wheel of an ++ * idle CPU then this timer might expire before the next timer event ++ * which is scheduled to wake up that CPU. In case of a completely ++ * idle system the next event might even be infinite time into the ++ * future. wake_up_idle_cpu() ensures that the CPU is woken up and ++ * leaves the inner idle loop so the newly added timer is taken into ++ * account when the CPU goes back to idle and evaluates the timer ++ * wheel for the next timer event. ++ */ ++static void wake_up_idle_cpu(int cpu) ++{ ++ if (cpu == smp_processor_id()) ++ return; ++ ++ if (set_nr_and_not_polling(cpu_rq(cpu)->idle)) ++ smp_sched_reschedule(cpu); ++ else ++ trace_sched_wake_idle_without_ipi(cpu); ++} ++ ++static bool wake_up_full_nohz_cpu(int cpu) ++{ ++ /* ++ * We just need the target to call irq_exit() and re-evaluate ++ * the next tick. The nohz full kick at least implies that. ++ * If needed we can still optimize that later with an ++ * empty IRQ. ++ */ ++ if (cpu_is_offline(cpu)) ++ return true; /* Don't try to wake offline CPUs. */ ++ if (tick_nohz_full_cpu(cpu)) { ++ if (cpu != smp_processor_id() || ++ tick_nohz_tick_stopped()) ++ tick_nohz_full_kick_cpu(cpu); ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * Wake up the specified CPU. If the CPU is going offline, it is the ++ * caller's responsibility to deal with the lost wakeup, for example, ++ * by hooking into the CPU_DEAD notifier like timers and hrtimers do. ++ */ ++void wake_up_nohz_cpu(int cpu) ++{ ++ if (!wake_up_full_nohz_cpu(cpu)) ++ wake_up_idle_cpu(cpu); ++} ++#endif /* CONFIG_NO_HZ_COMMON */ ++ ++/* ++ * This is how migration works: ++ * ++ * 1) we invoke migration_cpu_stop() on the target CPU using ++ * stop_one_cpu(). ++ * 2) stopper starts to run (implicitly forcing the migrated thread ++ * off the CPU) ++ * 3) it checks whether the migrated task is still in the wrong runqueue. ++ * 4) if it's in the wrong runqueue then the migration thread removes ++ * it and puts it into the right queue. ++ * 5) stopper completes and stop_one_cpu() returns and the migration ++ * is done. ++ */ ++ ++/* ++ * move_queued_task - move a queued task to new rq. ++ * ++ * Returns (locked) new rq. Old rq's lock is released. Running tasks ++ * are not on the skiplist, so callers must use task_queued(). ++ */ ++static struct rq *move_queued_task(struct rq *rq, ++ struct rq_flags __always_unused *rf, ++ struct task_struct *p, int new_cpu) ++{ ++ lockdep_assert_held(rq->lock); ++ ++ dequeue_task(rq, p, 0); ++ set_task_cpu(p, new_cpu); ++ rq_unlock(rq); ++ ++ rq = cpu_rq(new_cpu); ++ ++ rq_lock(rq); ++ WARN_ON_ONCE(task_cpu(p) != new_cpu); ++ enqueue_task(rq, p, ENQUEUE_MIGRATED); ++ try_preempt(p, rq); ++ ++ return rq; ++} ++ ++struct set_affinity_pending; ++ ++struct migration_arg { ++ struct task_struct *task; ++ int dest_cpu; ++ struct set_affinity_pending *pending; ++}; ++ ++/* ++ * @refs: number of wait_for_completion() ++ * @stop_pending: is @stop_work in use ++ */ ++struct set_affinity_pending { ++ refcount_t refs; ++ unsigned int stop_pending; ++ struct completion done; ++ struct cpu_stop_work stop_work; ++ struct migration_arg arg; ++}; ++ ++static struct rq *__migrate_task(struct rq *rq, struct rq_flags *rf, ++ struct task_struct *p, int dest_cpu) ++{ ++ /* Affinity changed (again). */ ++ if (!is_cpu_allowed(p, dest_cpu)) ++ return rq; ++ ++ return move_queued_task(rq, rf, p, dest_cpu); ++} ++ ++static int migration_cpu_stop(void *data) ++{ ++ struct migration_arg *arg = data; ++ struct set_affinity_pending *pending = arg->pending; ++ struct task_struct *p = arg->task; ++ struct rq *rq = this_rq(); ++ bool complete = false; ++ struct rq_flags rf; ++ ++ /* ++ * The original target CPU might have gone down and we might ++ * be on another CPU but it doesn't matter. ++ */ ++ local_irq_save(rf.flags); ++ /* ++ * Flush pending wakeups so we do not miss enforcing cpus_ptr, ++ * see set_cpus_allowed_ptr()'s TASK_WAKING test. ++ */ ++ flush_smp_call_function_queue(); ++ ++ raw_spin_lock(&p->pi_lock); ++ rq_lock(rq); ++ ++ /* ++ * If we were passed a pending, then ->stop_pending was set, thus ++ * p->migration_pending must have remained stable. ++ */ ++ WARN_ON_ONCE(pending && pending != p->migration_pending); ++ ++ /* ++ * If task_rq(p) != rq, it cannot be migrated here, because we're ++ * holding rq->lock. If p is not queued it cannot get enqueued ++ * because we're holding p->pi_lock. ++ */ ++ if (task_rq(p) == rq) { ++ if (is_migration_disabled(p)) ++ goto out; ++ ++ if (pending) { ++ p->migration_pending = NULL; ++ complete = true; ++ ++ if (cpumask_test_cpu(task_cpu(p), &p->cpus_mask)) ++ goto out; ++ } ++ ++ /* ++ * Mainline updates the rq clock before __migrate_task() here. ++ * MuQSS has no need to: dequeue_task() and enqueue_task() each ++ * call update_clocks() on the runqueue they touch, so niffies, ++ * which deadlines are based on, is current on both sides of ++ * the migration. Adding one here would only be redundant. ++ */ ++ if (task_queued(p)) ++ rq = __migrate_task(rq, &rf, p, arg->dest_cpu); ++ else ++ p->wake_cpu = arg->dest_cpu; ++ } else if (pending) { ++ /* ++ * The task moved before the stopper got to run. We're ++ * holding ->pi_lock, so the allowed mask is stable - if ++ * it got somewhere allowed, we're done. ++ */ ++ if (cpumask_test_cpu(task_cpu(p), p->cpus_ptr)) { ++ p->migration_pending = NULL; ++ complete = true; ++ goto out; ++ } ++ ++ /* ++ * When migrate_enable() hits a rq mis-match we can't ++ * reliably determine is_migration_disabled() and so ++ * have to chase after it. ++ */ ++ WARN_ON_ONCE(!pending->stop_pending); ++ preempt_disable(); ++ rq_unlock(rq); ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags); ++ stop_one_cpu_nowait(task_cpu(p), migration_cpu_stop, ++ &pending->arg, &pending->stop_work); ++ preempt_enable(); ++ return 0; ++ } ++out: ++ if (pending) ++ pending->stop_pending = false; ++ rq_unlock(rq); ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags); ++ ++ if (complete) ++ complete_all(&pending->done); ++ ++ return 0; ++} ++ ++/* ++ * When given a valid mask, __set_cpus_allowed_ptr() must block until ++ * the designated task is enqueued on an allowed CPU. A running task ++ * is kicked off with the CPU stopper. A migrate_disable() region on ++ * the target delays that move until the outermost migrate_enable(); ++ * concurrent waiters share one set_affinity_pending. ++ */ ++static int affine_move_task(struct rq *rq, struct task_struct *p, ++ struct rq_flags *rf, int dest_cpu, unsigned int flags) ++{ ++ struct set_affinity_pending my_pending = { }, *pending = NULL; ++ bool stop_pending, complete = false; ++ ++ /* Can the task run on the task's current CPU? If so, we're done */ ++ if (cpumask_test_cpu(task_cpu(p), &p->cpus_mask)) { ++ /* ++ * If there are pending waiters, but no pending stop_work, ++ * then complete now. ++ */ ++ pending = p->migration_pending; ++ if (pending && !pending->stop_pending) { ++ p->migration_pending = NULL; ++ complete = true; ++ } ++ ++ task_rq_unlock(rq, p, rf); ++ ++ if (complete) ++ complete_all(&pending->done); ++ ++ return 0; ++ } ++ ++ if (!(flags & SCA_MIGRATE_ENABLE)) { ++ /* serialized by p->pi_lock */ ++ if (!p->migration_pending) { ++ refcount_set(&my_pending.refs, 1); ++ init_completion(&my_pending.done); ++ my_pending.arg = (struct migration_arg) { ++ .task = p, ++ .dest_cpu = dest_cpu, ++ .pending = &my_pending, ++ }; ++ ++ p->migration_pending = &my_pending; ++ } else { ++ pending = p->migration_pending; ++ refcount_inc(&pending->refs); ++ /* ++ * Affinity has changed, but we've already installed a ++ * pending. migration_cpu_stop() *must* see this, else ++ * we risk completing despite a task on a disallowed ++ * CPU. Serialized by p->pi_lock. ++ */ ++ pending->arg.dest_cpu = dest_cpu; ++ } ++ } ++ pending = p->migration_pending; ++ /* ++ * !MIGRATE_ENABLE installs a pending if there wasn't one. ++ * MIGRATE_ENABLE only gets here because the current CPU no ++ * longer matches, so a concurrent set_cpus_allowed_ptr() ++ * should still be pending completion. ++ */ ++ if (WARN_ON_ONCE(!pending)) { ++ task_rq_unlock(rq, p, rf); ++ return -EINVAL; ++ } ++ ++ if (task_running(rq, p) || READ_ONCE(p->__state) == TASK_WAKING) { ++ /* ++ * MIGRATE_ENABLE gets here because 'p == current'. For ++ * anything else we cannot do is_migration_disabled(); ++ * punt and have the stopper handle it race-free. ++ */ ++ stop_pending = pending->stop_pending; ++ if (!stop_pending) ++ pending->stop_pending = true; ++ ++ preempt_disable(); ++ task_rq_unlock(rq, p, rf); ++ if (!stop_pending) { ++ stop_one_cpu_nowait(cpu_of(rq), migration_cpu_stop, ++ &pending->arg, &pending->stop_work); ++ } ++ preempt_enable(); ++ ++ if (flags & SCA_MIGRATE_ENABLE) ++ return 0; ++ } else { ++ if (!is_migration_disabled(p)) { ++ if (task_queued(p)) ++ rq = move_queued_task(rq, rf, p, dest_cpu); ++ ++ if (!pending->stop_pending) { ++ p->migration_pending = NULL; ++ complete = true; ++ } ++ } ++ task_rq_unlock(rq, p, rf); ++ ++ if (complete) ++ complete_all(&pending->done); ++ } ++ ++ wait_for_completion(&pending->done); ++ ++ if (refcount_dec_and_test(&pending->refs)) ++ wake_up_var(&pending->refs); /* No UaF, just an address */ ++ ++ /* ++ * Block the original owner of &pending until all subsequent ++ * callers have seen the completion and decremented the refcount ++ */ ++ wait_var_event(&my_pending.refs, !refcount_read(&my_pending.refs)); ++ ++ WARN_ON_ONCE(my_pending.stop_pending); ++ ++ return 0; ++} ++ ++/* ++ * Record @new_mask as the affinity userspace asked for, handing back the ++ * allocation it displaced for the caller to free once the locks are ++ * dropped. An explicit request also supersedes any override bind_zero() ++ * had to force, since this mask is the one that is wanted now. ++ */ ++static cpumask_t *set_user_cpus_ptr(struct task_struct *p, ++ const struct cpumask *new_mask, ++ cpumask_t *user_mask) ++{ ++ lockdep_assert_held(&p->pi_lock); ++ ++ if (user_mask) { ++ cpumask_copy(user_mask, new_mask); ++ swap(p->user_cpus_ptr, user_mask); ++ } ++ p->zerobound = false; ++ ++ return user_mask; ++} ++ ++/* ++ * Change a given task's CPU affinity. Migrate the thread to a ++ * proper CPU and schedule it away if the CPU it's executing on ++ * is removed from the allowed bitmask. ++ * ++ * NOTE: the caller must have a valid reference to the task, the ++ * task must not exit() & deallocate itself prematurely. The ++ * call is not atomic; no spinlocks may be held. ++ */ ++static int __set_cpus_allowed_ptr(struct task_struct *p, ++ const struct cpumask *new_mask, ++ u32 flags) ++{ ++ const struct cpumask *cpu_valid_mask = cpu_active_mask; ++ cpumask_t *user_mask = NULL; ++ unsigned int dest_cpu; ++ struct rq_flags rf; ++ struct rq *rq; ++ int ret = 0; ++ ++ /* ++ * user_cpus_ptr keeps what userspace asked for so that ++ * relax_compatible_cpus_allowed_ptr() and unbind_zero() have ++ * something to put back. Only sched_setaffinity() passes SCA_USER ++ * and it is always sleepable, so make room before taking any lock; ++ * failing here only costs the task that restore, not the syscall. ++ */ ++ if (flags & SCA_USER) ++ user_mask = kmalloc(cpumask_size(), GFP_KERNEL); ++ ++ rq = task_rq_lock(p, &rf); ++ update_rq_clock(rq); ++ ++ if ((p->flags & PF_KTHREAD) || is_migration_disabled(p)) { ++ /* ++ * Kernel threads are allowed on online && !active CPUs. ++ * migrate_disabled() tasks must not fail the dest pick ++ * on SCA_MIGRATE_ENABLE or we skip set_cpus_allowed_common() ++ * and never reset p->cpus_ptr. ++ */ ++ cpu_valid_mask = cpu_online_mask; ++ } ++ ++ /* ++ * Must re-check here, to close a race against __kthread_bind(), ++ * sched_setaffinity() is not guaranteed to observe the flag. ++ */ ++ if ((flags & SCA_CHECK) && (p->flags & PF_NO_SETAFFINITY)) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ if (!(flags & SCA_MIGRATE_ENABLE)) { ++ if (cpumask_equal(&p->cpus_mask, new_mask)) { ++ /* ++ * Nothing to change, but asking for exactly the mask ++ * bind_zero() forced is still userspace choosing it, ++ * and takes over from the restore. An internal caller ++ * changing nothing must not cost the task that. ++ */ ++ if (flags & SCA_USER) ++ user_mask = set_user_cpus_ptr(p, new_mask, ++ user_mask); ++ goto out; ++ } ++ ++ if (WARN_ON_ONCE(p == current && ++ is_migration_disabled(p) && ++ !cpumask_test_cpu(task_cpu(p), new_mask))) { ++ ret = -EBUSY; ++ goto out; ++ } ++ } ++ ++ /* ++ * Picking a ~random cpu helps in cases where we are changing affinity ++ * for groups of tasks (ie. cpuset), so that load balancing is not ++ * immediately required to distribute the tasks within their new mask. ++ */ ++ dest_cpu = cpumask_any_and_distribute(cpu_valid_mask, new_mask); ++ if (dest_cpu >= nr_cpu_ids) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ { ++ struct affinity_context ac = { ++ .new_mask = new_mask, ++ .flags = flags, ++ }; ++ ++ __do_set_cpus_allowed(p, &ac); ++ } ++ ++ /* ++ * migrate_enable() only reinstates cpus_mask, so it is not a change ++ * of what the task wants and must not disturb what is saved. ++ */ ++ if (!(flags & SCA_MIGRATE_ENABLE)) ++ user_mask = set_user_cpus_ptr(p, new_mask, user_mask); ++ ++ if (p->flags & PF_KTHREAD) { ++ /* ++ * For kernel threads that do indeed end up on online && ++ * !active we want to ensure they are strict per-CPU threads. ++ */ ++ WARN_ON(cpumask_intersects(new_mask, cpu_online_mask) && ++ !cpumask_intersects(new_mask, cpu_active_mask) && ++ p->nr_cpus_allowed != 1); ++ } ++ ++ ret = affine_move_task(rq, p, &rf, dest_cpu, flags); ++ kfree(user_mask); ++ ++ return ret; ++ ++out: ++ task_rq_unlock(rq, p, &rf); ++ kfree(user_mask); ++ ++ return ret; ++} ++ ++int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask) ++{ ++ return __set_cpus_allowed_ptr(p, new_mask, 0); ++} ++EXPORT_SYMBOL_GPL(set_cpus_allowed_ptr); ++ ++#ifdef CONFIG_HOTPLUG_CPU ++/* ++ * Skip affinity rewrite for tasks whose mask is managed elsewhere: the ++ * hotplug/stopper threads, idle, and kthreads that forbid setaffinity ++ * (including KTHREAD_IS_PER_CPU, rebound on unpark). ++ */ ++static bool bind_zero_skip_affinity(struct task_struct *p, struct rq *rq) ++{ ++ return p == current || is_idle_task(p) || p == rq->stop || ++ (p->flags & PF_NO_SETAFFINITY) || kthread_is_per_cpu(p); ++} ++ ++/* ++ * Move @p off @src_cpu under pi+rq lock, and only override its affinity ++ * if losing @src_cpu would leave it with no active CPU at all. ++ * ++ * Like mainline, the common case does not touch cpus_mask: an offline or ++ * deactivated CPU is already refused by is_cpu_allowed(), sched_other_cpu(), ++ * needs_other_cpu() and valid_task_cpu(), so the mask needs no editing and ++ * the task keeps the affinity userspace asked for across a hotplug cycle. ++ * Narrowing here would be permanent, as unbind_zero() only restores tasks ++ * that were forced. ++ * ++ * The desperate case still has to put the task somewhere: substitute an ++ * active fallback (usually CPU0) for @src_cpu and record that with ++ * zerobound, stashing the mask being replaced in user_cpus_ptr so ++ * unbind_zero() can put back exactly what userspace asked for once ++ * @src_cpu returns. ++ * ++ * migrate_disable() tasks keep their pin (cpus_ptr) and are left for ++ * wait_empty to wait out; only cpus_mask is updated so migrate_enable() ++ * will affine_move them. ++ */ ++static int bind_zero_one(struct task_struct *p, int src_cpu) ++{ ++ cpumask_t *user_mask, new_mask; ++ struct rq_flags rf; ++ struct rq *rq; ++ int dest, bound = 0; ++ ++ /* ++ * The override below cannot allocate under the runqueue lock, so ++ * speculatively provide it room for the old mask here. Almost every ++ * task takes the fast path and hands this straight back. ++ */ ++ user_mask = kmalloc(cpumask_size(), GFP_ATOMIC); ++ ++ rq = task_rq_lock(p, &rf); ++ ++ if (!bind_zero_skip_affinity(p, rq) && ++ cpumask_test_cpu(src_cpu, &p->cpus_mask)) { ++ cpumask_copy(&new_mask, &p->cpus_mask); ++ cpumask_clear_cpu(src_cpu, &new_mask); ++ if (!cpumask_intersects(&new_mask, cpu_active_mask)) { ++ dest = cpumask_any(cpu_active_mask); ++ if (dest >= nr_cpu_ids) ++ dest = cpumask_any(cpu_online_mask); ++ if (dest < nr_cpu_ids) { ++ struct affinity_context ac = { ++ .new_mask = &new_mask ++ }; ++ ++ /* ++ * Only the first override saves; a second one ++ * would only be replacing an already forced ++ * mask with another. What is being displaced ++ * is what has to come back, so it takes over ++ * any mask sched_setaffinity() left here - ++ * reusing its allocation - rather than let a ++ * request cpusets have since narrowed be ++ * restored wider than the cpuset allows. ++ */ ++ if (!p->zerobound) { ++ if (p->user_cpus_ptr) { ++ cpumask_copy(p->user_cpus_ptr, ++ &p->cpus_mask); ++ } else if (user_mask) { ++ cpumask_copy(user_mask, ++ &p->cpus_mask); ++ p->user_cpus_ptr = user_mask; ++ user_mask = NULL; ++ } ++ } ++ cpumask_set_cpu(dest, &new_mask); ++ p->zerobound = true; ++ __do_set_cpus_allowed(p, &ac); ++ bound = 1; ++ } ++ } ++ } ++ ++ if (task_cpu(p) == src_cpu && p != current && ++ !is_idle_task(p) && p != rq->stop && ++ !is_migration_disabled(p)) { ++ dest = valid_task_cpu(p); ++ if (dest != src_cpu) { ++ /* ++ * Only a queued task has to be moved. A blocked one is ++ * on no runqueue, so it does not hold @src_cpu up, and ++ * its destination must not be stamped into wake_cpu: ++ * that arms return_task()'s migration handshake, which ++ * would then fire on a task nobody is migrating. Its ++ * wakeup picks an allowed CPU by itself. ++ */ ++ if (task_queued(p)) ++ rq = move_queued_task(rq, &rf, p, dest); ++ } ++ } ++ ++ task_rq_unlock(rq, p, &rf); ++ kfree(user_mask); ++ return bound; ++} ++ ++/* ++ * Called from sched_cpu_wait_empty() (sleepable) on the outgoing CPU. ++ * Walk every task and move anyone still queued here. Affinity is only ++ * overridden for the few tasks @src_cpu leaving would otherwise strand, ++ * so on a normal offline this reports nothing. ++ */ ++static void bind_zero(int src_cpu) ++{ ++ struct task_struct *g, *p; ++ int bound = 0; ++ ++ if (src_cpu == 0) ++ return; ++ ++ rcu_read_lock(); ++ for_each_process_thread(g, p) { ++ if (task_cpu(p) != src_cpu && ++ !cpumask_test_cpu(src_cpu, p->cpus_ptr) && ++ !cpumask_test_cpu(src_cpu, &p->cpus_mask)) ++ continue; ++ bound += bind_zero_one(p, src_cpu); ++ } ++ rcu_read_unlock(); ++ ++ if (bound) { ++ printk(KERN_INFO "MuQSS overrode affinity for %d processes left with no active cpu by offlining cpu %d\n", ++ bound, src_cpu); ++ } ++} ++ ++/* ++ * Undo the affinity override bind_zero() had to force onto tasks that ++ * @src_cpu leaving would have stranded. Hold pi+rq so cpus_mask / ++ * nr_cpus_allowed stay in lockstep. Unbound kthreads are eligible — only ++ * per-CPU / PF_NO_SETAFFINITY threads were skipped on the way out. ++ * ++ * Restoring the saved mask can take the CPU the task is on right now away ++ * from it, so kick it off exactly like do_set_cpus_allowed() does. ++ */ ++static void unbind_zero(int src_cpu) ++{ ++ int restored = 0, unbound = 0; ++ struct task_struct *g, *p; ++ ++ if (src_cpu == 0) ++ return; ++ ++ rcu_read_lock(); ++ for_each_process_thread(g, p) { ++ struct rq_flags rf; ++ struct rq *rq; ++ ++ if (!p->zerobound) ++ continue; ++ ++ rq = task_rq_lock(p, &rf); ++ if (!p->zerobound) { ++ task_rq_unlock(rq, p, &rf); ++ continue; ++ } ++ ++ if (likely(p->user_cpus_ptr)) { ++ /* ++ * Wait for one of the CPUs the task actually wanted, ++ * or restoring would strand it all over again. The ++ * saved mask stays put once copied back: it is still ++ * what the task wants, and what a later override or ++ * relax_compatible_cpus_allowed_ptr() restores. ++ */ ++ if (cpumask_test_cpu(src_cpu, p->user_cpus_ptr)) { ++ struct affinity_context ac = { ++ .new_mask = p->user_cpus_ptr ++ }; ++ ++ __do_set_cpus_allowed(p, &ac); ++ p->zerobound = false; ++ restored++; ++ } ++ } else if (!cpumask_test_cpu(src_cpu, &p->cpus_mask)) { ++ /* ++ * bind_zero() had no room to save the old mask, so ++ * hand @src_cpu back and stop there rather than ++ * accumulate every CPU that ever comes online. ++ */ ++ cpumask_set_cpu(src_cpu, &p->cpus_mask); ++ p->nr_cpus_allowed = cpumask_weight(&p->cpus_mask); ++ p->zerobound = false; ++ unbound++; ++ } ++ ++ if (!p->zerobound && needs_other_cpu(p, task_cpu(p))) { ++ int dest = valid_task_cpu(p); ++ ++ /* ++ * As in bind_zero_one(), a blocked task is left alone ++ * rather than having @dest stamped into its wake_cpu, ++ * which would arm return_task()'s migration handshake ++ * for a migration that is not happening. ++ */ ++ if (task_queued(p)) ++ rq = move_queued_task(rq, &rf, p, dest); ++ else if (task_running(rq, p)) { ++ set_task_cpu(p, dest); ++ resched_task(p); ++ } ++ } ++ ++ task_rq_unlock(rq, p, &rf); ++ } ++ rcu_read_unlock(); ++ ++ if (restored) { ++ printk(KERN_INFO "MuQSS restored the original affinity of %d processes by onlining cpu %d\n", ++ restored, src_cpu); ++ } ++ if (unbound) { ++ printk(KERN_INFO "MuQSS added affinity for %d processes to cpu %d\n", ++ unbound, src_cpu); ++ } ++} ++ ++/* ++ * idle_task_exit() is an empty inline in now; the ++ * outgoing CPU switches back to init_mm from sched_cpu_wait_empty() instead. ++ */ ++#else /* CONFIG_HOTPLUG_CPU */ ++static void unbind_zero(int src_cpu) {} ++#endif /* CONFIG_HOTPLUG_CPU */ ++ ++void sched_set_stop_task(int cpu, struct task_struct *stop) ++{ ++ struct sched_param stop_param = { .sched_priority = STOP_PRIO }; ++ struct sched_param start_param = { .sched_priority = 0 }; ++ struct task_struct *old_stop = cpu_rq(cpu)->stop; ++ ++ if (stop) { ++ /* ++ * Make it appear like a SCHED_FIFO task, its something ++ * userspace knows about and won't get confused about. ++ * ++ * Also, it will make PI more or less work without too ++ * much confusion -- but then, stop work should not ++ * rely on PI working anyway. ++ */ ++ sched_setscheduler_nocheck(stop, SCHED_FIFO, &stop_param); ++ } ++ ++ cpu_rq(cpu)->stop = stop; ++ ++ if (old_stop) { ++ /* ++ * Reset it back to a normal scheduling policy so that ++ * it can die in pieces. ++ */ ++ sched_setscheduler_nocheck(old_stop, SCHED_NORMAL, &start_param); ++ } ++} ++ ++#if defined(CONFIG_SCHED_DEBUG) && defined(CONFIG_SYSCTL) ++ ++static struct ctl_table sd_ctl_dir[] = { ++ { ++ .procname = "sched_domain", ++ .mode = 0555, ++ }, ++ {} ++}; ++ ++static struct ctl_table sd_ctl_root[] = { ++ { ++ .procname = "kernel", ++ .mode = 0555, ++ .child = sd_ctl_dir, ++ }, ++ {} ++}; ++ ++static struct ctl_table *sd_alloc_ctl_entry(int n) ++{ ++ struct ctl_table *entry = ++ kcalloc(n, sizeof(struct ctl_table), GFP_KERNEL); ++ ++ return entry; ++} ++ ++static void sd_free_ctl_entry(struct ctl_table **tablep) ++{ ++ struct ctl_table *entry; ++ ++ /* ++ * In the intermediate directories, both the child directory and ++ * procname are dynamically allocated and could fail but the mode ++ * will always be set. In the lowest directory the names are ++ * static strings and all have proc handlers. ++ */ ++ for (entry = *tablep; entry->mode; entry++) { ++ if (entry->child) ++ sd_free_ctl_entry(&entry->child); ++ if (entry->proc_handler == NULL) ++ kfree(entry->procname); ++ } ++ ++ kfree(*tablep); ++ *tablep = NULL; ++} ++ ++static void ++set_table_entry(struct ctl_table *entry, ++ const char *procname, void *data, int maxlen, ++ umode_t mode, proc_handler *proc_handler) ++{ ++ entry->procname = procname; ++ entry->data = data; ++ entry->maxlen = maxlen; ++ entry->mode = mode; ++ entry->proc_handler = proc_handler; ++} ++ ++static struct ctl_table * ++sd_alloc_ctl_domain_table(struct sched_domain *sd) ++{ ++ struct ctl_table *table = sd_alloc_ctl_entry(9); ++ ++ if (table == NULL) ++ return NULL; ++ ++ set_table_entry(&table[0], "min_interval", &sd->min_interval, sizeof(long), 0644, proc_doulongvec_minmax); ++ set_table_entry(&table[1], "max_interval", &sd->max_interval, sizeof(long), 0644, proc_doulongvec_minmax); ++ set_table_entry(&table[2], "busy_factor", &sd->busy_factor, sizeof(int), 0644, proc_dointvec_minmax); ++ set_table_entry(&table[3], "imbalance_pct", &sd->imbalance_pct, sizeof(int), 0644, proc_dointvec_minmax); ++ set_table_entry(&table[4], "cache_nice_tries", &sd->cache_nice_tries, sizeof(int), 0644, proc_dointvec_minmax); ++ set_table_entry(&table[5], "flags", &sd->flags, sizeof(int), 0644, proc_dointvec_minmax); ++ set_table_entry(&table[6], "max_newidle_lb_cost", &sd->max_newidle_lb_cost, sizeof(long), 0644, proc_doulongvec_minmax); ++ set_table_entry(&table[7], "name", sd->name, CORENAME_MAX_SIZE, 0444, proc_dostring); ++ /* &table[8] is terminator */ ++ ++ return table; ++} ++ ++static struct ctl_table *sd_alloc_ctl_cpu_table(int cpu) ++{ ++ struct ctl_table *entry, *table; ++ struct sched_domain *sd; ++ int domain_num = 0, i; ++ char buf[32]; ++ ++ for_each_domain(cpu, sd) ++ domain_num++; ++ entry = table = sd_alloc_ctl_entry(domain_num + 1); ++ if (table == NULL) ++ return NULL; ++ ++ i = 0; ++ for_each_domain(cpu, sd) { ++ snprintf(buf, 32, "domain%d", i); ++ entry->procname = kstrdup(buf, GFP_KERNEL); ++ entry->mode = 0555; ++ entry->child = sd_alloc_ctl_domain_table(sd); ++ entry++; ++ i++; ++ } ++ return table; ++} ++ ++static cpumask_var_t sd_sysctl_cpus; ++static struct ctl_table_header *sd_sysctl_header; ++ ++void register_sched_domain_sysctl(void) ++{ ++ static struct ctl_table *cpu_entries; ++ static struct ctl_table **cpu_idx; ++ char buf[32]; ++ int i; ++ ++ if (!cpu_entries) { ++ cpu_entries = sd_alloc_ctl_entry(num_possible_cpus() + 1); ++ if (!cpu_entries) ++ return; ++ ++ WARN_ON(sd_ctl_dir[0].child); ++ sd_ctl_dir[0].child = cpu_entries; ++ } ++ ++ if (!cpu_idx) { ++ struct ctl_table *e = cpu_entries; ++ ++ cpu_idx = kcalloc(nr_cpu_ids, sizeof(struct ctl_table*), GFP_KERNEL); ++ if (!cpu_idx) ++ return; ++ ++ /* deal with sparse possible map */ ++ for_each_possible_cpu(i) { ++ cpu_idx[i] = e; ++ e++; ++ } ++ } ++ ++ if (!cpumask_available(sd_sysctl_cpus)) { ++ if (!alloc_cpumask_var(&sd_sysctl_cpus, GFP_KERNEL)) ++ return; ++ ++ /* init to possible to not have holes in @cpu_entries */ ++ cpumask_copy(sd_sysctl_cpus, cpu_possible_mask); ++ } ++ ++ for_each_cpu(i, sd_sysctl_cpus) { ++ struct ctl_table *e = cpu_idx[i]; ++ ++ if (e->child) ++ sd_free_ctl_entry(&e->child); ++ ++ if (!e->procname) { ++ snprintf(buf, 32, "cpu%d", i); ++ e->procname = kstrdup(buf, GFP_KERNEL); ++ } ++ e->mode = 0555; ++ e->child = sd_alloc_ctl_cpu_table(i); ++ ++ __cpumask_clear_cpu(i, sd_sysctl_cpus); ++ } ++ ++ WARN_ON(sd_sysctl_header); ++ sd_sysctl_header = register_sysctl_table(sd_ctl_root); ++} ++ ++void dirty_sched_domain_sysctl(int cpu) ++{ ++ if (cpumask_available(sd_sysctl_cpus)) ++ __cpumask_set_cpu(cpu, sd_sysctl_cpus); ++} ++ ++/* may be called multiple times per register */ ++void unregister_sched_domain_sysctl(void) ++{ ++ unregister_sysctl_table(sd_sysctl_header); ++ sd_sysctl_header = NULL; ++} ++#endif /* CONFIG_SYSCTL */ ++ ++void set_rq_online(struct rq *rq) ++{ ++ if (!rq->online) { ++ cpumask_set_cpu(cpu_of(rq), rq->rd->online); ++ rq->online = true; ++ } ++} ++ ++void set_rq_offline(struct rq *rq) ++{ ++ if (rq->online) { ++ int cpu = cpu_of(rq); ++ ++ cpumask_clear_cpu(cpu, rq->rd->online); ++ rq->online = false; ++ clear_cpuidle_map(cpu); ++ } ++} ++ ++/* ++ * used to mark begin/end of suspend/resume: ++ */ ++static int num_cpus_frozen; ++ ++/* ++ * Update cpusets according to cpu_active mask. If cpusets are ++ * disabled, cpuset_update_active_cpus() becomes a simple wrapper ++ * around partition_sched_domains(). ++ * ++ * If we come here as part of a suspend/resume, don't touch cpusets because we ++ * want to restore it back to its original state upon resume anyway. ++ */ ++static void cpuset_cpu_active(void) ++{ ++ if (cpuhp_tasks_frozen) { ++ /* ++ * num_cpus_frozen tracks how many CPUs are involved in suspend ++ * resume sequence. As long as this is not the last online ++ * operation in the resume sequence, just build a single sched ++ * domain, ignoring cpusets. ++ */ ++ partition_sched_domains(1, NULL, NULL); ++ if (--num_cpus_frozen) ++ return; ++ /* ++ * This is the last CPU online operation. So fall through and ++ * restore the original sched domains by considering the ++ * cpuset configurations. ++ */ ++ cpuset_force_rebuild(); ++ } ++ ++ cpuset_update_active_cpus(); ++} ++ ++static int cpuset_cpu_inactive(unsigned int cpu) ++{ ++ if (!cpuhp_tasks_frozen) { ++ cpuset_update_active_cpus(); ++ } else { ++ num_cpus_frozen++; ++ partition_sched_domains(1, NULL, NULL); ++ } ++ return 0; ++} ++ ++int sched_cpu_activate(unsigned int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ ++ /* ++ * When going up, increment the number of cores with SMT present. ++ * cpu_smt_mask() is cpumask_of(cpu) when !CONFIG_SCHED_SMT, so this ++ * never fires there. ++ */ ++ if (cpumask_weight(cpu_smt_mask(cpu)) == 2) ++ static_branch_inc_cpuslocked(&sched_smt_present); ++ ++ set_cpu_active(cpu, true); ++ ++ if (sched_smp_initialized) { ++ sched_domains_numa_masks_set(cpu); ++ cpuset_cpu_active(); ++ } ++ ++ /* ++ * Put the rq online, if not already. This happens: ++ * ++ * 1) In the early boot process, because we build the real domains ++ * after all CPUs have been brought up. ++ * ++ * 2) At runtime, if cpuset_cpu_active() fails to rebuild the ++ * domains. ++ */ ++ rq_lock_irqsave(rq, &rf); ++ if (rq->rd) { ++ BUG_ON(!cpumask_test_cpu(cpu, rq->rd->span)); ++ set_rq_online(rq); ++ } ++ rq_unlock_irqrestore(rq, &rf); ++ /* ++ * unbind_zero() takes each task's pi+rq lock; do not nest that ++ * under this CPU's rq lock. ++ */ ++ unbind_zero(cpu); ++ ++ return 0; ++} ++ ++int sched_cpu_deactivate(unsigned int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ int ret; ++ ++ set_cpu_active(cpu, false); ++ /* ++ * We've cleared cpu_active_mask, wait for all preempt-disabled and RCU ++ * users of this state to go away such that all new such users will ++ * observe it. ++ * ++ * Do sync before park smpboot threads to take care the rcu boost case. ++ */ ++ synchronize_rcu(); ++ ++ sched_domains_free_llc_id(cpu); ++ ++ rq_lock_irqsave(rq, &rf); ++ if (rq->rd) { ++ update_rq_clock(rq); ++ BUG_ON(!cpumask_test_cpu(cpu, rq->rd->span)); ++ set_rq_offline(rq); ++ } ++ rq_unlock_irqrestore(rq, &rf); ++ ++ /* ++ * When going down, decrement the number of cores with SMT present. ++ */ ++ if (cpumask_weight(cpu_smt_mask(cpu)) == 2) ++ static_branch_dec_cpuslocked(&sched_smt_present); ++ ++ if (!sched_smp_initialized) ++ return 0; ++ ++ ret = cpuset_cpu_inactive(cpu); ++ if (ret) { ++ set_cpu_active(cpu, true); ++ return ret; ++ } ++ sched_domains_numa_masks_clear(cpu); ++ return 0; ++} ++ ++int sched_cpu_starting(unsigned int cpu) ++{ ++ sched_tick_start(cpu); ++ return 0; ++} ++ ++#ifdef CONFIG_HOTPLUG_CPU ++/* ++ * Invoked on the outgoing CPU in context of the CPU hotplug thread after ++ * ensuring that there are no user space tasks left on the CPU. ++ * ++ * If there is a lazy mm in use on the hotplug thread, drop it and switch to ++ * init_mm. finish_cpu() on the control CPU drops the init_mm reference and ++ * WARNs if we left anything else behind. 5.12 did this from idle_task_exit(); ++ * mainline now does it here from sched_cpu_wait_empty(). ++ */ ++static void __sched_force_init_mm(void) ++{ ++ struct mm_struct *mm = current->active_mm; ++ ++ if (mm == &init_mm) ++ return; ++ ++ mmgrab_lazy_tlb(&init_mm); ++ current->active_mm = &init_mm; ++ switch_mm_irqs_off(mm, &init_mm, current); ++ finish_arch_post_lock_switch(); ++ mmdrop_lazy_tlb(mm); ++} ++ ++static void sched_force_init_mm(void) ++{ ++ unsigned long flags; ++ ++ local_irq_save(flags); ++ __sched_force_init_mm(); ++ local_irq_restore(flags); ++} ++ ++static void dump_rq_tasks(struct rq *rq, const char *loglvl) ++{ ++ struct task_struct *g, *p; ++ int cpu = cpu_of(rq); ++ ++ lockdep_assert_rq_held(rq); ++ ++ printk("%sCPU%d tasks (nr_running=%u nr_pinned=%u):\n", ++ loglvl, cpu, rq->nr_running, rq->nr_pinned); ++ rcu_read_lock(); ++ for_each_process_thread(g, p) { ++ if (task_cpu(p) != cpu) ++ continue; ++ printk("%s\tpid: %d, name: %s, queued: %d, on_rq: %d\n", ++ loglvl, p->pid, p->comm, task_queued(p), p->on_rq); ++ } ++ rcu_read_unlock(); ++} ++ ++/* ++ * Invoked on the outgoing CPU after per-CPU kthreads have been parked. ++ * bind_zero() takes affinity off this CPU and moves anyone still queued ++ * here. Then wait until only this hotplug thread remains and every ++ * migrate_disable() pin has dropped, so finish_cpu() cannot see a user mm. ++ */ ++int sched_cpu_wait_empty(unsigned int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ ++ WARN_ON_ONCE(cpu != smp_processor_id()); ++ ++ for (;;) { ++ bind_zero(cpu); ++ if (READ_ONCE(rq->nr_running) <= 1 && !READ_ONCE(rq->nr_pinned)) ++ break; ++ schedule_timeout_uninterruptible(1); ++ } ++ ++ sched_force_init_mm(); ++ return 0; ++} ++ ++int sched_cpu_dying(unsigned int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ ++ /* Handle pending wakeups; tasks were already moved in wait_empty. */ ++ sched_tick_stop(cpu); ++ ++ rq_lock_irqsave(rq, &rf); ++ if (rq->rd) { ++ BUG_ON(!cpumask_test_cpu(cpu, rq->rd->span)); ++ set_rq_offline(rq); ++ } ++ if (rq->nr_running > 1 || rq->nr_pinned) { ++ WARN(true, "Dying CPU not properly vacated!"); ++ dump_rq_tasks(rq, KERN_WARNING); ++ } ++ rq_unlock_irqrestore(rq, &rf); ++ ++ sched_start_tick(rq, cpu); ++ hrexpiry_clear(rq); ++ /* ++ * Belt-and-suspenders: wait_empty already switched to init_mm, ++ * but a preempting migrate_disable() task could have left a ++ * lazy user mm on current. Drop it again now that this is the ++ * last thing to run before idle takes over. ++ */ ++ local_irq_disable(); ++ __sched_force_init_mm(); ++ local_irq_enable(); ++ ++ return 0; ++} ++#endif ++ ++#if defined(CONFIG_SCHED_SMT) || defined(CONFIG_SCHED_MC) ++/* ++ * Cheaper version of the below functions in case support for SMT and MC is ++ * compiled in but CPUs have no siblings. ++ */ ++static bool sole_cpu_idle(struct rq *rq) ++{ ++ return rq_idle(rq); ++} ++#endif ++#ifdef CONFIG_SCHED_SMT ++static const cpumask_t *thread_cpumask(int cpu) ++{ ++ return topology_sibling_cpumask(cpu); ++} ++#endif ++#ifdef CONFIG_SCHED_MC ++static const cpumask_t *core_cpumask(int cpu) ++{ ++ return topology_core_cpumask(cpu); ++} ++/* All this CPU's shared cache siblings are idle */ ++static bool cache_cpu_idle(struct rq *rq) ++{ ++ return cpumask_subset(&rq->core_mask, &cpu_idle_map); ++} ++/* MC siblings CPU mask which share the same LLC */ ++static const cpumask_t *llc_core_cpumask(int cpu) ++{ ++#ifdef CONFIG_X86 ++ return per_cpu(cpu_llc_shared_map, cpu); ++#else ++ return topology_core_cpumask(cpu); ++#endif ++} ++#endif ++ ++enum sched_domain_level { ++ SD_LV_NONE = 0, ++ SD_LV_SIBLING, ++ SD_LV_MC, ++ SD_LV_BOOK, ++ SD_LV_CPU, ++ SD_LV_NODE, ++ SD_LV_ALLNODES, ++ SD_LV_MAX ++}; ++ ++#ifdef CONFIG_SMT_NICE ++/* ++ * Recorded by select_leaders() and consumed by sched_init_smp() once the ++ * runqueue locks have been dropped. See the comment where it is set. ++ */ ++static bool __initdata smt_nice_needed; ++#endif ++ ++/* ++ * Set up the relative cache distance of each online cpu from each ++ * other in a simple array for quick lookup. Locality is determined ++ * by the closest sched_domain that CPUs are separated by. CPUs with ++ * shared cache in SMT and MC are treated as local. Separate CPUs ++ * (within the same package or physically) within the same node are ++ * treated as not local. CPUs not even in the same domain (different ++ * nodes) are treated as very distant. ++ * ++ * Called with interrupts disabled and every runqueue lock held, so nothing ++ * here may sleep or wait on another CPU. ++ */ ++static void __init select_leaders(void) ++{ ++ struct rq *rq, *other_rq, *leader; ++ struct sched_domain *sd; ++ int cpu, other_cpu; ++#ifdef CONFIG_SCHED_SMT ++ bool smt_threads = false; ++#endif ++ ++ for (cpu = 0; cpu < num_online_cpus(); cpu++) { ++ rq = cpu_rq(cpu); ++ leader = NULL; ++ /* First check if this cpu is in the same node */ ++ for_each_domain(cpu, sd) { ++ if (sd->level > SD_LV_MC) ++ continue; ++ if (rqshare != RQSHARE_ALL) ++ leader = NULL; ++ /* Set locality to local node if not already found lower */ ++ for_each_cpu(other_cpu, sched_domain_span(sd)) { ++ if (rqshare >= RQSHARE_SMP) { ++ other_rq = cpu_rq(other_cpu); ++ ++ /* Set the smp_leader to the first CPU */ ++ if (!leader) ++ leader = rq; ++ if (!other_rq->smp_leader) ++ other_rq->smp_leader = leader; ++ } ++ if (rq->cpu_locality[other_cpu] > LOCALITY_SMP) ++ rq->cpu_locality[other_cpu] = LOCALITY_SMP; ++ } ++ } ++ ++ /* ++ * Each runqueue has its own function in case it doesn't have ++ * siblings of its own allowing mixed topologies. ++ */ ++#ifdef CONFIG_SCHED_MC ++ leader = NULL; ++ if (cpumask_weight(core_cpumask(cpu)) > 1) { ++ cpumask_copy(&rq->core_mask, llc_core_cpumask(cpu)); ++ cpumask_clear_cpu(cpu, &rq->core_mask); ++ for_each_cpu(other_cpu, core_cpumask(cpu)) { ++ if (rqshare == RQSHARE_MC || ++ (rqshare == RQSHARE_MC_LLC && cpumask_test_cpu(other_cpu, llc_core_cpumask(cpu)))) { ++ other_rq = cpu_rq(other_cpu); ++ ++ /* Set the mc_leader to the first CPU */ ++ if (!leader) ++ leader = rq; ++ if (!other_rq->mc_leader) ++ other_rq->mc_leader = leader; ++ } ++ if (rq->cpu_locality[other_cpu] > LOCALITY_MC) { ++ /* this is to get LLC into play even in case LLC sharing is not used */ ++ if (cpumask_test_cpu(other_cpu, llc_core_cpumask(cpu))) ++ rq->cpu_locality[other_cpu] = LOCALITY_MC_LLC; ++ else ++ rq->cpu_locality[other_cpu] = LOCALITY_MC; ++ } ++ } ++ rq->cache_idle = cache_cpu_idle; ++ } ++#endif ++#ifdef CONFIG_SCHED_SMT ++ leader = NULL; ++ if (cpumask_weight(thread_cpumask(cpu)) > 1) { ++ cpumask_copy(&rq->thread_mask, thread_cpumask(cpu)); ++ cpumask_clear_cpu(cpu, &rq->thread_mask); ++ for_each_cpu(other_cpu, thread_cpumask(cpu)) { ++ if (rqshare == RQSHARE_SMT) { ++ other_rq = cpu_rq(other_cpu); ++ ++ /* Set the smt_leader to the first CPU */ ++ if (!leader) ++ leader = rq; ++ if (!other_rq->smt_leader) ++ other_rq->smt_leader = leader; ++ } ++ if (rq->cpu_locality[other_cpu] > LOCALITY_SMT) ++ rq->cpu_locality[other_cpu] = LOCALITY_SMT; ++ } ++ rq->has_smt_sibling = true; ++ smt_threads = true; ++ } ++#endif ++ } ++ ++#ifdef CONFIG_SMT_NICE ++ /* ++ * Only record it here; sched_init_smp() turns SMT nice on once it has ++ * dropped the runqueue locks and re-enabled interrupts. ++ * ++ * static_branch_enable() must not be called from this context. It ++ * takes cpus_read_lock() and jump_label_mutex, either of which can ++ * sleep - and scheduling from here would try to take a runqueue lock ++ * this CPU already holds - and it then patches the jump site with ++ * text_poke_bp(), which waits for every other CPU to answer a sync ++ * IPI. Any CPU spinning on one of the runqueue locks held here has ++ * interrupts disabled and can never answer, so the wait never ends and ++ * the machine is gone with nothing on the console. ++ */ ++ smt_nice_needed = smt_threads; ++#endif ++ ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ for_each_online_cpu(other_cpu) { ++ printk(KERN_DEBUG "MuQSS locality CPU %d to %d: %d\n", cpu, other_cpu, rq->cpu_locality[other_cpu]); ++ } ++ } ++} ++ ++/* ++ * Fold @rq into @leader while all runqueue locks are already held by ++ * lock_all_rqs(). Secondary CPUs may already have tasks queued by the time ++ * sched_init_smp() runs (hotplug kthreads, RCU, early kworkers), so the ++ * skiplist must be drained onto the leader before its storage is freed. ++ * ++ * Lock handoff: rq_lock() is a plain raw_spin_lock(rq->lock) with no re-check, ++ * so a CPU that had already read the follower's lock pointer would acquire the ++ * old lock after we drop it and then walk the leader's skiplist without the ++ * leader's lock. share_rqs() therefore runs from stop_machine(), where no ++ * other CPU can be inside or waiting on any rq lock, which is what makes ++ * repointing rq->lock and freeing the old one safe. ++ */ ++static void __init share_and_free_rq(struct rq *leader, struct rq *rq) ++{ ++ raw_spinlock_t *old_lock = rq->lock; ++ skiplist_node *old_node = rq->node; ++ skiplist *old_sl = rq->sl; ++ ++ /* ++ * Move every queued task onto the leader skiplist. nr_running is ++ * accounted against the rq the task belongs to (task_rq(p), which is ++ * unchanged here) rather than the rq owning the skiplist, so hand the ++ * counts straight back or the follower underflows on the next dequeue ++ * and the leader keeps a phantom entry forever. ++ */ ++ while (rq->sl->entries > 0) { ++ struct task_struct *p = container_of(rq->node->next[0], ++ struct task_struct, node); ++ ++ dequeue_task(rq, p, DEQUEUE_SAVE); ++ enqueue_task(leader, p, ENQUEUE_RESTORE); ++ leader->nr_running--; ++ rq->nr_running++; ++ if (rt_task(p)) { ++ leader->rt_nr_running--; ++ rq->rt_nr_running++; ++ } ++ } ++ ++ /* ++ * A non-idle curr is not on the skiplist but still accounts for one ++ * nr_running. Leave that count on @rq; only the shared skiplist and ++ * lock are merged. ++ */ ++ WARN_ON_ONCE(rq->sl->entries != 0); ++ ++ /* Point the follower at the leader's skiplist and lock. */ ++ rq->node = leader->node; ++ rq->sl = leader->sl; ++ rq->lock = leader->lock; ++ rq->is_leader = false; ++ ++ /* ++ * Drop the follower's private lock, taken by lock_all_rqs(). Only the ++ * leader lock remains held for @rq, and nothing can be waiting on the ++ * old one, so free it. ++ */ ++ do_raw_spin_unlock(old_lock); ++ ++ kfree(old_node); ++ skiplist_free(old_sl); ++ kfree(old_lock); ++} ++ ++/* ++ * Called with every runqueue lock held via lock_all_rqs() and IRQs off. ++ * Must not take locks again. After this, only leader rqs own a unique lock; ++ * unlock_all_rqs() is replaced by unlock_leader_rqs(). ++ */ ++static void __init share_rqs(void) ++{ ++ struct rq *rq, *leader; ++ int cpu; ++ ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ leader = rq->smp_leader; ++ ++ if (leader && rq != leader) { ++ printk(KERN_INFO "MuQSS sharing SMP runqueue from CPU %d to CPU %d\n", ++ leader->cpu, rq->cpu); ++ share_and_free_rq(leader, rq); ++ } ++ } ++ ++#ifdef CONFIG_SCHED_MC ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ leader = rq->mc_leader; ++ ++ if (leader && rq != leader) { ++ printk(KERN_INFO "MuQSS sharing MC runqueue from CPU %d to CPU %d\n", ++ leader->cpu, rq->cpu); ++ share_and_free_rq(leader, rq); ++ } ++ } ++#endif /* CONFIG_SCHED_MC */ ++ ++#ifdef CONFIG_SCHED_SMT ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ leader = rq->smt_leader; ++ ++ if (leader && rq != leader) { ++ printk(KERN_INFO "MuQSS sharing SMT runqueue from CPU %d to CPU %d\n", ++ leader->cpu, rq->cpu); ++ share_and_free_rq(leader, rq); ++ } ++ } ++#endif /* CONFIG_SCHED_SMT */ ++} ++ ++/* Unlock each unique runqueue lock once after share_rqs(). */ ++static inline void unlock_leader_rqs(void) ++{ ++ int cpu; ++ ++ for_each_possible_cpu(cpu) { ++ struct rq *rq = cpu_rq(cpu); ++ ++ if (rq->is_leader) ++ do_raw_spin_unlock(rq->lock); ++ } ++ preempt_enable(); ++} ++ ++/* ++ * Fold the runqueues from stop_machine() context. Every other CPU is parked ++ * in the stopper with interrupts disabled, so none of them is inside an rq ++ * lock or spinning on one while share_and_free_rq() repoints a follower's ++ * rq->lock at its leader and frees the old lock. Any single CPU can run this. ++ */ ++static int __init share_rqs_stopper(void *unused) ++{ ++ lock_all_rqs(); ++ share_rqs(); ++ unlock_leader_rqs(); ++ ++ return 0; ++} ++ ++static void __init set_rq_order(struct rq *rq, int idx, struct rq *other) ++{ ++ rq->rq_order[idx] = other; ++ rq->sl_order[idx] = other->sl; ++} ++ ++static void __init setup_rq_orders(void) ++{ ++ int *selected_cpus, *ordered_cpus; ++ struct rq *rq, *other_rq; ++ int cpu, other_cpu, i; ++ ++ selected_cpus = kmalloc(sizeof(int) * NR_CPUS, GFP_ATOMIC); ++ ordered_cpus = kmalloc(sizeof(int) * NR_CPUS, GFP_ATOMIC); ++ ++ total_runqueues = 0; ++ for_each_online_cpu(cpu) { ++ int locality, total_rqs = 0, total_cpus = 0; ++ ++ rq = cpu_rq(cpu); ++ if (rq->is_leader) ++ total_runqueues++; ++ ++ for (locality = LOCALITY_SAME; locality <= LOCALITY_DISTANT; locality++) { ++ int selected_cpu_cnt, selected_cpu_idx, test_cpu_idx, cpu_idx, best_locality, test_cpu; ++ int ordered_cpus_idx; ++ ++ ordered_cpus_idx = -1; ++ selected_cpu_cnt = 0; ++ ++ for_each_online_cpu(test_cpu) { ++ if (cpu < num_online_cpus() / 2) ++ other_cpu = cpu + test_cpu; ++ else ++ other_cpu = cpu - test_cpu; ++ if (other_cpu < 0) ++ other_cpu += num_online_cpus(); ++ else ++ other_cpu %= num_online_cpus(); ++ /* gather CPUs of the same locality */ ++ if (rq->cpu_locality[other_cpu] == locality) { ++ selected_cpus[selected_cpu_cnt] = other_cpu; ++ selected_cpu_cnt++; ++ } ++ } ++ ++ /* reserve first CPU as starting point */ ++ if (selected_cpu_cnt > 0) { ++ ordered_cpus_idx++; ++ ordered_cpus[ordered_cpus_idx] = selected_cpus[ordered_cpus_idx]; ++ selected_cpus[ordered_cpus_idx] = -1; ++ } ++ ++ /* take each CPU and sort it within the same locality based on each inter-CPU localities */ ++ for (test_cpu_idx = 1; test_cpu_idx < selected_cpu_cnt; test_cpu_idx++) { ++ /* starting point with worst locality and current CPU */ ++ best_locality = LOCALITY_DISTANT; ++ selected_cpu_idx = test_cpu_idx; ++ ++ /* try to find the best locality within group */ ++ for (cpu_idx = 1; cpu_idx < selected_cpu_cnt; cpu_idx++) { ++ /* if CPU has not been used and locality is better */ ++ if (selected_cpus[cpu_idx] > -1) { ++ other_rq = cpu_rq(ordered_cpus[ordered_cpus_idx]); ++ if (best_locality > other_rq->cpu_locality[selected_cpus[cpu_idx]]) { ++ /* assign best locality and best CPU idx in array */ ++ best_locality = other_rq->cpu_locality[selected_cpus[cpu_idx]]; ++ selected_cpu_idx = cpu_idx; ++ } ++ } ++ } ++ ++ /* add our next best CPU to ordered list */ ++ ordered_cpus_idx++; ++ ordered_cpus[ordered_cpus_idx] = selected_cpus[selected_cpu_idx]; ++ /* mark this CPU as used */ ++ selected_cpus[selected_cpu_idx] = -1; ++ } ++ ++ /* set up RQ and CPU orders */ ++ for (test_cpu = 0; test_cpu <= ordered_cpus_idx; test_cpu++) { ++ other_rq = cpu_rq(ordered_cpus[test_cpu]); ++ /* set up cpu orders */ ++ rq->cpu_order[total_cpus++] = other_rq; ++ if (other_rq->is_leader) { ++ /* set up RQ orders */ ++ set_rq_order(rq, total_rqs++, other_rq); ++ } ++ } ++ } ++ } ++ ++ kfree(selected_cpus); ++ kfree(ordered_cpus); ++ ++#ifdef CONFIG_X86 ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ for (i = 0; i < total_runqueues; i++) { ++ printk(KERN_DEBUG "MuQSS CPU %d llc %d RQ order %d RQ %d llc %d\n", cpu, per_cpu_llc_id(cpu), i, ++ rq->rq_order[i]->cpu, per_cpu_llc_id(rq->rq_order[i]->cpu)); ++ } ++ } ++ ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ for (i = 0; i < num_online_cpus(); i++) { ++ printk(KERN_DEBUG "MuQSS CPU %d llc %d CPU order %d RQ %d llc %d\n", cpu, per_cpu_llc_id(cpu), i, ++ rq->cpu_order[i]->cpu, per_cpu_llc_id(rq->cpu_order[i]->cpu)); ++ } ++ } ++#endif ++} ++ ++void __init sched_init_smp(void) ++{ ++ sched_init_numa(NUMA_NO_NODE); ++ ++ /* ++ * There's no userspace yet to cause hotplug operations; hence all the ++ * cpu masks are stable and all blatant races in the below code cannot ++ * happen. ++ */ ++ mutex_lock(&sched_domains_mutex); ++ sched_init_domains(cpu_active_mask); ++ mutex_unlock(&sched_domains_mutex); ++ ++ /* Move init over to a non-isolated CPU */ ++ if (set_cpus_allowed_ptr(current, housekeeping_cpumask(HK_TYPE_DOMAIN)) < 0) ++ BUG(); ++ ++ /* ++ * Take the sleeping lock before disabling interrupts - the 5.12 order ++ * trips "sleeping function called from invalid context" under ++ * CONFIG_DEBUG_ATOMIC_SLEEP. select_leaders() walks the domain tree, ++ * which is what the mutex is for. ++ */ ++ mutex_lock(&sched_domains_mutex); ++ local_irq_disable(); ++ lock_all_rqs(); ++ ++ printk(KERN_INFO "MuQSS possible/present/online CPUs: %d/%d/%d\n", ++ num_possible_cpus(), num_present_cpus(), num_online_cpus()); ++ ++ select_leaders(); ++ ++ unlock_all_rqs(); ++ local_irq_enable(); ++ mutex_unlock(&sched_domains_mutex); ++ ++#ifdef CONFIG_SMT_NICE ++ /* ++ * Safe to patch the jump site only now that the runqueue locks are ++ * dropped and interrupts are back on; see select_leaders(). Until ++ * this point smt_schedule() just returns true and check_siblings()/ ++ * wake_siblings() are the no-op variants, which is correct behaviour, ++ * merely without SMT nice. ++ */ ++ if (smt_nice_needed) { ++ check_siblings = &check_smt_siblings; ++ wake_siblings = &wake_smt_siblings; ++ static_branch_enable(&smt_nice_enabled); ++ } ++#endif ++ ++ /* ++ * Only now fold the runqueues together, and do it from stop_machine(): ++ * 7.1 has the secondary CPUs running by this point, so a follower's rq ++ * lock can be held - or waited on - by another CPU exactly while it is ++ * handed over to the leader and freed. Quiescing everybody closes that ++ * window without putting a re-check in the rq_lock() fast path. Note ++ * stop_machine() takes cpus_read_lock(), so it must not nest inside ++ * sched_domains_mutex. ++ */ ++ stop_machine(share_rqs_stopper, NULL, cpumask_of(raw_smp_processor_id())); ++ ++ setup_rq_orders(); ++ ++ switch (rqshare) { ++ case RQSHARE_ALL: ++ /* This should only ever read 1 */ ++ printk(KERN_INFO "MuQSS runqueue share type ALL total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_SMP: ++ printk(KERN_INFO "MuQSS runqueue share type SMP total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_MC: ++ printk(KERN_INFO "MuQSS runqueue share type MC total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_MC_LLC: ++ printk(KERN_INFO "MuQSS runqueue share type LLC total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_SMT: ++ printk(KERN_INFO "MuQSS runqueue share type SMT total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_NONE: ++ printk(KERN_INFO "MuQSS runqueue share type NONE total runqueues: %d\n", ++ total_runqueues); ++ break; ++ } ++ ++ sched_smp_initialized = true; ++} ++#else /* !CONFIG_SMP */ ++void __init sched_init_smp(void) ++{ ++ sched_smp_initialized = true; ++} ++ ++/* ++ * 7.1 dropped the !CONFIG_SMP inline stubs these used to have in ++ * , so the scheduler has to provide them on UP too. There is ++ * only ever cpu 0 to run on, so affinity is either trivially satisfied or ++ * impossible. ++ */ ++int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask) ++{ ++ if (!cpumask_test_cpu(0, new_mask)) ++ return -EINVAL; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(set_cpus_allowed_ptr); ++ ++void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask) ++{ ++ cpumask_copy(&p->cpus_mask, new_mask); ++ p->nr_cpus_allowed = cpumask_weight(new_mask); ++} ++ ++#ifdef CONFIG_NO_HZ_COMMON ++void nohz_balance_enter_idle(int cpu) {} ++ ++/* ++ * The only CPU is the one already running this, so it is by definition not ++ * idle and needs no kick. ++ */ ++void wake_up_nohz_cpu(int cpu) ++{ ++} ++#endif /* CONFIG_NO_HZ_COMMON */ ++ ++/* ++ * topology.c is not built on UP, but declares this ++ * unconditionally and amd-pstate calls it. There are no sched domains to ++ * update. ++ */ ++void sched_update_asym_prefer_cpu(int cpu, int old_prio, int new_prio) ++{ ++} ++#endif /* CONFIG_SMP */ ++ ++int in_sched_functions(unsigned long addr) ++{ ++ return in_lock_functions(addr) || ++ (addr >= (unsigned long)__sched_text_start ++ && addr < (unsigned long)__sched_text_end); ++} ++ ++#ifdef CONFIG_CGROUP_SCHED ++/* ++ * Default CFS bandwidth period (100ms). Used only for accept-and-ignore ++ * readback of cpu.max / cfs_period_us — MuQSS does not enforce quotas. ++ */ ++#define MUQSS_CGROUP_PERIOD_DFL_US 100000ULL ++ ++/* task group related information */ ++struct task_group { ++ struct cgroup_subsys_state css; ++ ++ struct rcu_head rcu; ++ struct list_head list; ++ ++ struct task_group *parent; ++ struct list_head siblings; ++ struct list_head children; ++ ++ /* ++ * cpu controller knobs stored for ABI/readback only. Writes are ++ * validated then ignored for scheduling — MuQSS has no group fairness ++ * or bandwidth enforcement. Keeps systemd/docker/podman happy. ++ */ ++ unsigned long weight; /* cgroup weight [CGROUP_WEIGHT_MIN, MAX] */ ++ s64 idle; ++ u64 period_us; ++ s64 quota_us; /* -1 == unlimited ("max") */ ++ u64 burst_us; ++}; ++ ++/* ++ * Default task group. ++ * Every task in system belongs to this group at bootup. ++ */ ++struct task_group root_task_group; ++LIST_HEAD(task_groups); ++ ++/* Cacheline aligned slab cache for task_group */ ++static struct kmem_cache *task_group_cache __read_mostly; ++ ++static void init_tg_cgroup_defaults(struct task_group *tg) ++{ ++ tg->weight = CGROUP_WEIGHT_DFL; ++ tg->idle = 0; ++ tg->period_us = MUQSS_CGROUP_PERIOD_DFL_US; ++ tg->quota_us = -1; ++ tg->burst_us = 0; ++} ++#endif /* CONFIG_CGROUP_SCHED */ ++ ++void __init sched_init(void) ++{ ++#ifdef CONFIG_SMP ++ int cpu_ids; ++#endif ++ int i; ++ struct rq *rq; ++ ++ wait_bit_init(); ++ ++ prio_ratios[0] = 128; ++ for (i = 1 ; i < NICE_WIDTH ; i++) ++ prio_ratios[i] = prio_ratios[i - 1] * 11 / 10; ++ ++ skiplist_node_init(&init_task.node); ++ ++#ifdef CONFIG_SMP ++ init_defrootdomain(); ++ cpumask_clear(&cpu_idle_map); ++#else ++ uprq = &per_cpu(runqueues, 0); ++#endif ++ ++#ifdef CONFIG_CGROUP_SCHED ++ task_group_cache = KMEM_CACHE(task_group, 0); ++ ++ list_add(&root_task_group.list, &task_groups); ++ INIT_LIST_HEAD(&root_task_group.children); ++ INIT_LIST_HEAD(&root_task_group.siblings); ++ init_tg_cgroup_defaults(&root_task_group); ++#endif /* CONFIG_CGROUP_SCHED */ ++ skiplist_cache_init(); ++ for_each_possible_cpu(i) { ++ rq = cpu_rq(i); ++ rq->node = kmalloc(sizeof(skiplist_node), GFP_ATOMIC); ++ skiplist_init(rq->node); ++ rq->sl = new_skiplist(rq->node); ++ rq->lock = kmalloc(sizeof(raw_spinlock_t), GFP_ATOMIC); ++ raw_spin_lock_init(rq->lock); ++ rq->nr_running = 0; ++ rq->nr_uninterruptible = 0; ++ rq->nr_switches = 0; ++ rq->clock = rq->niffies = rq->jiffy_niffies = 0; ++ rq->last_jiffy = jiffies; ++ rq->user_ns = rq->nice_ns = rq->softirq_ns = rq->system_ns = ++ rq->iowait_ns = rq->idle_ns = 0; ++ rq->dither = 0; ++ set_rq_task(rq, &init_task); ++ rq->iso_ticks = 0; ++ rq->iso_refractory = false; ++#ifdef CONFIG_SMP ++ rq->is_leader = true; ++ rq->smp_leader = NULL; ++#ifdef CONFIG_SCHED_MC ++ rq->mc_leader = NULL; ++#endif ++#ifdef CONFIG_SCHED_SMT ++ rq->smt_leader = NULL; ++#endif ++ rq->sd = NULL; ++ rq->rd = NULL; ++ rq->online = false; ++ rq->cpu = i; ++ rq_attach_root(rq, &def_root_domain); ++#endif /* CONFIG_SMP */ ++ init_rq_hrexpiry(rq); ++ atomic_set(&rq->nr_iowait, 0); ++ } ++ ++#ifdef CONFIG_SMP ++ cpu_ids = i; ++ /* ++ * Set the base locality for cpu cache distance calculation to ++ * "distant" (3). Make sure the distance from a CPU to itself is 0. ++ */ ++ for_each_possible_cpu(i) { ++ int j; ++ ++ rq = cpu_rq(i); ++#ifdef CONFIG_SCHED_MC ++ rq->cache_idle = sole_cpu_idle; ++#endif ++ rq->cpu_locality = kmalloc(cpu_ids * sizeof(int *), GFP_ATOMIC); ++ for_each_possible_cpu(j) { ++ if (i == j) ++ rq->cpu_locality[j] = LOCALITY_SAME; ++ else ++ rq->cpu_locality[j] = LOCALITY_DISTANT; ++ } ++ /* sl_order is O(possible_cpusÂČ) pointers, same as rq_order. */ ++ rq->rq_order = kmalloc(cpu_ids * sizeof(struct rq *), GFP_ATOMIC); ++ rq->sl_order = kmalloc(cpu_ids * sizeof(skiplist *), GFP_ATOMIC); ++ rq->cpu_order = kmalloc(cpu_ids * sizeof(struct rq *), GFP_ATOMIC); ++ set_rq_order(rq, 0, rq); ++ rq->cpu_order[0] = rq; ++ for (j = 1; j < cpu_ids; j++) { ++ set_rq_order(rq, j, cpu_rq(j)); ++ rq->cpu_order[j] = cpu_rq(j); ++ } ++ } ++#endif ++ ++ /* ++ * The boot idle thread does lazy MMU switching as well: ++ */ ++ mmgrab_lazy_tlb(&init_mm); ++ enter_lazy_tlb(&init_mm, current); ++ ++ /* ++ * Make us the idle thread. Technically, schedule() should not be ++ * called from this thread, however somewhere below it might be, ++ * but because we are the idle thread, we just pick up running again ++ * when this runqueue becomes "idle". ++ */ ++ init_idle(current, smp_processor_id()); ++ ++#ifdef CONFIG_SMP ++ idle_thread_set_boot_cpu(); ++#endif /* SMP */ ++ ++ init_schedstats(); ++ ++ psi_init(); ++ ++ preempt_dynamic_init(); ++ ++ print_scheduler_version(); ++} ++ ++#ifdef CONFIG_DEBUG_ATOMIC_SLEEP ++static inline int preempt_count_equals(int preempt_offset) ++{ ++ int nested = preempt_count() + rcu_preempt_depth(); ++ ++ return (nested == preempt_offset); ++} ++ ++void __might_sleep(const char *file, int line) ++{ ++ unsigned int state = get_current_state(); ++ /* ++ * Blocking primitives will set (and therefore destroy) current->__state, ++ * since we will exit with TASK_RUNNING make sure we enter with it, ++ * otherwise we will destroy state. ++ */ ++ WARN_ONCE(state != TASK_RUNNING && current->task_state_change, ++ "do not call blocking ops when !TASK_RUNNING; " ++ "state=%x set at [<%p>] %pS\n", state, ++ (void *)current->task_state_change, ++ (void *)current->task_state_change); ++ ++ __might_resched(file, line, 0); ++} ++EXPORT_SYMBOL(__might_sleep); ++ ++void __cant_migrate(const char *file, int line) ++{ ++ static unsigned long prev_jiffy; ++ ++ if (irqs_disabled()) ++ return; ++ ++ if (is_migration_disabled(current)) ++ return; ++ ++ if (!IS_ENABLED(CONFIG_PREEMPT_COUNT)) ++ return; ++ ++ if (preempt_count() > 0) ++ return; ++ ++ if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) ++ return; ++ prev_jiffy = jiffies; ++ ++ pr_err("BUG: assuming non migratable context at %s:%d\n", file, line); ++ pr_err("in_atomic(): %d, irqs_disabled(): %d, migration_disabled() %u pid: %d, name: %s\n", ++ in_atomic(), irqs_disabled(), is_migration_disabled(current), ++ current->pid, current->comm); ++ ++ debug_show_held_locks(current); ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++} ++EXPORT_SYMBOL_GPL(__cant_migrate); ++ ++void __cant_sleep(const char *file, int line, int preempt_offset) ++{ ++ static unsigned long prev_jiffy; ++ ++ if (irqs_disabled()) ++ return; ++ ++ if (!IS_ENABLED(CONFIG_PREEMPT_COUNT)) ++ return; ++ ++ if (preempt_count() > preempt_offset) ++ return; ++ ++ if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) ++ return; ++ prev_jiffy = jiffies; ++ ++ printk(KERN_ERR "BUG: assuming atomic context at %s:%d\n", file, line); ++ printk(KERN_ERR "in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n", ++ in_atomic(), irqs_disabled(), ++ current->pid, current->comm); ++ ++ debug_show_held_locks(current); ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++} ++EXPORT_SYMBOL_GPL(__cant_sleep); ++ ++void __might_resched(const char *file, int line, unsigned int offsets) ++{ ++ /* Ratelimiting timestamp: */ ++ static unsigned long prev_jiffy; ++ ++ unsigned long preempt_disable_ip; ++ ++ /* WARN_ON_ONCE() by default, no rate limit required: */ ++ rcu_sleep_check(); ++ ++ if ((preempt_count_equals(offsets) && !irqs_disabled() && ++ !is_idle_task(current) && !current->non_block_count) || ++ system_state == SYSTEM_BOOTING || system_state > SYSTEM_RUNNING || ++ oops_in_progress) ++ return; ++ ++ if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) ++ return; ++ prev_jiffy = jiffies; ++ ++ /* Save this before calling printk(), since that will clobber it: */ ++ preempt_disable_ip = get_preempt_disable_ip(current); ++ ++ printk(KERN_ERR ++ "BUG: sleeping function called from invalid context at %s:%d\n", ++ file, line); ++ printk(KERN_ERR ++ "in_atomic(): %d, irqs_disabled(): %d, non_block: %d, pid: %d, name: %s\n", ++ in_atomic(), irqs_disabled(), current->non_block_count, ++ current->pid, current->comm); ++ ++ if (task_stack_end_corrupted(current)) ++ printk(KERN_EMERG "Thread overran stack, or stack corrupted\n"); ++ ++ debug_show_held_locks(current); ++ if (irqs_disabled()) ++ print_irqtrace_events(current); ++ if (IS_ENABLED(CONFIG_DEBUG_PREEMPT) ++ && !preempt_count_equals(offsets)) { ++ pr_err("Preemption disabled at:"); ++ print_ip_sym(KERN_ERR, preempt_disable_ip); ++ } ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++} ++EXPORT_SYMBOL(__might_resched); ++#endif ++ ++#ifdef CONFIG_MAGIC_SYSRQ ++static inline void normalise_rt_tasks(void) ++{ ++ struct sched_attr attr = {}; ++ struct task_struct *g, *p; ++ struct rq_flags rf; ++ struct rq *rq; ++ ++ read_lock(&tasklist_lock); ++ for_each_process_thread(g, p) { ++ /* ++ * Only normalize user tasks: ++ */ ++ if (p->flags & PF_KTHREAD) ++ continue; ++ ++ if (!rt_task(p) && !iso_task(p)) ++ continue; ++ ++ rq = task_rq_lock(p, &rf); ++ __setscheduler(p, rq, SCHED_NORMAL, 0, &attr, false); ++ task_rq_unlock(rq, p, &rf); ++ } ++ read_unlock(&tasklist_lock); ++} ++ ++void normalize_rt_tasks(void) ++{ ++ normalise_rt_tasks(); ++} ++#endif /* CONFIG_MAGIC_SYSRQ */ ++ ++#if defined(CONFIG_IA64) || defined(CONFIG_KGDB_KDB) ++/* ++ * These functions are only useful for the IA64 MCA handling, or kdb. ++ * ++ * They can only be called when the whole system has been ++ * stopped - every CPU needs to be quiescent, and no scheduling ++ * activity can take place. Using them for anything else would ++ * be a serious bug, and as a result, they aren't even visible ++ * under any other configuration. ++ */ ++ ++/** ++ * curr_task - return the current task for a given CPU. ++ * @cpu: the processor in question. ++ * ++ * ONLY VALID WHEN THE WHOLE SYSTEM IS STOPPED! ++ * ++ * Return: The current task for @cpu. ++ */ ++struct task_struct *curr_task(int cpu) ++{ ++ return cpu_curr(cpu); ++} ++ ++#endif /* defined(CONFIG_IA64) || defined(CONFIG_KGDB_KDB) */ ++ ++#ifdef CONFIG_IA64 ++/** ++ * ia64_set_curr_task - set the current task for a given CPU. ++ * @cpu: the processor in question. ++ * @p: the task pointer to set. ++ * ++ * Description: This function must only be used when non-maskable interrupts ++ * are serviced on a separate stack. It allows the architecture to switch the ++ * notion of the current task on a CPU in a non-blocking manner. This function ++ * must be called with all CPU's synchronised, and interrupts disabled, the ++ * and caller must save the original value of the current task (see ++ * curr_task() above) and restore that value before reenabling interrupts and ++ * re-starting the system. ++ * ++ * ONLY VALID WHEN THE WHOLE SYSTEM IS STOPPED! ++ */ ++void ia64_set_curr_task(int cpu, struct task_struct *p) ++{ ++ cpu_curr(cpu) = p; ++} ++ ++#endif ++ ++#ifdef CONFIG_SCHED_DEBUG ++__read_mostly bool sched_debug_enabled; ++ ++void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, ++ struct seq_file *m) ++{ ++ seq_printf(m, "%s (%d, #threads: %d)\n", p->comm, task_pid_nr_ns(p, ns), ++ get_nr_threads(p)); ++} ++ ++void proc_sched_set_task(struct task_struct *p) ++{} ++#endif ++ ++#ifdef CONFIG_CGROUP_SCHED ++static void sched_free_group(struct task_group *tg) ++{ ++ kmem_cache_free(task_group_cache, tg); ++} ++ ++/* allocate runqueue etc for a new task group */ ++struct task_group *sched_create_group(struct task_group *parent) ++{ ++ struct task_group *tg; ++ ++ tg = kmem_cache_alloc(task_group_cache, GFP_KERNEL | __GFP_ZERO); ++ if (!tg) ++ return ERR_PTR(-ENOMEM); ++ ++ init_tg_cgroup_defaults(tg); ++ return tg; ++} ++ ++void sched_online_group(struct task_group *tg, struct task_group *parent) ++{ ++} ++ ++/* rcu callback to free various structures associated with a task group */ ++static void sched_free_group_rcu(struct rcu_head *rhp) ++{ ++ /* Now it should be safe to free those cfs_rqs */ ++ sched_free_group(container_of(rhp, struct task_group, rcu)); ++} ++ ++void sched_destroy_group(struct task_group *tg) ++{ ++ /* Wait for possible concurrent references to cfs_rqs complete */ ++ call_rcu(&tg->rcu, sched_free_group_rcu); ++} ++ ++void sched_release_group(struct task_group *tg) ++{ ++} ++ ++static inline struct task_group *css_tg(struct cgroup_subsys_state *css) ++{ ++ return css ? container_of(css, struct task_group, css) : NULL; ++} ++ ++static struct cgroup_subsys_state * ++cpu_cgroup_css_alloc(struct cgroup_subsys_state *parent_css) ++{ ++ struct task_group *parent = css_tg(parent_css); ++ struct task_group *tg; ++ ++ if (!parent) { ++ /* This is early initialization for the top cgroup */ ++ return &root_task_group.css; ++ } ++ ++ tg = sched_create_group(parent); ++ if (IS_ERR(tg)) ++ return ERR_PTR(-ENOMEM); ++ return &tg->css; ++} ++ ++/* Expose task group only after completing cgroup initialization */ ++static int cpu_cgroup_css_online(struct cgroup_subsys_state *css) ++{ ++ struct task_group *tg = css_tg(css); ++ struct task_group *parent = css_tg(css->parent); ++ ++ if (parent) ++ sched_online_group(tg, parent); ++ return 0; ++} ++ ++static void cpu_cgroup_css_released(struct cgroup_subsys_state *css) ++{ ++ struct task_group *tg = css_tg(css); ++ ++ sched_release_group(tg); ++} ++ ++static void cpu_cgroup_css_free(struct cgroup_subsys_state *css) ++{ ++ struct task_group *tg = css_tg(css); ++ ++ /* ++ * Relies on the RCU grace period between css_released() and this. ++ */ ++ sched_free_group(tg); ++} ++ ++static void cpu_cgroup_fork(struct task_struct *task) ++{ ++} ++ ++static int cpu_cgroup_can_attach(struct cgroup_taskset *tset) ++{ ++ return 0; ++} ++ ++static void cpu_cgroup_attach(struct cgroup_taskset *tset) ++{ ++} ++ ++/* ++ * Accept-and-ignore cpu controller files. ++ * ++ * Mainline wires these to CFS shares/bandwidth. MuQSS has neither, but ++ * container runtimes and systemd write CPUWeight=/CPUQuota=/--cpus and ++ * fail if the files are missing. Validate ranges, store for readback, ++ * and leave scheduling unaffected. ++ * ++ * nice↔weight table matches CFS (sched_prio_to_weight) so weight.nice ++ * round-trips to the same values userspace expects. ++ */ ++static const int muqss_prio_to_weight[40] = { ++ /* -20 */ 88761, 71755, 56483, 46273, 36291, ++ /* -15 */ 29154, 23254, 18705, 14949, 11916, ++ /* -10 */ 9548, 7620, 6100, 4904, 3906, ++ /* -5 */ 3121, 2501, 1991, 1586, 1277, ++ /* 0 */ 1024, 820, 655, 526, 423, ++ /* 5 */ 335, 272, 215, 172, 137, ++ /* 10 */ 110, 87, 70, 56, 45, ++ /* 15 */ 36, 29, 23, 18, 15, ++}; ++ ++static unsigned long muqss_weight_from_cgroup(unsigned long cgrp_weight) ++{ ++ return DIV_ROUND_CLOSEST_ULL(cgrp_weight * 1024, CGROUP_WEIGHT_DFL); ++} ++ ++static unsigned long muqss_weight_to_cgroup(unsigned long weight) ++{ ++ return clamp_t(unsigned long, ++ DIV_ROUND_CLOSEST_ULL(weight * CGROUP_WEIGHT_DFL, 1024), ++ CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX); ++} ++ ++static u64 cpu_weight_read_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->weight; ++} ++ ++static int cpu_weight_write_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft, u64 cgrp_weight) ++{ ++ if (cgrp_weight < CGROUP_WEIGHT_MIN || cgrp_weight > CGROUP_WEIGHT_MAX) ++ return -ERANGE; ++ ++ css_tg(css)->weight = cgrp_weight; ++ return 0; ++} ++ ++static s64 cpu_weight_nice_read_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ unsigned long weight = muqss_weight_from_cgroup(css_tg(css)->weight); ++ int last_delta = INT_MAX; ++ int prio, delta; ++ ++ for (prio = 0; prio < ARRAY_SIZE(muqss_prio_to_weight); prio++) { ++ delta = abs(muqss_prio_to_weight[prio] - (int)weight); ++ if (delta >= last_delta) ++ break; ++ last_delta = delta; ++ } ++ ++ return PRIO_TO_NICE(prio - 1 + MAX_RT_PRIO); ++} ++ ++static int cpu_weight_nice_write_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft, s64 nice) ++{ ++ int idx; ++ ++ if (nice < MIN_NICE || nice > MAX_NICE) ++ return -ERANGE; ++ ++ idx = NICE_TO_PRIO(nice) - MAX_RT_PRIO; ++ idx = array_index_nospec(idx, ARRAY_SIZE(muqss_prio_to_weight)); ++ css_tg(css)->weight = ++ muqss_weight_to_cgroup(muqss_prio_to_weight[idx]); ++ return 0; ++} ++ ++static s64 cpu_idle_read_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->idle; ++} ++ ++static int cpu_idle_write_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft, s64 idle) ++{ ++ if (idle != 0 && idle != 1) ++ return -EINVAL; ++ ++ css_tg(css)->idle = idle; ++ return 0; ++} ++ ++static void cpu_period_quota_print(struct seq_file *sf, long period, long quota) ++{ ++ if (quota < 0) ++ seq_puts(sf, "max"); ++ else ++ seq_printf(sf, "%ld", quota); ++ ++ seq_printf(sf, " %ld\n", period); ++} ++ ++static int cpu_period_quota_parse(char *buf, u64 *period_us_p, u64 *quota_us_p) ++{ ++ char tok[21]; /* U64_MAX */ ++ ++ if (sscanf(buf, "%20s %llu", tok, period_us_p) < 1) ++ return -EINVAL; ++ ++ if (sscanf(tok, "%llu", quota_us_p) < 1) { ++ if (!strcmp(tok, "max")) ++ *quota_us_p = U64_MAX; ++ else ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int cpu_max_show(struct seq_file *sf, void *v) ++{ ++ struct task_group *tg = css_tg(seq_css(sf)); ++ s64 quota = tg->quota_us; ++ ++ cpu_period_quota_print(sf, tg->period_us, quota); ++ return 0; ++} ++ ++static ssize_t cpu_max_write(struct kernfs_open_file *of, ++ char *buf, size_t nbytes, loff_t off) ++{ ++ struct task_group *tg = css_tg(of_css(of)); ++ u64 period_us = tg->period_us, quota_us; ++ int ret; ++ ++ ret = cpu_period_quota_parse(buf, &period_us, "a_us); ++ if (ret) ++ return ret; ++ ++ if (!period_us || period_us > USEC_PER_SEC) ++ return -EINVAL; ++ ++ tg->period_us = period_us; ++ tg->quota_us = (quota_us == U64_MAX) ? -1 : (s64)quota_us; ++ return nbytes; ++} ++ ++static u64 cpu_burst_read_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->burst_us; ++} ++ ++static int cpu_burst_write_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft, u64 burst_us) ++{ ++ struct task_group *tg = css_tg(css); ++ ++ /* Burst must not exceed a finite quota when one is set. */ ++ if (tg->quota_us >= 0 && burst_us > (u64)tg->quota_us) ++ return -EINVAL; ++ ++ tg->burst_us = burst_us; ++ return 0; ++} ++ ++/* Legacy v1 interfaces */ ++static u64 cpu_shares_read_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return muqss_weight_from_cgroup(css_tg(css)->weight); ++} ++ ++static int cpu_shares_write_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft, u64 share) ++{ ++ if (share < 2 || share > 262144) ++ return -ERANGE; ++ ++ css_tg(css)->weight = muqss_weight_to_cgroup(share); ++ return 0; ++} ++ ++static u64 cpu_period_read_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->period_us; ++} ++ ++static int cpu_period_write_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft, u64 period_us) ++{ ++ if (!period_us || period_us > USEC_PER_SEC) ++ return -EINVAL; ++ ++ css_tg(css)->period_us = period_us; ++ return 0; ++} ++ ++static s64 cpu_quota_read_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->quota_us; ++} ++ ++static int cpu_quota_write_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft, s64 quota_us) ++{ ++ if (quota_us < -1 || quota_us > (s64)USEC_PER_SEC * 1024) ++ return -EINVAL; ++ ++ css_tg(css)->quota_us = quota_us; ++ return 0; ++} ++ ++static struct cftype cpu_legacy_files[] = { ++ { ++ .name = "shares", ++ .read_u64 = cpu_shares_read_u64, ++ .write_u64 = cpu_shares_write_u64, ++ }, ++ { ++ .name = "idle", ++ .read_s64 = cpu_idle_read_s64, ++ .write_s64 = cpu_idle_write_s64, ++ }, ++ { ++ .name = "cfs_period_us", ++ .read_u64 = cpu_period_read_u64, ++ .write_u64 = cpu_period_write_u64, ++ }, ++ { ++ .name = "cfs_quota_us", ++ .read_s64 = cpu_quota_read_s64, ++ .write_s64 = cpu_quota_write_s64, ++ }, ++ { ++ .name = "cfs_burst_us", ++ .read_u64 = cpu_burst_read_u64, ++ .write_u64 = cpu_burst_write_u64, ++ }, ++ { } /* Terminate */ ++}; ++ ++static struct cftype cpu_files[] = { ++ { ++ .name = "weight", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_u64 = cpu_weight_read_u64, ++ .write_u64 = cpu_weight_write_u64, ++ }, ++ { ++ .name = "weight.nice", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_s64 = cpu_weight_nice_read_s64, ++ .write_s64 = cpu_weight_nice_write_s64, ++ }, ++ { ++ .name = "idle", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_s64 = cpu_idle_read_s64, ++ .write_s64 = cpu_idle_write_s64, ++ }, ++ { ++ .name = "max", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .seq_show = cpu_max_show, ++ .write = cpu_max_write, ++ }, ++ { ++ .name = "max.burst", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_u64 = cpu_burst_read_u64, ++ .write_u64 = cpu_burst_write_u64, ++ }, ++ { } /* terminate */ ++}; ++ ++static int cpu_extra_stat_show(struct seq_file *sf, ++ struct cgroup_subsys_state *css) ++{ ++ return 0; ++} ++ ++struct cgroup_subsys cpu_cgrp_subsys = { ++ .css_alloc = cpu_cgroup_css_alloc, ++ .css_online = cpu_cgroup_css_online, ++ .css_released = cpu_cgroup_css_released, ++ .css_free = cpu_cgroup_css_free, ++ .css_extra_stat_show = cpu_extra_stat_show, ++ .fork = cpu_cgroup_fork, ++ .can_attach = cpu_cgroup_can_attach, ++ .attach = cpu_cgroup_attach, ++ .legacy_cftypes = cpu_legacy_files, ++ .dfl_cftypes = cpu_files, ++ .early_init = true, ++ .threaded = true, ++}; ++#endif /* CONFIG_CGROUP_SCHED */ ++ ++void call_trace_sched_update_nr_running(struct rq *rq, int count) ++{ ++ trace_sched_update_nr_running_tp(rq, count); ++} ++ ++/* CFS Compat */ ++#ifdef CONFIG_RCU_TORTURE_TEST ++int sysctl_sched_rt_runtime; ++#endif ++ ++/* ++ * Compatibility layer for core.c interfaces added after 5.12. ++ * ++ * These are all consumed by code outside the scheduler. Where the feature ++ * behind them does not exist under MuQSS (deadline bandwidth, sched_ext, ++ * mm_cid, the CFS runqueue debugfs) the implementation is deliberately inert ++ * rather than absent, so mainline callers need no #ifdef. ++ */ ++ ++/* ++ * Tracepoint helpers behind set_current_state()/set_need_resched(). Callers ++ * MUST guard these with a tracepoint_enabled() check, which is why they use ++ * the unguarded trace_call__() form. ++ */ ++void __trace_set_current_state(int state_value) ++{ ++ trace_call__sched_set_state_tp(current, state_value); ++} ++EXPORT_SYMBOL(__trace_set_current_state); ++ ++void __trace_set_need_resched(struct task_struct *curr, int tif) ++{ ++ trace_call__sched_set_need_resched_tp(curr, smp_processor_id(), tif); ++} ++EXPORT_SYMBOL_GPL(__trace_set_need_resched); ++ ++unsigned long long nr_context_switches_cpu(int cpu) ++{ ++ return cpu_rq(cpu)->nr_switches; ++} ++ ++/* ++ * External (non-sched/) callers cannot see task_on_rq_queued(); wrap it. ++ * Used by tick-sched nohz full path among others. ++ */ ++bool sched_task_on_rq(struct task_struct *p) ++{ ++ return task_on_rq_queued(p); ++} ++ ++unsigned long get_wchan(struct task_struct *p) ++{ ++ unsigned long ip = 0; ++ unsigned int state; ++ ++ if (!p || p == current) ++ return 0; ++ ++ /* Only get wchan if task is blocked and we can keep it that way. */ ++ raw_spin_lock_irq(&p->pi_lock); ++ state = READ_ONCE(p->__state); ++ smp_rmb(); /* see try_to_wake_up() */ ++ if (state != TASK_RUNNING && state != TASK_WAKING && !p->on_rq) ++ ip = __get_wchan(p); ++ raw_spin_unlock_irq(&p->pi_lock); ++ ++ return ip; ++} ++ ++/* ++ * Fork path. MuQSS does no cgroup bandwidth accounting and has no sched_ext ++ * to cancel, so these only need to exist. ++ */ ++int sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) ++{ ++ return 0; ++} ++ ++void sched_cancel_fork(struct task_struct *p) ++{ ++} ++ ++/* ++ * rt_mutex helpers. MuQSS has no proxy execution; pre/post still run the ++ * worker submit/update pair so blocking on an rt_mutex flushes plugged IO. ++ * rt_mutex_schedule() must not re-enter schedule() or submit_work runs twice. ++ */ ++#ifdef CONFIG_RT_MUTEXES ++#define fetch_and_set(x, v) ({ int _x = (x); (x) = (v); _x; }) ++#endif ++ ++void rt_mutex_pre_schedule(void) ++{ ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(!fetch_and_set(current->sched_rt_mutex, 1)); ++#endif ++ sched_submit_work(current); ++} ++ ++/* Futex callers cannot have worker state or plugged IO to submit. */ ++void rt_mutex_futex_pre_schedule(void) ++{ ++ lockdep_assert(!(current->flags & (PF_WQ_WORKER | PF_IO_WORKER))); ++ lockdep_assert(!current->plug); ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(!fetch_and_set(current->sched_rt_mutex, 1)); ++#endif ++} ++ ++void rt_mutex_schedule(void) ++{ ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(current->sched_rt_mutex); ++#endif ++ __schedule_loop(SM_NONE); ++} ++ ++void rt_mutex_post_schedule(void) ++{ ++ sched_update_worker(current); ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0)); ++#endif ++} ++ ++void rt_mutex_futex_post_schedule(void) ++{ ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0)); ++#endif ++} ++ ++int dl_task_check_affinity(struct task_struct *p __always_unused, ++ const struct cpumask *mask __always_unused) ++{ ++ /* MuQSS has no deadline bandwidth admission. */ ++ return 0; ++} ++ ++#ifdef CONFIG_SMP ++/* Callers must hold p->pi_lock across the read and its use. */ ++static const struct cpumask *task_user_cpus(struct task_struct *p) ++{ ++ lockdep_assert_held(&p->pi_lock); ++ ++ if (!p->user_cpus_ptr) ++ return cpu_possible_mask; ++ return p->user_cpus_ptr; ++} ++ ++/* ++ * Copy out the intersection of the task's user-requested mask and @mask. ++ * pi_lock keeps user_cpus_ptr alive for the copy: everything that frees it ++ * clears the pointer under task_rq_lock(), which nests pi_lock, and only ++ * frees once that has been dropped. ++ */ ++static bool user_cpus_and(struct task_struct *p, struct cpumask *dst, ++ const struct cpumask *mask) ++{ ++ unsigned long flags; ++ bool ret; ++ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ ret = cpumask_and(dst, task_user_cpus(p), mask); ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++ ++ return ret; ++} ++ ++/* ++ * Intersect the task's user-requested mask with @subset_mask and apply it. ++ * Empty intersection leaves affinity unchanged. ++ */ ++static int restrict_cpus_allowed_ptr(struct task_struct *p, ++ struct cpumask *new_mask, ++ const struct cpumask *subset_mask) ++{ ++ if (!user_cpus_and(p, new_mask, subset_mask)) ++ return -EINVAL; ++ ++ return __set_cpus_allowed_ptr(p, new_mask, 0); ++} ++#endif ++ ++/* ++ * Restrict @p to CPUs it can actually run on (task_cpu_possible_mask()). ++ * ARM64 32-bit execve uses this when 64-bit-only CPUs exist. ++ */ ++void force_compatible_cpus_allowed_ptr(struct task_struct *p) ++{ ++ const struct cpumask *override_mask = task_cpu_possible_mask(p); ++#ifdef CONFIG_SMP ++ cpumask_var_t new_mask; ++ ++ alloc_cpumask_var(&new_mask, GFP_KERNEL); ++ ++ /* ++ * __migrate_task() can fail silently if the dest CPU is offlined ++ * concurrently, so hold the hotplug lock across the move. ++ */ ++ cpus_read_lock(); ++ if (!cpumask_available(new_mask)) ++ goto out_set_mask; ++ ++ if (!restrict_cpus_allowed_ptr(p, new_mask, override_mask)) ++ goto out_free_mask; ++ ++ cpuset_cpus_allowed(p, new_mask); ++ override_mask = new_mask; ++ ++out_set_mask: ++ if (printk_ratelimit()) { ++ printk_deferred("Overriding affinity for process %d (%s) to CPUs %*pbl\n", ++ task_pid_nr(p), p->comm, ++ cpumask_pr_args(override_mask)); ++ } ++ ++ WARN_ON(set_cpus_allowed_ptr(p, override_mask)); ++out_free_mask: ++ cpus_read_unlock(); ++ free_cpumask_var(new_mask); ++#else ++ WARN_ON(set_cpus_allowed_ptr(p, override_mask)); ++#endif ++} ++ ++/* ++ * Restore the affinity previously restricted by ++ * force_compatible_cpus_allowed_ptr(). Caller serialises the pair. ++ */ ++void relax_compatible_cpus_allowed_ptr(struct task_struct *p) ++{ ++#ifdef CONFIG_SMP ++ cpumask_var_t cpus_allowed, new_mask; ++ int ret = -ENOMEM; ++ ++ if (!alloc_cpumask_var(&cpus_allowed, GFP_KERNEL)) ++ goto warn; ++ if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) ++ goto out_free_cpus_allowed; ++ ++ cpuset_cpus_allowed(p, cpus_allowed); ++ user_cpus_and(p, new_mask, cpus_allowed); ++ ret = __set_cpus_allowed_ptr(p, new_mask, SCA_CHECK); ++ ++ free_cpumask_var(new_mask); ++out_free_cpus_allowed: ++ free_cpumask_var(cpus_allowed); ++warn: ++ WARN_ON_ONCE(ret); ++#else ++ WARN_ON(set_cpus_allowed_ptr(p, cpu_possible_mask)); ++#endif ++} ++ ++const char *preempt_model_str(void) ++{ ++ if (IS_ENABLED(CONFIG_PREEMPT_RT)) ++ return "PREEMPT_RT"; ++ if (preempt_model_full()) ++ return "PREEMPT"; ++ if (preempt_model_voluntary()) ++ return "VOLUNTARY"; ++ return "NONE"; ++} ++ ++#ifdef CONFIG_SMP ++bool cpus_equal_capacity(int this_cpu, int that_cpu) ++{ ++ if (!sched_asym_cpucap_active()) ++ return true; ++ ++ if (this_cpu == that_cpu) ++ return true; ++ ++ return arch_scale_cpu_capacity(this_cpu) == arch_scale_cpu_capacity(that_cpu); ++} ++ ++void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask) ++{ ++ struct affinity_context ac = { ++ .new_mask = new_mask, ++ .user_mask = NULL, ++ .flags = SCA_USER, ++ }; ++ ++ do_set_cpus_allowed(p, &ac); ++} ++ ++void ___migrate_enable(void) ++{ ++ __set_cpus_allowed_ptr(current, ¤t->cpus_mask, ++ SCA_MIGRATE_ENABLE); ++} ++#else /* !CONFIG_SMP */ ++/* ++ * Nothing ever repoints cpus_ptr away from cpus_mask on UP, so ++ * __migrate_enable() never reaches here - it just has to link. ++ */ ++void ___migrate_enable(void) ++{ ++} ++#endif /* CONFIG_SMP */ ++EXPORT_SYMBOL_GPL(___migrate_enable); ++ ++/* ++ * user_cpus_ptr records the affinity a task asked for so that it can be ++ * restored after a temporary restriction: sched_setaffinity() stores it, and ++ * bind_zero() stores the mask it displaces from a task that would otherwise ++ * have been stranded by a CPU going down. Either way the child inherits both ++ * the restriction (zerobound, copied by dup_task_struct()) and the mask that ++ * has to be put back for it. ++ */ ++int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, ++ int node) ++{ ++ cpumask_t *user_mask; ++ unsigned long flags; ++ ++ dst->user_cpus_ptr = NULL; ++ ++ /* ++ * Racing here is harmless: losing it just means the child forgoes a ++ * restore it would have got for free, and taking the pi_lock on every ++ * fork to close it is not worth that. ++ */ ++ if (data_race(!src->user_cpus_ptr)) ++ return 0; ++ ++ user_mask = kmalloc_node(cpumask_size(), GFP_KERNEL, node); ++ if (!user_mask) ++ return -ENOMEM; ++ ++ raw_spin_lock_irqsave(&src->pi_lock, flags); ++ if (src->user_cpus_ptr) { ++ swap(dst->user_cpus_ptr, user_mask); ++ cpumask_copy(dst->user_cpus_ptr, src->user_cpus_ptr); ++ } ++ raw_spin_unlock_irqrestore(&src->pi_lock, flags); ++ ++ kfree(user_mask); ++ ++ return 0; ++} ++ ++void release_user_cpus_ptr(struct task_struct *p) ++{ ++ kfree(p->user_cpus_ptr); ++ p->user_cpus_ptr = NULL; ++} ++ ++/* ++ * Modules cannot see the runqueues layout, so export out-of-line wrappers ++ * (INSTANTIATE_EXPORTED_MIGRATE_DISABLE makes the inlines become externs). ++ */ ++void migrate_disable(void) ++{ ++ __migrate_disable(); ++} ++EXPORT_SYMBOL_GPL(migrate_disable); ++ ++void migrate_enable(void) ++{ ++ __migrate_enable(); ++} ++EXPORT_SYMBOL_GPL(migrate_enable); ++ ++/* ++ * MuQSS picks the CPU for a task at schedule() time rather than at exec, so ++ * there is nothing useful to do here. ++ */ ++void sched_exec(void) ++{ ++} ++ ++/* ++ * Deadline bandwidth accounting. There is no deadline class, so no bandwidth ++ * is ever reserved and cpuset has nothing to move between root domains. ++ */ ++u64 dl_cookie; ++ ++int dl_bw_alloc(int cpu, u64 dl_bw) ++{ ++ return 0; ++} ++ ++void dl_bw_free(int cpu, u64 dl_bw) ++{ ++} ++ ++void sched_set_fifo_secondary(struct task_struct *p) ++{ ++ struct sched_param sp = { .sched_priority = MAX_RT_PRIO / 2 - 1 }; ++ ++ WARN_ON_ONCE(sched_setscheduler_nocheck(p, SCHED_FIFO, &sp) != 0); ++} ++EXPORT_SYMBOL_GPL(sched_set_fifo_secondary); ++ ++/* ++ * Scheduler debugfs. debug.c is CFS/EEVDF runqueue introspection and is not ++ * built under MuQSS; /proc//sched reports nothing. ++ */ ++bool sched_debug_verbose; ++ ++#ifdef CONFIG_SMP ++/* Only topology.c calls these, and it is not built on UP. */ ++void update_sched_domain_debugfs(void) ++{ ++} ++ ++void dirty_sched_domain_sysctl(int cpu) ++{ ++} ++#endif /* CONFIG_SMP */ ++ ++void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, ++ struct seq_file *m) ++{ ++} ++ ++void proc_sched_set_task(struct task_struct *p) ++{ ++} +diff --git a/kernel/sched/MuQSS.h b/kernel/sched/MuQSS.h +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/MuQSS.h +@@ -0,0 +1,1254 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef MUQSS_SCHED_H ++#define MUQSS_SCHED_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_PARAVIRT ++#include ++#endif ++ ++/* ++ * cpupri.h is deliberately not included: MuQSS has no RT class priority ++ * bitmap, root_domain keeps an opaque pointer instead, and pulling the header ++ * in would clash with the no-op cpupri_init()/cpupri_cleanup() below. ++ */ ++ ++#include ++ ++#ifdef CONFIG_SCHED_DEBUG ++# define SCHED_WARN_ON(x) WARN_ONCE(x, #x) ++#else ++# define SCHED_WARN_ON(x) ((void)(x)) ++#endif ++ ++/* Wake flags. The first three directly map to some SD flag value */ ++#define WF_EXEC 0x02 /* Wakeup after exec; maps to SD_BALANCE_EXEC */ ++#define WF_FORK 0x04 /* Wakeup after fork; maps to SD_BALANCE_FORK */ ++#define WF_TTWU 0x08 /* Wakeup; maps to SD_BALANCE_WAKE */ ++ ++#define WF_SYNC 0x10 /* Waker goes to sleep after wakeup */ ++#define WF_MIGRATED 0x20 /* Internal use, task got migrated */ ++#define WF_CURRENT_CPU 0x40 /* Prefer to move the wakee to the current CPU. */ ++#define WF_ON_CPU 0x80 /* Wakee is on_cpu */ ++ ++#ifdef CONFIG_SMP ++static_assert(WF_EXEC == SD_BALANCE_EXEC); ++static_assert(WF_FORK == SD_BALANCE_FORK); ++static_assert(WF_TTWU == SD_BALANCE_WAKE); ++#endif ++ ++/* task_struct::on_rq states: */ ++#define TASK_ON_RQ_QUEUED 1 ++#define TASK_ON_RQ_MIGRATING 2 ++ ++extern void call_trace_sched_update_nr_running(struct rq *rq, int count); ++ ++struct rq; ++ ++#ifdef CONFIG_SMP ++ ++static inline bool sched_asym_prefer(int a, int b) ++{ ++ return arch_asym_cpu_priority(a) > arch_asym_cpu_priority(b); ++} ++ ++struct perf_domain { ++ struct em_perf_domain *em_pd; ++ struct perf_domain *next; ++ struct rcu_head rcu; ++}; ++ ++/* Scheduling group status flags */ ++#define SG_OVERLOAD 0x1 /* More than one runnable task on a CPU. */ ++#define SG_OVERUTILIZED 0x2 /* One or more CPUs are over-utilized. */ ++ ++/* ++ * We add the notion of a root-domain which will be used to define per-domain ++ * variables. Each exclusive cpuset essentially defines an island domain by ++ * fully partitioning the member cpus from any other cpuset. Whenever a new ++ * exclusive cpuset is created, we also create and attach a new root-domain ++ * object. ++ * ++ */ ++struct root_domain { ++ atomic_t refcount; ++ atomic_t rto_count; ++ struct rcu_head rcu; ++ cpumask_var_t span; ++ cpumask_var_t online; ++ ++ /* ++ * Indicate pullable load on at least one CPU, e.g: ++ * - More than one runnable task ++ * - Running task is misfit ++ */ ++ int overload; ++ ++ /* Indicate one or more cpus over-utilized (tipping point) */ ++ int overutilized; ++ ++ /* ++ * The bit corresponding to a CPU gets set here if such CPU has more ++ * than one runnable -deadline task (as it is below for RT tasks). ++ */ ++ cpumask_var_t dlo_mask; ++ atomic_t dlo_count; ++ ++ /* Replace unused CFS structures with void */ ++ //struct dl_bw dl_bw; ++ //struct cpudl cpudl; ++ void *dl_bw; ++ void *cpudl; ++ u64 visit_cookie; ++ ++ /* ++ * The "RT overload" flag: it gets set if a CPU has more than ++ * one runnable RT task. ++ */ ++ cpumask_var_t rto_mask; ++ //struct cpupri cpupri; ++ void *cpupri; ++ ++ unsigned long max_cpu_capacity; ++ ++ /* ++ * NULL-terminated list of performance domains intersecting with the ++ * CPUs of the rd. Protected by RCU. ++ */ ++ struct perf_domain *pd; ++}; ++ ++extern void init_defrootdomain(void); ++extern int sched_init_domains(const struct cpumask *cpu_map); ++extern void rq_attach_root(struct rq *rq, struct root_domain *rd); ++ ++static inline void cpupri_cleanup(void __maybe_unused *cpupri) ++{ ++} ++ ++static inline void cpudl_cleanup(void __maybe_unused *cpudl) ++{ ++} ++ ++static inline void init_dl_bw(void __maybe_unused *dl_bw) ++{ ++} ++ ++static inline int cpudl_init(void __maybe_unused *dl_bw) ++{ ++ return 0; ++} ++ ++static inline int cpupri_init(void __maybe_unused *cpupri) ++{ ++ return 0; ++} ++#endif /* CONFIG_SMP */ ++ ++/* ++ * This is the main, per-CPU runqueue data structure. ++ * This data should only be modified by the local cpu. ++ */ ++struct rq { ++ raw_spinlock_t *lock; ++ raw_spinlock_t *orig_lock; ++ ++ struct task_struct __rcu *curr; ++ struct task_struct *idle; ++ struct task_struct *stop; ++ struct mm_struct *prev_mm; ++ ++ unsigned int nr_running; ++ /* ++ * This is part of a global counter where only the total sum ++ * over all CPUs matters. A task can increase this counter on ++ * one CPU and if it got migrated afterwards it may decrease ++ * it on another CPU. Always updated under the runqueue lock: ++ */ ++ unsigned long nr_uninterruptible; ++#ifdef CONFIG_SMP ++ unsigned int ttwu_pending; ++#endif ++ /* ++ * migrate_disable() nesting count. Mainline's migrate_disable() and ++ * __migrate_enable() are inline in and reach this via ++ * the RQ_nr_pinned offset generated from rq-offsets.c, so the member ++ * is required whether or not MuQSS itself consults it. ++ */ ++ unsigned int nr_pinned; ++ u64 nr_switches; ++ ++ /* ++ * Isolated curr snapshot for lockless peeks. Own cacheline so ++ * last_jiffy / niffies (and remote synchronise_niffies()) do not ++ * invalidate it. Under SMT_NICE this is four fields; without it, ++ * deadline + prio only. Do not pull nr_running, online, or sl in. ++ */ ++ u64 rq_deadline ____cacheline_aligned; ++ int rq_prio; ++#ifdef CONFIG_SMT_NICE ++ struct mm_struct *rq_mm; ++ int rq_smt_bias; /* Policy/nice level bias across smt siblings */ ++#endif ++ u8 __rq_snap_pad[0] ____cacheline_aligned; ++ ++ unsigned long last_scheduler_tick; /* Last jiffy this RQ ticked */ ++ unsigned long last_jiffy; /* Last jiffy this RQ updated rq clock */ ++ u64 niffies; /* Last time this RQ updated rq clock */ ++ u64 jiffy_niffies; /* Niffies as counted by the tick alone */ ++ ++ u64 load_update; /* When we last updated load */ ++ unsigned long load_avg; /* Rolling load average */ ++#ifdef CONFIG_HAVE_SCHED_AVG_IRQ ++ u64 irq_load_update; /* When we last updated IRQ load */ ++ unsigned long irq_load_avg; /* Rolling IRQ load average */ ++#endif ++ /* Accurate timekeeping data */ ++ unsigned long user_ns, nice_ns, irq_ns, softirq_ns, system_ns, ++ iowait_ns, idle_ns; ++ atomic_t nr_iowait; ++ ++#ifdef CONFIG_MEMBARRIER ++ int membarrier_state; ++#endif ++ ++ /* ++ * MuQSS runs no deadline server. topology.c unconditionally tests ++ * rq->fair_server.dl_server when attaching a root domain, so carry a ++ * one-byte flag that is never set rather than #ifdef that code out. ++ * __dl_server_attach_root() below is a no-op taking void *, so the ++ * member's type does not have to match sched_dl_entity. ++ */ ++ struct { ++ bool dl_server; ++ } fair_server; ++ ++ skiplist_node *node; ++ skiplist *sl; ++#ifdef CONFIG_SMP ++ struct task_struct *preempt; /* Preempt triggered on this task */ ++ struct task_struct *preempting; /* Hint only, what task is preempting */ ++ ++ int cpu; /* cpu of this runqueue */ ++ bool online; ++ ++ struct root_domain *rd; ++ struct sched_domain *sd; ++ ++ unsigned long cpu_capacity_orig; ++ ++ int *cpu_locality; /* CPU relative cache distance */ ++ struct rq **rq_order; /* Shared RQs ordered by relative cache distance */ ++ skiplist **sl_order; /* Leaders' skiplists, parallel to rq_order */ ++ struct rq **cpu_order; /* RQs of discrete CPUs ordered by distance */ ++ ++ bool is_leader; ++ struct rq *smp_leader; /* First physical CPU per node */ ++#ifdef CONFIG_SCHED_THERMAL_PRESSURE ++ struct sched_avg avg_thermal; ++#endif /* CONFIG_SCHED_THERMAL_PRESSURE */ ++#ifdef CONFIG_SCHED_SMT ++ struct rq *smt_leader; /* First logical CPU in SMT siblings */ ++ cpumask_t thread_mask; ++ bool has_smt_sibling; /* This CPU has SMT siblings at all */ ++#endif /* CONFIG_SCHED_SMT */ ++#ifdef CONFIG_SCHED_MC ++ struct rq *mc_leader; /* First logical CPU in MC siblings */ ++ cpumask_t core_mask; ++ bool (*cache_idle)(struct rq *rq); ++ /* See if all cache siblings are idle */ ++#endif /* CONFIG_SCHED_MC */ ++ /* last busy→idle, wakeup idle pick */ ++ unsigned long idle_jiffy; ++#endif /* CONFIG_SMP */ ++ ++#ifdef CONFIG_IRQ_TIME_ACCOUNTING ++ u64 prev_irq_time; ++ u64 psi_irq_time; ++#endif /* CONFIG_IRQ_TIME_ACCOUNTING */ ++#ifdef CONFIG_PARAVIRT ++ u64 prev_steal_time; ++#endif /* CONFIG_PARAVIRT */ ++#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING ++ u64 prev_steal_time_rq; ++#endif /* CONFIG_PARAVIRT_TIME_ACCOUNTING */ ++ ++ u64 clock, last_tick; ++ /* Ensure that all clocks are in the same cache line */ ++ u64 clock_task ____cacheline_aligned; ++ int dither; ++ ++ int iso_ticks; ++ bool iso_refractory; ++ ++#ifdef CONFIG_HIGH_RES_TIMERS ++ struct hrtimer hrexpiry_timer; ++ /* ++ * Deferred start/cancel around __schedule() — same scheme as mainline ++ * hrtick. Reprogramming the oneshot clockevent from set_rq_task() on ++ * every context switch under the rq lock starves TIMER_SOFTIRQ on SMP. ++ */ ++ call_single_data_t hrexpiry_csd; ++ unsigned int hrexpiry_sched; ++ s64 hrexpiry_delay; ++ ktime_t hrexpiry_time; ++#endif ++ ++ int rt_nr_running; /* Number real time tasks running */ ++#ifdef CONFIG_SCHEDSTATS ++ ++ /* latency stats */ ++ struct sched_info rq_sched_info; ++ unsigned long long rq_cpu_time; ++ /* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */ ++ ++ /* sys_sched_yield() stats */ ++ unsigned int yld_count; ++ ++ /* schedule() stats */ ++ unsigned int sched_switch; ++ unsigned int sched_count; ++ unsigned int sched_goidle; ++ ++ /* try_to_wake_up() stats */ ++ unsigned int ttwu_count; ++ unsigned int ttwu_local; ++#endif /* CONFIG_SCHEDSTATS */ ++ ++#ifdef CONFIG_CPU_IDLE ++ /* Must be inspected within a rcu lock section */ ++ struct cpuidle_state *idle_state; ++#endif ++}; ++ ++static inline u64 __rq_clock_broken(struct rq *rq) ++{ ++ return READ_ONCE(rq->clock); ++} ++ ++static inline u64 rq_clock(struct rq *rq) ++{ ++ lockdep_assert_held(rq->lock); ++ ++ return rq->clock; ++} ++ ++static inline u64 rq_clock_task(struct rq *rq) ++{ ++ lockdep_assert_held(rq->lock); ++ ++ return rq->clock_task; ++} ++ ++/** ++ * By default the decay is the default pelt decay period. ++ * The decay shift can change the decay period in ++ * multiples of 32. ++ * Decay shift Decay period(ms) ++ * 0 32 ++ * 1 64 ++ * 2 128 ++ * 3 256 ++ * 4 512 ++ */ ++extern int sched_thermal_decay_shift; ++ ++static inline u64 rq_clock_thermal(struct rq *rq) ++{ ++ return rq_clock_task(rq) >> sched_thermal_decay_shift; ++} ++ ++struct rq_flags { ++ unsigned long flags; ++}; ++ ++#ifdef CONFIG_SMP ++struct rq *cpu_rq(int cpu); ++#endif ++ ++#ifndef CONFIG_SMP ++extern struct rq *uprq; ++#define cpu_rq(cpu) (uprq) ++#define this_rq() (uprq) ++#define raw_rq() (uprq) ++#define task_rq(p) (uprq) ++#define cpu_curr(cpu) ((uprq)->curr) ++#else /* CONFIG_SMP */ ++DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); ++#define cpu_rq(cpu) (&per_cpu(runqueues, (cpu))) ++#define this_rq() this_cpu_ptr(&runqueues) ++#define task_rq(p) cpu_rq(task_cpu(p)) ++#define cpu_curr(cpu) (cpu_rq(cpu)->curr) ++#define raw_rq() raw_cpu_ptr(&runqueues) ++#endif /* CONFIG_SMP */ ++ ++static inline int task_current(struct rq *rq, struct task_struct *p) ++{ ++ return rq->curr == p; ++} ++ ++static inline int task_running(struct rq *rq, struct task_struct *p) ++{ ++#ifdef CONFIG_SMP ++ return p->on_cpu; ++#else ++ return task_current(rq, p); ++#endif ++} ++ ++/* ++ * Mainline name used by stats.h / psi.c / RT helpers. Equivalent to ++ * task_running() under SMP; always uses p->on_cpu so the @rq argument is ++ * unused (kept for API compatibility with mainline call sites). ++ */ ++static inline int task_on_cpu(struct rq *rq, struct task_struct *p) ++{ ++#ifdef CONFIG_SMP ++ return p->on_cpu; ++#else ++ return task_current(rq, p); ++#endif ++} ++ ++static inline int task_on_rq_queued(struct task_struct *p) ++{ ++ return p->on_rq == TASK_ON_RQ_QUEUED; ++} ++ ++static inline int task_on_rq_migrating(struct task_struct *p) ++{ ++ return READ_ONCE(p->on_rq) == TASK_ON_RQ_MIGRATING; ++} ++ ++static inline void lockdep_assert_rq_held(struct rq *rq) ++{ ++ lockdep_assert_held(rq->lock); ++} ++ ++static inline void rq_lock(struct rq *rq) ++ __acquires(rq->lock) ++{ ++ raw_spin_lock(rq->lock); ++} ++ ++static inline void rq_unlock(struct rq *rq) ++ __releases(rq->lock) ++{ ++ raw_spin_unlock(rq->lock); ++} ++ ++static inline void rq_lock_irq(struct rq *rq) ++ __acquires(rq->lock) ++{ ++ raw_spin_lock_irq(rq->lock); ++} ++ ++static inline void rq_unlock_irq(struct rq *rq, struct rq_flags __always_unused *rf) ++ __releases(rq->lock) ++{ ++ raw_spin_unlock_irq(rq->lock); ++} ++ ++static inline void rq_lock_irqsave(struct rq *rq, struct rq_flags *rf) ++ __acquires(rq->lock) ++{ ++ raw_spin_lock_irqsave(rq->lock, rf->flags); ++} ++ ++static inline void rq_unlock_irqrestore(struct rq *rq, struct rq_flags *rf) ++ __releases(rq->lock) ++{ ++ raw_spin_unlock_irqrestore(rq->lock, rf->flags); ++} ++ ++static inline struct rq *task_rq_lock(struct task_struct *p, struct rq_flags *rf) ++ __acquires(p->pi_lock) ++ __acquires(rq->lock) ++{ ++ struct rq *rq; ++ ++ while (42) { ++ raw_spin_lock_irqsave(&p->pi_lock, rf->flags); ++ rq = task_rq(p); ++ raw_spin_lock(rq->lock); ++ if (likely(rq == task_rq(p))) ++ break; ++ raw_spin_unlock(rq->lock); ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags); ++ } ++ return rq; ++} ++ ++static inline void task_rq_unlock(struct rq *rq, struct task_struct *p, struct rq_flags *rf) ++ __releases(rq->lock) ++ __releases(p->pi_lock) ++{ ++ rq_unlock(rq); ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags); ++} ++ ++static inline struct rq *__task_rq_lock(struct task_struct *p, struct rq_flags __always_unused *rf) ++ __acquires(rq->lock) ++{ ++ struct rq *rq; ++ ++ lockdep_assert_held(&p->pi_lock); ++ ++ while (42) { ++ rq = task_rq(p); ++ raw_spin_lock(rq->lock); ++ if (likely(rq == task_rq(p))) ++ break; ++ raw_spin_unlock(rq->lock); ++ } ++ return rq; ++} ++ ++/* ++ * Match mainline's 3-arg signature (stats.h / psi paths pass @p). MuQSS does ++ * not pin the lock cookie, so @p and @rf are unused beyond API compatibility. ++ */ ++static inline void __task_rq_unlock(struct rq *rq, ++ struct task_struct __always_unused *p, ++ struct rq_flags __always_unused *rf) ++{ ++ rq_unlock(rq); ++} ++ ++/* ++ * scoped_guard / guard(rq_lock_irq) used by psi_cgroup_restart() and similar. ++ * MuQSS rq_lock_irq takes only the rq (no pin cookie). ++ */ ++DEFINE_LOCK_GUARD_1(rq_lock_irq, struct rq, ++ rq_lock_irq(_T->lock), ++ rq_unlock_irq(_T->lock, &_T->rf), ++ struct rq_flags rf) ++ ++DEFINE_LOCK_GUARD_1(rq_lock_irqsave, struct rq, ++ rq_lock_irqsave(_T->lock, &_T->rf), ++ rq_unlock_irqrestore(_T->lock, &_T->rf), ++ struct rq_flags rf) ++ ++static inline struct rq * ++this_rq_lock_irq(struct rq_flags *rf) ++ __acquires(rq->lock) ++{ ++ struct rq *rq; ++ ++ local_irq_disable(); ++ rq = this_rq(); ++ rq_lock(rq); ++ return rq; ++} ++ ++/* ++ * {de,en}queue flags: Most not used on MuQSS. ++ * ++ * DEQUEUE_SLEEP - task is no longer runnable ++ * ENQUEUE_WAKEUP - task just became runnable ++ * ++ * SAVE/RESTORE - an otherwise spurious dequeue/enqueue, done to ensure tasks ++ * are in a known state which allows modification. Such pairs ++ * should preserve as much state as possible. ++ * ++ * MOVE - paired with SAVE/RESTORE, explicitly does not preserve the location ++ * in the runqueue. ++ * ++ * ENQUEUE_HEAD - place at front of runqueue (tail if not specified) ++ * ENQUEUE_REPLENISH - CBS (replenish runtime and postpone deadline) ++ * ENQUEUE_MIGRATED - the task was migrated during wakeup ++ * ++ */ ++ ++#define DEQUEUE_SLEEP 0x01 ++#define DEQUEUE_SAVE 0x02 /* matches ENQUEUE_RESTORE */ ++ ++#define ENQUEUE_WAKEUP 0x01 ++#define ENQUEUE_RESTORE 0x02 ++ ++#ifdef CONFIG_SMP ++#define ENQUEUE_MIGRATED 0x40 ++#else ++#define ENQUEUE_MIGRATED 0x00 ++#endif ++ ++#ifdef CONFIG_NUMA ++enum numa_topology_type { ++ NUMA_DIRECT, ++ NUMA_GLUELESS_MESH, ++ NUMA_BACKPLANE, ++}; ++extern enum numa_topology_type sched_numa_topology_type; ++extern int sched_max_numa_distance; ++extern bool find_numa_distance(int distance); ++extern void sched_init_numa(int offline_node); ++extern void sched_domains_numa_masks_set(unsigned int cpu); ++extern void sched_domains_numa_masks_clear(unsigned int cpu); ++extern int sched_numa_find_closest(const struct cpumask *cpus, int cpu); ++#else ++static inline void sched_init_numa(int offline_node) { } ++static inline void sched_domains_numa_masks_set(unsigned int cpu) { } ++static inline void sched_domains_numa_masks_clear(unsigned int cpu) { } ++static inline int sched_numa_find_closest(const struct cpumask *cpus, int cpu) ++{ ++ return nr_cpu_ids; ++} ++#endif ++ ++extern struct mutex sched_domains_mutex; ++extern struct static_key_false sched_schedstats; ++ ++#define rcu_dereference_sched_domain(p) \ ++ rcu_dereference_all_check((p), lockdep_is_held(&sched_domains_mutex)) ++ ++struct affinity_context { ++ const struct cpumask *new_mask; ++ struct cpumask *user_mask; ++ unsigned int flags; ++}; ++ ++#define MDF_PUSH 0x01 ++ ++static inline bool is_migration_disabled(struct task_struct *p) ++{ ++ return p->migration_disabled; ++} ++ ++#define SCA_CHECK 0x01 ++#define SCA_MIGRATE_DISABLE 0x02 ++#define SCA_MIGRATE_ENABLE 0x04 ++#define SCA_USER 0x08 ++ ++#ifdef CONFIG_SMP ++ ++/* ++ * The domain tree (rq->sd) is protected by RCU's quiescent state transition. ++ * See destroy_sched_domains: call_rcu for details. ++ * ++ * The domain tree of any CPU may only be accessed from within ++ * preempt-disabled sections. ++ */ ++#define for_each_domain(cpu, __sd) \ ++ for (__sd = rcu_dereference_sched_domain(cpu_rq(cpu)->sd); \ ++ __sd; __sd = __sd->parent) ++ ++/** ++ * highest_flag_domain - Return highest sched_domain containing flag. ++ * @cpu: The cpu whose highest level of sched domain is to ++ * be returned. ++ * @flag: The flag to check for the highest sched_domain ++ * for the given cpu. ++ * ++ * Returns the highest sched_domain of a cpu which contains the given flag. ++ */ ++static inline struct sched_domain *highest_flag_domain(int cpu, int flag) ++{ ++ struct sched_domain *sd, *hsd = NULL; ++ ++ for_each_domain(cpu, sd) { ++ if (!(sd->flags & flag)) ++ break; ++ hsd = sd; ++ } ++ ++ return hsd; ++} ++ ++static inline struct sched_domain *lowest_flag_domain(int cpu, int flag) ++{ ++ struct sched_domain *sd; ++ ++ for_each_domain(cpu, sd) { ++ if (sd->flags & flag) ++ break; ++ } ++ ++ return sd; ++} ++ ++DECLARE_PER_CPU(struct sched_domain __rcu *, sd_llc); ++DECLARE_PER_CPU(int, sd_llc_size); ++DECLARE_PER_CPU(int, sd_llc_id); ++DECLARE_PER_CPU(int, sd_share_id); ++DECLARE_PER_CPU(struct sched_domain_shared __rcu *, sd_llc_shared); ++DECLARE_PER_CPU(struct sched_domain_shared __rcu *, sd_balance_shared); ++DECLARE_PER_CPU(struct sched_domain __rcu *, sd_numa); ++DECLARE_PER_CPU(struct sched_domain __rcu *, sd_asym_packing); ++DECLARE_PER_CPU(struct sched_domain __rcu *, sd_asym_cpucapacity); ++ ++void sched_domains_free_llc_id(int cpu); ++ ++extern struct static_key_false sched_asym_cpucapacity; ++extern struct static_key_false sched_cluster_active; ++ ++static __always_inline bool sched_asym_cpucap_active(void) ++{ ++ return static_branch_unlikely(&sched_asym_cpucapacity); ++} ++ ++struct sched_group_capacity { ++ atomic_t ref; ++ /* ++ * CPU capacity of this group, SCHED_CAPACITY_SCALE being max capacity ++ * for a single CPU. ++ */ ++ unsigned long capacity; ++ unsigned long min_capacity; /* Min per-CPU capacity in group */ ++ unsigned long max_capacity; /* Max per-CPU capacity in group */ ++ unsigned long next_update; ++ int imbalance; /* XXX unrelated to capacity but shared group state */ ++ ++ int id; ++ ++ unsigned long cpumask[]; /* Balance mask */ ++}; ++ ++struct sched_group { ++ struct sched_group *next; /* Must be a circular list */ ++ atomic_t ref; ++ ++ unsigned int group_weight; ++ unsigned int cores; ++ struct sched_group_capacity *sgc; ++ int asym_prefer_cpu; /* CPU of highest priority in group */ ++ int flags; ++ ++ /* ++ * The CPUs this group covers. ++ * ++ * NOTE: this field is variable length. (Allocated dynamically ++ * by attaching extra space to the end of the structure, ++ * depending on how many CPUs the kernel has booted up with) ++ */ ++ unsigned long cpumask[]; ++}; ++ ++static inline struct cpumask *sched_group_span(struct sched_group *sg) ++{ ++ return to_cpumask(sg->cpumask); ++} ++ ++/* ++ * See build_balance_mask(). ++ */ ++static inline struct cpumask *group_balance_mask(struct sched_group *sg) ++{ ++ return to_cpumask(sg->sgc->cpumask); ++} ++ ++extern int group_balance_cpu(struct sched_group *sg); ++extern void update_sched_domain_debugfs(void); ++extern void dirty_sched_domain_sysctl(int cpu); ++extern void sched_update_numa(int cpu, bool online); ++extern void sched_get_rd(struct root_domain *rd); ++extern void sched_put_rd(struct root_domain *rd); ++extern bool sched_debug_verbose; ++ ++/** ++ * group_first_cpu - Returns the first cpu in the cpumask of a sched_group. ++ * @group: The group whose first cpu is to be returned. ++ */ ++static inline unsigned int group_first_cpu(struct sched_group *group) ++{ ++ return cpumask_first(sched_group_span(group)); ++} ++ ++ ++/* ++ * register_sched_domain_sysctl()/unregister_sched_domain_sysctl() were ++ * replaced upstream by update_sched_domain_debugfs(), declared above with ++ * dirty_sched_domain_sysctl(). flush_smp_call_function_from_idle() likewise ++ * became flush_smp_call_function_queue(), declared in smp.h. ++ */ ++ ++extern void set_cpus_allowed_common(struct task_struct *p, struct affinity_context *ctx); ++ ++extern void set_rq_online (struct rq *rq); ++extern void set_rq_offline(struct rq *rq); ++extern bool sched_smp_initialized; ++ ++static inline void update_group_capacity(struct sched_domain *sd, int cpu) ++{ ++} ++ ++static inline void trigger_load_balance(struct rq *rq) ++{ ++} ++ ++#else /* CONFIG_SMP */ ++ ++static inline void flush_smp_call_function_from_idle(void) { } ++ ++/* ++ * UP has no sched domains and MuQSS's rq has no ->sd, but stats.c walks the ++ * domain list unconditionally. Hand it an empty list. ++ */ ++#define for_each_domain(cpu, __sd) \ ++ for (__sd = NULL; __sd; __sd = __sd->parent) ++ ++#endif /* CONFIG_SMP */ ++ ++#ifdef CONFIG_CPU_IDLE ++static inline void idle_set_state(struct rq *rq, ++ struct cpuidle_state *idle_state) ++{ ++ rq->idle_state = idle_state; ++} ++ ++static inline struct cpuidle_state *idle_get_state(struct rq *rq) ++{ ++ SCHED_WARN_ON(!rcu_read_lock_held()); ++ return rq->idle_state; ++} ++#else ++static inline void idle_set_state(struct rq *rq, ++ struct cpuidle_state *idle_state) ++{ ++} ++ ++static inline struct cpuidle_state *idle_get_state(struct rq *rq) ++{ ++ return NULL; ++} ++#endif ++ ++#ifdef CONFIG_SCHED_DEBUG ++extern bool sched_debug_enabled; ++#endif ++ ++extern void schedule_idle(void); ++ ++#ifdef CONFIG_IRQ_TIME_ACCOUNTING ++struct irqtime { ++ u64 total; ++ u64 tick_delta; ++ u64 irq_start_time; ++ struct u64_stats_sync sync; ++}; ++ ++DECLARE_PER_CPU(struct irqtime, cpu_irqtime); ++ ++/* ++ * Returns the irqtime minus the softirq time computed by ksoftirqd. ++ * Otherwise ksoftirqd's sum_exec_runtime is substracted its own runtime ++ * and never move forward. ++ */ ++static inline u64 irq_time_read(int cpu) ++{ ++ struct irqtime *irqtime = &per_cpu(cpu_irqtime, cpu); ++ unsigned int seq; ++ u64 total; ++ ++ do { ++ seq = __u64_stats_fetch_begin(&irqtime->sync); ++ total = irqtime->total; ++ } while (__u64_stats_fetch_retry(&irqtime->sync, seq)); ++ ++ return total; ++} ++#endif /* CONFIG_IRQ_TIME_ACCOUNTING */ ++ ++static inline bool sched_stop_runnable(struct rq *rq) ++{ ++ return rq->stop && task_on_rq_queued(rq->stop); ++} ++ ++#ifdef CONFIG_SMP ++static inline int cpu_of(struct rq *rq) ++{ ++ return rq->cpu; ++} ++#else /* CONFIG_SMP */ ++static inline int cpu_of(struct rq *rq) ++{ ++ return 0; ++} ++#endif ++ ++#ifdef CONFIG_CPU_FREQ ++DECLARE_PER_CPU(struct update_util_data *, cpufreq_update_util_data); ++ ++static inline void cpufreq_trigger(struct rq *rq, unsigned int flags) ++{ ++ struct update_util_data *data; ++ ++ data = rcu_dereference_sched(*per_cpu_ptr(&cpufreq_update_util_data, ++ cpu_of(rq))); ++ ++ if (data) ++ data->func(data, rq->niffies, flags); ++} ++#else ++static inline void cpufreq_trigger(struct rq *rq, unsigned int flag) ++{ ++} ++#endif /* CONFIG_CPU_FREQ */ ++ ++static __always_inline ++unsigned int uclamp_rq_util_with(struct rq __maybe_unused *rq, unsigned int util, ++ struct task_struct __maybe_unused *p) ++{ ++ return util; ++} ++ ++static inline bool uclamp_is_used(void) ++{ ++ return false; ++} ++ ++#ifndef arch_scale_freq_tick ++static __always_inline ++void arch_scale_freq_tick(void) ++{ ++} ++#endif ++ ++#ifdef arch_scale_freq_capacity ++#ifndef arch_scale_freq_invariant ++#define arch_scale_freq_invariant() (true) ++#endif ++#else /* arch_scale_freq_capacity */ ++#define arch_scale_freq_invariant() (false) ++#endif ++ ++/* ++ * read_sum_exec_runtime() lives in cputime.c and now goes through ++ * tsk_seruntime(), so MuQSS needs no copy of it here. ++ */ ++ ++#ifndef arch_scale_freq_capacity ++/** ++ * arch_scale_freq_capacity - get the frequency scale factor of a given CPU. ++ * @cpu: the CPU in question. ++ * ++ * Return: the frequency scale factor normalized against SCHED_CAPACITY_SCALE, i.e. ++ * ++ * f_curr ++ * ------ * SCHED_CAPACITY_SCALE ++ * f_max ++ */ ++static __always_inline ++unsigned long arch_scale_freq_capacity(int cpu) ++{ ++ return SCHED_CAPACITY_SCALE; ++} ++#endif ++ ++#ifdef CONFIG_NO_HZ_FULL ++extern bool sched_can_stop_tick(struct rq *rq); ++extern int __init sched_tick_offload_init(void); ++ ++/* ++ * Tick may be needed by tasks in the runqueue depending on their policy and ++ * requirements. If tick is needed, lets send the target an IPI to kick it out of ++ * nohz mode if necessary. ++ */ ++static inline void sched_update_tick_dependency(struct rq *rq) ++{ ++ int cpu = cpu_of(rq); ++ ++ if (!tick_nohz_full_cpu(cpu)) ++ return; ++ ++ if (sched_can_stop_tick(rq)) ++ tick_nohz_dep_clear_cpu(cpu, TICK_DEP_BIT_SCHED); ++ else ++ tick_nohz_dep_set_cpu(cpu, TICK_DEP_BIT_SCHED); ++} ++#else ++static inline int sched_tick_offload_init(void) { return 0; } ++static inline void sched_update_tick_dependency(struct rq *rq) { } ++#endif ++ ++#define SCHED_FLAG_SUGOV 0x10000000 ++ ++#ifdef CONFIG_SMP ++/* ++ * enum cpu_util_type and its FREQUENCY_UTIL/ENERGY_UTIL members were removed ++ * upstream; effective_cpu_util() now reports a utilisation figure plus the ++ * usable capacity range. MuQSS supplies it from MuQSS.c. ++ */ ++unsigned long effective_cpu_util(int cpu, unsigned long util_cfs, ++ unsigned long *min, unsigned long *max); ++ ++static inline unsigned long cpu_bw_dl(struct rq *rq) ++{ ++ return 0; ++} ++ ++static inline unsigned long cpu_util_dl(struct rq *rq) ++{ ++ return 0; ++} ++ ++static inline unsigned long cpu_util_cfs(struct rq *rq) ++{ ++ unsigned long ret = READ_ONCE(rq->load_avg); ++ ++ if (ret > SCHED_CAPACITY_SCALE) ++ ret = SCHED_CAPACITY_SCALE; ++ return ret; ++} ++ ++static inline unsigned long cpu_util_rt(struct rq *rq) ++{ ++ unsigned long ret = READ_ONCE(rq->rt_nr_running); ++ ++ if (ret > SCHED_CAPACITY_SCALE) ++ ret = SCHED_CAPACITY_SCALE; ++ return ret; ++} ++ ++#ifdef CONFIG_HAVE_SCHED_AVG_IRQ ++static inline unsigned long cpu_util_irq(struct rq *rq) ++{ ++ unsigned long ret = READ_ONCE(rq->irq_load_avg); ++ ++ if (ret > SCHED_CAPACITY_SCALE) ++ ret = SCHED_CAPACITY_SCALE; ++ return ret; ++} ++ ++static inline ++unsigned long scale_irq_capacity(unsigned long util, unsigned long irq, unsigned long max) ++{ ++ util *= (max - irq); ++ util /= max; ++ ++ return util; ++ ++} ++#else /* CONFIG_HAVE_SCHED_AVG_IRQ */ ++static inline unsigned long cpu_util_irq(struct rq *rq) ++{ ++ return 0; ++} ++ ++static inline ++unsigned long scale_irq_capacity(unsigned long util, unsigned long irq, unsigned long max) ++{ ++ return util; ++} ++#endif /* CONFIG_HAVE_SCHED_AVG_IRQ */ ++#endif /* CONFIG_SMP */ ++ ++#if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL) ++#define perf_domain_span(pd) (to_cpumask(((pd)->em_pd->cpus))) ++ ++DECLARE_STATIC_KEY_FALSE(sched_energy_present); ++ ++static inline bool sched_energy_enabled(void) ++{ ++ return static_branch_unlikely(&sched_energy_present); ++} ++ ++#else /* ! (CONFIG_ENERGY_MODEL && CONFIG_CPU_FREQ_GOV_SCHEDUTIL) */ ++ ++#define perf_domain_span(pd) NULL ++static inline bool sched_energy_enabled(void) { return false; } ++ ++#endif /* CONFIG_ENERGY_MODEL && CONFIG_CPU_FREQ_GOV_SCHEDUTIL */ ++ ++#ifdef CONFIG_MEMBARRIER ++/* ++ * The scheduler provides memory barriers required by membarrier between: ++ * - prior user-space memory accesses and store to rq->membarrier_state, ++ * - store to rq->membarrier_state and following user-space memory accesses. ++ * In the same way it provides those guarantees around store to rq->curr. ++ */ ++static inline void membarrier_switch_mm(struct rq *rq, ++ struct mm_struct *prev_mm, ++ struct mm_struct *next_mm) ++{ ++ int membarrier_state; ++ ++ if (prev_mm == next_mm) ++ return; ++ ++ membarrier_state = atomic_read(&next_mm->membarrier_state); ++ if (READ_ONCE(rq->membarrier_state) == membarrier_state) ++ return; ++ ++ WRITE_ONCE(rq->membarrier_state, membarrier_state); ++} ++#else ++static inline void membarrier_switch_mm(struct rq *rq, ++ struct mm_struct *prev_mm, ++ struct mm_struct *next_mm) ++{ ++} ++#endif ++ ++#ifdef CONFIG_SMP ++static inline bool is_per_cpu_kthread(struct task_struct *p) ++{ ++ if (!(p->flags & PF_KTHREAD)) ++ return false; ++ ++ if (p->nr_cpus_allowed != 1) ++ return false; ++ ++ return true; ++} ++#endif ++ ++void swake_up_all_locked(struct swait_queue_head *q); ++void __prepare_to_swait(struct swait_queue_head *q, struct swait_queue *wait); ++ ++/* pelt.h compat CONFIG_SCHED_THERMAL_PRESSURE impossible with MUQSS */ ++static inline int ++update_thermal_load_avg(u64 now, struct rq *rq, u64 capacity) ++{ ++ return 0; ++} ++ ++static inline u64 thermal_load_avg(struct rq *rq) ++{ ++ return 0; ++} ++ ++#ifdef CONFIG_RCU_TORTURE_TEST ++extern int sysctl_sched_rt_runtime; ++#endif ++ ++/* ++ * Asymmetric CPU capacity bits, used by topology.c. MuQSS does not do ++ * capacity-aware placement, but the domain build code still maintains the ++ * list. ++ */ ++struct asym_cap_data { ++ struct list_head link; ++ struct rcu_head rcu; ++ unsigned long capacity; ++ unsigned long cpus[]; ++}; ++ ++extern struct list_head asym_cap_list; ++ ++#define cpu_capacity_span(asym_data) to_cpumask((asym_data)->cpus) ++ ++/* ++ * Stubs for mainline scheduler machinery MuQSS does not implement. These are ++ * referenced by the support files retained in build_muqss.c. ++ */ ++ ++/* No deadline server: topology.c attaches none. See rq->fair_server. */ ++static inline void __dl_server_attach_root(void *dl_se, struct rq *rq) ++{ ++} ++ ++/* sched_ext is mutually exclusive with MuQSS; schedutil still asks. */ ++static inline u32 scx_cpuperf_target(s32 cpu) ++{ ++ return 0; ++} ++ ++static inline bool scx_switched_all(void) ++{ ++ return false; ++} ++ ++/* No nohz idle balancing: MuQSS balances implicitly at task selection. */ ++static inline void nohz_run_idle_balance(int cpu) ++{ ++} ++ ++/* No utilisation clamping, and no CFS utilisation to boost or clamp. */ ++static inline bool uclamp_rq_is_capped(struct rq *rq) ++{ ++ return false; ++} ++ ++static inline unsigned long cpu_util_cfs_boost(int cpu) ++{ ++ return 0; ++} ++ ++#ifdef CONFIG_IRQ_TIME_ACCOUNTING ++DECLARE_STATIC_KEY_FALSE(sched_clock_irqtime); ++ ++static inline int irqtime_enabled(void) ++{ ++ return static_branch_likely(&sched_clock_irqtime); ++} ++#else ++static inline int irqtime_enabled(void) ++{ ++ return 0; ++} ++#endif ++ ++extern unsigned long sugov_effective_cpu_perf(int cpu, unsigned long actual, ++ unsigned long min, ++ unsigned long max); ++ ++extern int try_to_wake_up(struct task_struct *p, unsigned int state, ++ int wake_flags); ++ ++asmlinkage void schedule_user(void); ++extern void resched_cpu(int cpu); ++extern bool available_idle_cpu(int cpu); ++extern void do_set_cpus_allowed(struct task_struct *p, ++ struct affinity_context *ctx); ++extern void __do_set_cpus_allowed(struct task_struct *p, ++ struct affinity_context *ctx); ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++extern int sched_dynamic_mode(const char *str); ++extern void sched_dynamic_update(int mode); ++#endif ++ ++#ifdef CONFIG_CGROUP_SCHED ++extern struct task_group *sched_create_group(struct task_group *parent); ++extern void sched_online_group(struct task_group *tg, ++ struct task_group *parent); ++extern void sched_destroy_group(struct task_group *tg); ++extern void sched_release_group(struct task_group *tg); ++#endif ++ ++#endif /* MUQSS_SCHED_H */ +diff --git a/kernel/sched/build_muqss.c b/kernel/sched/build_muqss.c +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/build_muqss.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * The scheduler support files MuQSS retains from mainline, built in a single ++ * compilation unit for the same build-efficiency reasons as build_policy.c ++ * and build_utility.c. ++ * ++ * MuQSS replaces core.c, fair.c, rt.c, deadline.c, syscalls.c and stop_task.c ++ * outright, so neither of the mainline aggregates can be used as-is. The ++ * files gathered here are the subset of build_utility.c and build_policy.c ++ * that survives, and they are #included rather than built individually ++ * because several of them (clock.c, completion.c, psi.c) carry no includes of ++ * their own and only compile as part of an aggregate. ++ * ++ * Deliberately absent, and why: ++ * ++ * fair.c rt.c deadline.c ext.c stop_task.c policy replaced by MuQSS ++ * cpudeadline.c cpupri.c pelt.c supporting data for those classes ++ * syscalls.c MuQSS implements the syscalls ++ * core_sched.c SCHED_CORE gated off ++ * debug.c CFS/EEVDF runqueue introspection ++ * loadavg.c MuQSS keeps its own load average ++ * autogroup.c cpuacct.c gated off in Kconfig.MuQSS ++ */ ++ ++/* Headers: */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++#include ++ ++#include "sched.h" ++#include "stats.h" ++ ++/* psi.c needs wq_worker_last_func(); mainline gets this via sched.h. */ ++#include "../workqueue_internal.h" ++ ++/* Source code modules: */ ++ ++#include "clock.c" ++ ++#ifdef CONFIG_CPU_FREQ ++# include "cpufreq.c" ++#endif ++ ++#ifdef CONFIG_CPU_FREQ_GOV_SCHEDUTIL ++# include "cpufreq_schedutil.c" ++#endif ++ ++#ifdef CONFIG_SCHEDSTATS ++# include "stats.c" ++#endif ++ ++#include "completion.c" ++#include "swait.c" ++#include "wait_bit.c" ++#include "wait.c" ++ ++#include "cputime.c" ++#include "idle.c" ++ ++#ifdef CONFIG_SMP ++# include "topology.c" ++#endif ++ ++#ifdef CONFIG_PSI ++# include "psi.c" ++#endif ++ ++#ifdef CONFIG_MEMBARRIER ++# include "membarrier.c" ++#endif ++ ++#ifdef CONFIG_CPU_ISOLATION ++# include "isolation.c" ++#endif ++ ++#include "skip_list.c" +diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c +--- a/kernel/sched/cputime.c ++++ b/kernel/sched/cputime.c +@@ -305,7 +305,7 @@ static inline u64 account_other_time(u64 max) + #ifdef CONFIG_64BIT + static inline u64 read_sum_exec_runtime(struct task_struct *t) + { +- return t->se.sum_exec_runtime; ++ return tsk_seruntime(t); + } + #else /* !CONFIG_64BIT: */ + static u64 read_sum_exec_runtime(struct task_struct *t) +@@ -315,7 +315,7 @@ static u64 read_sum_exec_runtime(struct task_struct *t) + struct rq *rq; + + rq = task_rq_lock(t, &rf); +- ns = t->se.sum_exec_runtime; ++ ns = tsk_seruntime(t); + task_rq_unlock(rq, t, &rf); + + return ns; +@@ -689,6 +689,19 @@ void account_process_tick(struct task_struct *p, int user_tick) + { + u64 cputime, steal; + ++ /* ++ * MuQSS accounts this tick itself, from sched_tick() -> ++ * update_cpu_clock_tick(), which update_process_times() calls a few ++ * lines after calling us. Its accounting is in real nanoseconds rather ++ * than whole ticks, and it is the same p->utime/p->stime and the same ++ * kcpustat[] fields we would be adding to here, so running both counts ++ * every busy tick twice. That is invisible in per-task figures, since ++ * cputime_adjust() rescales utime/stime to sched_time, but it doubles ++ * the busy buckets of /proc/stat. Leave it all to MuQSS. ++ */ ++ if (IS_ENABLED(CONFIG_SCHED_MUQSS)) ++ return; ++ + if (vtime_accounting_enabled_this_cpu()) + return; + +@@ -811,7 +824,7 @@ void cputime_adjust(struct task_cputime *curr, struct prev_cputime *prev, + void task_cputime_adjusted(struct task_struct *p, u64 *ut, u64 *st) + { + struct task_cputime cputime = { +- .sum_exec_runtime = p->se.sum_exec_runtime, ++ .sum_exec_runtime = tsk_seruntime(p), + }; + + if (task_cputime(p, &cputime.utime, &cputime.stime)) +diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c +--- a/kernel/sched/idle.c ++++ b/kernel/sched/idle.c +@@ -454,6 +454,7 @@ void cpu_startup_entry(enum cpuhp_state state) + do_idle(); + } + ++#ifndef CONFIG_SCHED_MUQSS + /* + * idle-task scheduling class. + */ +@@ -591,3 +592,4 @@ DEFINE_SCHED_CLASS(idle) = { + .switching_to = switching_to_idle, + .update_curr = update_curr_idle, + }; ++#endif /* CONFIG_SCHED_MUQSS */ +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -5,6 +5,10 @@ + #ifndef _KERNEL_SCHED_SCHED_H + #define _KERNEL_SCHED_SCHED_H + ++#ifdef CONFIG_SCHED_MUQSS ++#include "MuQSS.h" ++#else /* CONFIG_SCHED_MUQSS */ ++ + #include + #include + #include +@@ -4216,4 +4220,6 @@ DEFINE_CLASS_IS_UNCONDITIONAL(sched_change) + + #include "ext/ext.h" + ++#endif /* CONFIG_SCHED_MUQSS */ ++ + #endif /* _KERNEL_SCHED_SCHED_H */ +diff --git a/kernel/sched/skip_list.c b/kernel/sched/skip_list.c +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/skip_list.c +@@ -0,0 +1,187 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ Copyright (C) 2011,2016 Con Kolivas. ++ ++ Code based on example originally by William Pugh. ++ ++Skip Lists are a probabilistic alternative to balanced trees, as ++described in the June 1990 issue of CACM and were invented by ++William Pugh in 1987. ++ ++A couple of comments about this implementation: ++The routine randomLevel has been hard-coded to generate random ++levels using p=0.25. It can be easily changed. ++ ++The insertion routine has been implemented so as to use the ++dirty hack described in the CACM paper: if a random level is ++generated that is more than the current maximum level, the ++current maximum level plus one is used instead. ++ ++Levels start at zero and go up to MaxLevel (which is equal to ++MaxNumberOfLevels-1). ++ ++The routines defined in this file are: ++ ++init: defines slnode ++ ++new_skiplist: returns a new, empty list ++ ++randomLevel: Returns a random level based on a u64 random seed passed to it. ++In MuQSS, the "niffy" time is used for this purpose. ++ ++insert(l, node, key): inserts node into l ordered by key. This operation ++occurs in O(log n) time. ++ ++delnode(slnode, l, node): deletes the node from l. This operation occurs ++in O(k) time where k is the number of levels of the node in question ++(max 4). The original delete function occurred in O(log n) time and ++involved a search. ++ ++MuQSS Notes: In this implementation of skiplists, there are bidirectional ++next/prev pointers. The node is embedded in task_struct; the task is ++recovered with container_of(). The key here is chosen by the scheduler ++so as to sort tasks according to the priority list requirements. The ++scheduler lookup occurs in O(1) time because it is always the first ++item in the level 0 linked list. Since the task struct embeds the node, ++it can also remove it much faster than the original implementation with ++the aid of prev<->next pointer manipulation and no searching. ++ ++*/ ++ ++#include ++#include ++#include ++#include ++ ++static struct kmem_cache *skiplist_cache __read_mostly; ++ ++#define MaxNumberOfLevels SKIPLIST_MAXLEVEL ++#define MaxLevel (MaxNumberOfLevels - 1) ++ ++void skiplist_init(skiplist_node *slnode) ++{ ++ int i; ++ ++ slnode->key = 0xFFFFFFFFFFFFFFFF; ++ for (i = 0; i < MaxNumberOfLevels; i++) ++ slnode->next[i] = slnode->prev[i] = slnode; ++} ++ ++void __init skiplist_cache_init(void) ++{ ++ skiplist_cache = kmem_cache_create("skiplist", sizeof(skiplist), ++ 0, SLAB_HWCACHE_ALIGN, NULL); ++ BUG_ON(!skiplist_cache); ++} ++ ++skiplist *new_skiplist(skiplist_node *slnode) ++{ ++ skiplist *l = kmem_cache_zalloc(skiplist_cache, GFP_ATOMIC); ++ ++ BUG_ON(!l); ++ l->header = slnode; ++ l->best_key = ~0ULL; ++ return l; ++} ++ ++void skiplist_free(skiplist *l) ++{ ++ kmem_cache_free(skiplist_cache, l); ++} ++ ++void free_skiplist(skiplist *l) ++{ ++ skiplist_node *p, *q; ++ ++ p = l->header; ++ do { ++ q = p->next[0]; ++ p->next[0]->prev[0] = q->prev[0]; ++ skiplist_node_init(p); ++ p = q; ++ } while (p != l->header); ++ skiplist_free(l); ++} ++ ++void skiplist_node_init(skiplist_node *node) ++{ ++ memset(node, 0, sizeof(skiplist_node)); ++} ++ ++static inline unsigned int randomLevel(const long unsigned int randseed) ++{ ++ /* ++ * Two bits per extra level (p = 0.25). Search 2*MaxLevel+1 bits ++ * so the result stays in 0..MaxLevel; this matches the old ++ * 8-slot arrays whose random height never exceeded 3. ++ */ ++ return find_first_bit(&randseed, MaxLevel * 2 + 1) / 2; ++} ++ ++void skiplist_insert(skiplist *l, skiplist_node *node, keyType key, unsigned int randseed) ++{ ++ skiplist_node *update[MaxNumberOfLevels]; ++ skiplist_node *p, *q; ++ int k = l->level; ++ ++ p = l->header; ++ do { ++ while (q = p->next[k], q->key <= key) ++ p = q; ++ update[k] = p; ++ } while (--k >= 0); ++ ++ WRITE_ONCE(l->entries, l->entries + 1); ++ k = randomLevel(randseed); ++ if (k > l->level) { ++ k = ++l->level; ++ update[k] = l->header; ++ } ++ ++ node->key = key; ++ do { ++ p = update[k]; ++ node->next[k] = p->next[k]; ++ p->next[k] = node; ++ node->prev[k] = p; ++ node->next[k]->prev[k] = node; ++ } while (--k >= 0); ++} ++ ++/* ++ * Unlink @node from @l. Returns true if it was linked and has been removed, ++ * false if it was not on any list and nothing was changed - the caller's own ++ * accounting has to be skipped along with the removal in that case. ++ */ ++bool skiplist_delete(skiplist *l, skiplist_node *node) ++{ ++ int k, m; ++ ++ /* ++ * Insert fills levels contiguously from 0 up and removal zeroes the ++ * whole node, so a NULL next[] marks the top of this node and stands ++ * in for the level count the node used to carry. ++ */ ++ for (k = 0; k < MaxNumberOfLevels && node->next[k]; k++) { ++ node->prev[k]->next[k] = node->next[k]; ++ node->next[k]->prev[k] = node->prev[k]; ++ } ++ m = k - 1; ++ /* ++ * A node that was never queued has no levels linked at all. Leave ++ * before decrementing entries, which would otherwise go negative and ++ * make the emptiness tests in earliest_deadline_task() pass on an ++ * empty list, handing the caller the header as if it were a task. ++ */ ++ if (WARN_ON_ONCE(m < 0)) ++ return false; ++ skiplist_node_init(node); ++ if (m == l->level) { ++ while (l->header->next[m] == l->header && l->header->prev[m] == l->header && m > 0) ++ m--; ++ l->level = m; ++ } ++ WRITE_ONCE(l->entries, l->entries - 1); ++ ++ return true; ++} +diff --git a/kernel/sched/stats.h b/kernel/sched/stats.h +--- a/kernel/sched/stats.h ++++ b/kernel/sched/stats.h +@@ -89,6 +89,8 @@ static inline void rq_sched_info_depart (struct rq *rq, unsigned long long delt + + #endif /* CONFIG_SCHEDSTATS */ + ++#ifndef CONFIG_SCHED_MUQSS ++/* Only reachable from fair.c, debug.c and core.c, none of which MuQSS builds. */ + static inline struct sched_statistics * + __schedstats_from_se(struct sched_entity *se) + { +@@ -98,6 +100,7 @@ __schedstats_from_se(struct sched_entity *se) + #endif + return &task_of(se)->stats; + } ++#endif /* CONFIG_SCHED_MUQSS */ + + #ifdef CONFIG_PSI + void psi_task_change(struct task_struct *task, int clear, int set); +@@ -135,6 +138,23 @@ static inline void psi_enqueue(struct task_struct *p, int flags) + if (task_on_cpu(task_rq(p), p)) + return; + ++#ifdef CONFIG_SCHED_MUQSS ++ /* ++ * MuQSS has no delayed-dequeue (no task_struct::se), so a migration ++ * always moves a runnable task and a non-migration is a wakeup. ++ */ ++ if (flags & ENQUEUE_MIGRATED) { ++ set = TSK_RUNNING; ++ if (p->in_memstall) ++ set |= TSK_MEMSTALL | TSK_MEMSTALL_RUNNING; ++ } else { ++ if (p->in_iowait) ++ clear |= TSK_IOWAIT; ++ set = TSK_RUNNING; ++ if (p->in_memstall) ++ set |= TSK_MEMSTALL_RUNNING; ++ } ++#else + if (p->se.sched_delayed) { + /* CPU migration of "sleeping" task */ + WARN_ON_ONCE(!(flags & ENQUEUE_MIGRATED)); +@@ -155,6 +175,7 @@ static inline void psi_enqueue(struct task_struct *p, int flags) + if (p->in_memstall) + set |= TSK_MEMSTALL_RUNNING; + } ++#endif /* CONFIG_SCHED_MUQSS */ + + psi_task_change(p, clear, set); + } +diff --git a/kernel/time/Kconfig b/kernel/time/Kconfig +--- a/kernel/time/Kconfig ++++ b/kernel/time/Kconfig +@@ -103,18 +103,36 @@ config NO_HZ_COMMON + bool + select TICK_ONESHOT + ++# ++# Silent symbols keep CONFIG_NO_HZ_* / CONFIG_HZ_PERIODIC available to the ++# rest of the tree. The promptable *_NODEF names are what olddefconfig can ++# stick on; renaming every choice member (including idle) means a saved ++# "# CONFIG_NO_HZ_IDLE is not set" cannot pin the choice onto full dynticks. ++# ++config HZ_PERIODIC ++ bool ++ ++config NO_HZ_IDLE ++ bool ++ ++config NO_HZ_FULL ++ bool ++ + choice + prompt "Timer tick handling" +- default NO_HZ_IDLE if NO_HZ ++ default NO_HZ_IDLE_NODEF if NO_HZ ++ default NO_HZ_IDLE_NODEF + +-config HZ_PERIODIC ++config HZ_PERIODIC_NODEF + bool "Periodic timer ticks (constant rate, no dynticks)" ++ select HZ_PERIODIC + help + This option keeps the tick running periodically at a constant + rate, even when the CPU doesn't need it. + +-config NO_HZ_IDLE ++config NO_HZ_IDLE_NODEF + bool "Idle dynticks system (tickless idle)" ++ select NO_HZ_IDLE + select NO_HZ_COMMON + help + This option enables a tickless idle system: timer interrupts +@@ -123,8 +141,9 @@ config NO_HZ_IDLE + + Most of the time you want to say Y here. + +-config NO_HZ_FULL ++config NO_HZ_FULL_NODEF + bool "Full dynticks system (tickless)" ++ select NO_HZ_FULL + # NO_HZ_COMMON dependency + # We need at least one periodic CPU for timekeeping + depends on SMP +@@ -150,6 +169,8 @@ config NO_HZ_FULL + By default, without passing the nohz_full parameter, this behaves just + like NO_HZ_IDLE. + ++ Not recommended for desktops, laptops, or mobile devices. ++ + If you're a distro say Y. + + endchoice +diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c +--- a/kernel/time/clockevents.c ++++ b/kernel/time/clockevents.c +@@ -194,8 +194,8 @@ int clockevents_tick_resume(struct clock_event_device *dev) + + #ifdef CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST + +-/* Limit min_delta to a jiffy */ +-#define MIN_DELTA_LIMIT (NSEC_PER_SEC / HZ) ++/* Limit min_delta to the configurable hrtimer granularity */ ++#define MIN_DELTA_LIMIT (hrtimer_granularity_us * NSEC_PER_USEC) + + /** + * clockevents_increase_min_delta - raise minimum delta of a clock event device +diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c +--- a/kernel/time/posix-cpu-timers.c ++++ b/kernel/time/posix-cpu-timers.c +@@ -223,7 +223,7 @@ static void task_sample_cputime(struct task_struct *p, u64 *samples) + u64 stime, utime; + + task_cputime(p, &utime, &stime); +- store_samples(samples, stime, utime, p->se.sum_exec_runtime); ++ store_samples(samples, stime, utime, tsk_seruntime(p)); + } + + static void proc_sample_cputime_atomic(struct task_cputime_atomic *at, +@@ -905,10 +905,12 @@ static void collect_posix_cputimers(struct posix_cputimers *pct, u64 *samples, + + static inline void check_dl_overrun(struct task_struct *tsk) + { ++#ifndef CONFIG_SCHED_MUQSS + if (tsk->dl.dl_overrun) { + tsk->dl.dl_overrun = 0; + send_signal_locked(SIGXCPU, SEND_SIG_PRIV, tsk, PIDTYPE_TGID); + } ++#endif + } + + static bool check_rlimit(u64 time, u64 limit, int signo, bool rt, bool hard) +@@ -952,7 +954,7 @@ static void check_thread_timers(struct task_struct *tsk, + soft = task_rlimit(tsk, RLIMIT_RTTIME); + if (soft != RLIM_INFINITY) { + /* Task RT timeout is accounted in jiffies. RTTIME is usec */ +- unsigned long rttime = tsk->rt.timeout * (USEC_PER_SEC / HZ); ++ unsigned long rttime = tsk_rttimeout(tsk) * (USEC_PER_SEC / HZ); + unsigned long hard = task_rlimit_max(tsk, RLIMIT_RTTIME); + + /* At the hard limit, send SIGKILL. No further action. */ +@@ -1183,7 +1185,7 @@ static inline bool fastpath_timer_check(struct task_struct *tsk) + return true; + } + +- if (dl_task(tsk) && tsk->dl.dl_overrun) ++ if (dl_task(tsk) && tsk_dl_overrun(tsk)) + return true; + + return false; +diff --git a/kernel/time/sleep_timeout.c b/kernel/time/sleep_timeout.c +--- a/kernel/time/sleep_timeout.c ++++ b/kernel/time/sleep_timeout.c +@@ -4,13 +4,57 @@ + */ + + #include ++#include + #include + #include + #include + #include ++#include + + #include "tick-internal.h" + ++/* ++ * Granularity, in microseconds, below which a high resolution timeout is not ++ * worth arming. Also caps how far clockevents_increase_min_delta() is allowed ++ * to raise a clock event device's minimum delta. ++ */ ++int __read_mostly hrtimer_granularity_us = 100; ++ ++/* The timeout, in microseconds, used by schedule_min_hrtimeout(). */ ++int __read_mostly hrtimeout_min_us = 500; ++ ++#ifdef CONFIG_SYSCTL ++static const int hrtimeout_us_max = 10000; ++ ++static const struct ctl_table hrtimeout_sysctl[] = { ++ { ++ .procname = "hrtimer_granularity_us", ++ .data = &hrtimer_granularity_us, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ONE, ++ .extra2 = (void *)&hrtimeout_us_max, ++ }, ++ { ++ .procname = "hrtimeout_min_us", ++ .data = &hrtimeout_min_us, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ONE, ++ .extra2 = (void *)&hrtimeout_us_max, ++ }, ++}; ++ ++static int __init hrtimeout_sysctl_init(void) ++{ ++ register_sysctl("kernel", hrtimeout_sysctl); ++ return 0; ++} ++device_initcall(hrtimeout_sysctl_init); ++#endif /* CONFIG_SYSCTL */ ++ + /* + * Since schedule_timeout()'s timer is defined on the stack, it must store + * the target task on the stack as well. +@@ -279,10 +323,164 @@ int __sched schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode) + } + EXPORT_SYMBOL_GPL(schedule_hrtimeout); + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* ++ * As per schedule_hrtimeout but takes a millisecond value and returns how ++ * many milliseconds are left. ++ */ ++long __sched schedule_msec_hrtimeout(long timeout) ++{ ++ struct hrtimer_sleeper t; ++ int delta, jiffs; ++ ktime_t expires; ++ ++ if (!timeout) { ++ __set_current_state(TASK_RUNNING); ++ return 0; ++ } ++ ++ jiffs = msecs_to_jiffies(timeout); ++ /* ++ * If regular timer resolution is adequate or hrtimer resolution is not ++ * (yet) better than Hz, as would occur during startup, use regular ++ * timers. Freezing gets the same treatment as some drivers still do not ++ * correctly use freezable timeouts. ++ */ ++ if (jiffs > 4 || hrtimer_resolution >= NSEC_PER_SEC / HZ || pm_freezing) ++ return schedule_timeout(jiffs); ++ ++ /* The jiffs > 4 test above bounds timeout well under one second. */ ++ delta = (timeout % 1000) * NSEC_PER_MSEC; ++ expires = ktime_set(0, delta); ++ ++ hrtimer_setup_sleeper_on_stack(&t, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ /* ++ * No slack is given here. The whole point of taking the high resolution ++ * path is that the timeout is too short for the tick to resolve, and a ++ * slack of delta would push the expiry out to twice the request. ++ */ ++ hrtimer_set_expires(&t.timer, expires); ++ ++ hrtimer_sleeper_start_expires(&t, HRTIMER_MODE_REL); ++ ++ if (likely(t.task)) ++ schedule(); ++ ++ hrtimer_cancel(&t.timer); ++ destroy_hrtimer_on_stack(&t.timer); ++ ++ __set_current_state(TASK_RUNNING); ++ ++ expires = hrtimer_expires_remaining(&t.timer); ++ timeout = ktime_to_ms(expires); ++ return timeout < 0 ? 0 : timeout; ++} ++ ++EXPORT_SYMBOL(schedule_msec_hrtimeout); ++ ++/* ++ * As per schedule_msec_hrtimeout but takes a microsecond value and returns ++ * how many microseconds are left. ++ */ ++static long __sched schedule_usec_hrtimeout(long timeout) ++{ ++ struct hrtimer_sleeper t; ++ ktime_t expires; ++ int delta; ++ ++ if (!timeout) { ++ __set_current_state(TASK_RUNNING); ++ return 0; ++ } ++ ++ if (hrtimer_resolution >= NSEC_PER_SEC / HZ) ++ return schedule_timeout(usecs_to_jiffies(timeout)); ++ ++ if (timeout < hrtimer_granularity_us) ++ timeout = hrtimer_granularity_us; ++ delta = (timeout % USEC_PER_SEC) * NSEC_PER_USEC; ++ expires = ktime_set(0, delta); ++ ++ hrtimer_setup_sleeper_on_stack(&t, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ /* No slack is given here, as per schedule_msec_hrtimeout(). */ ++ hrtimer_set_expires(&t.timer, expires); ++ ++ hrtimer_sleeper_start_expires(&t, HRTIMER_MODE_REL); ++ ++ if (likely(t.task)) ++ schedule(); ++ ++ hrtimer_cancel(&t.timer); ++ destroy_hrtimer_on_stack(&t.timer); ++ ++ __set_current_state(TASK_RUNNING); ++ ++ expires = hrtimer_expires_remaining(&t.timer); ++ timeout = ktime_to_us(expires); ++ return timeout < 0 ? 0 : timeout; ++} ++ ++long __sched schedule_min_hrtimeout(void) ++{ ++ return usecs_to_jiffies(schedule_usec_hrtimeout(hrtimeout_min_us)); ++} ++ ++EXPORT_SYMBOL(schedule_min_hrtimeout); ++ ++long __sched schedule_msec_hrtimeout_interruptible(long timeout) ++{ ++ __set_current_state(TASK_INTERRUPTIBLE); ++ return schedule_msec_hrtimeout(timeout); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout_interruptible); ++ ++long __sched schedule_msec_hrtimeout_uninterruptible(long timeout) ++{ ++ __set_current_state(TASK_UNINTERRUPTIBLE); ++ return schedule_msec_hrtimeout(timeout); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout_uninterruptible); ++ ++#else /* !CONFIG_HIGH_RES_TIMERS */ ++ ++/* ++ * Without high resolution timers there is nothing better than the regular ++ * jiffy based timeouts to fall back on. ++ */ ++long __sched schedule_msec_hrtimeout(long timeout) ++{ ++ return schedule_timeout(msecs_to_jiffies(timeout)); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout); ++ ++long __sched schedule_min_hrtimeout(void) ++{ ++ return schedule_timeout(1); ++} ++EXPORT_SYMBOL(schedule_min_hrtimeout); ++ ++long __sched schedule_msec_hrtimeout_interruptible(long timeout) ++{ ++ return schedule_timeout_interruptible(msecs_to_jiffies(timeout)); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout_interruptible); ++ ++long __sched schedule_msec_hrtimeout_uninterruptible(long timeout) ++{ ++ return schedule_timeout_uninterruptible(msecs_to_jiffies(timeout)); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout_uninterruptible); ++ ++#endif /* CONFIG_HIGH_RES_TIMERS */ ++ + /** + * msleep - sleep safely even with waitqueue interruptions + * @msecs: Requested sleep duration in milliseconds + * ++ * Sleeps shorter than five ticks are handed to a high resolution timer instead, ++ * where that has better resolution than the tick, so the slack described below ++ * only applies to the jiffy based path. ++ * + * msleep() uses jiffy based timeouts for the sleep duration. Because of the + * design of the timer wheel, the maximum additional percentage delay (slack) is + * 12.5%. This is only valid for timers which will end up in level 1 or a higher +@@ -312,7 +510,19 @@ EXPORT_SYMBOL_GPL(schedule_hrtimeout); + */ + void msleep(unsigned int msecs) + { +- unsigned long timeout = msecs_to_jiffies(msecs); ++ int jiffs = msecs_to_jiffies(msecs); ++ unsigned long timeout; ++ ++ /* ++ * Use high resolution timers where the resolution of tick based ++ * timers is inadequate. ++ */ ++ if (jiffs < 5 && hrtimer_resolution < NSEC_PER_SEC / HZ && !pm_freezing) { ++ while (msecs) ++ msecs = schedule_msec_hrtimeout_uninterruptible(msecs); ++ return; ++ } ++ timeout = jiffs; + + while (timeout) + timeout = schedule_timeout_uninterruptible(timeout); +@@ -333,7 +543,15 @@ EXPORT_SYMBOL(msleep); + */ + unsigned long msleep_interruptible(unsigned int msecs) + { +- unsigned long timeout = msecs_to_jiffies(msecs); ++ int jiffs = msecs_to_jiffies(msecs); ++ unsigned long timeout; ++ ++ if (jiffs < 5 && hrtimer_resolution < NSEC_PER_SEC / HZ && !pm_freezing) { ++ while (msecs && !signal_pending(current)) ++ msecs = schedule_msec_hrtimeout_interruptible(msecs); ++ return msecs; ++ } ++ timeout = jiffs; + + while (timeout && !signal_pending(current)) + timeout = schedule_timeout_interruptible(timeout); +diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h +--- a/kernel/time/tick-internal.h ++++ b/kernel/time/tick-internal.h +@@ -13,6 +13,9 @@ struct timer_events { + u64 global; + }; + ++/* Defined in sleep_timeout.c, tunable via sysctl */ ++extern int hrtimer_granularity_us; ++ + #ifdef CONFIG_GENERIC_CLOCKEVENTS + + # define TICK_DO_TIMER_NONE -1 +diff --git a/kernel/trace/rv/monitors/deadline/Kconfig b/kernel/trace/rv/monitors/deadline/Kconfig +--- a/kernel/trace/rv/monitors/deadline/Kconfig ++++ b/kernel/trace/rv/monitors/deadline/Kconfig +@@ -1,5 +1,5 @@ + config RV_MON_DEADLINE +- depends on RV ++ depends on RV && !SCHED_MUQSS + bool "deadline monitor" + help + Collection of monitors to check the deadline scheduler and server +diff --git a/kernel/workqueue.c b/kernel/workqueue.c +--- a/kernel/workqueue.c ++++ b/kernel/workqueue.c +@@ -56,6 +56,7 @@ + #include + #include + #include ++#include + + #include "workqueue_internal.h" + +@@ -1438,7 +1439,7 @@ void wq_worker_running(struct task_struct *task) + * CPU intensive auto-detection cares about how long a work item hogged + * CPU without sleeping. Reset the starting timestamp on wakeup. + */ +- worker->current_at = worker->task->se.sum_exec_runtime; ++ worker->current_at = tsk_seruntime(worker->task); + + WRITE_ONCE(worker->sleeping, 0); + } +@@ -1523,7 +1524,7 @@ void wq_worker_tick(struct task_struct *task) + * We probably want to make this prettier in the future. + */ + if ((worker->flags & WORKER_NOT_RUNNING) || READ_ONCE(worker->sleeping) || +- worker->task->se.sum_exec_runtime - worker->current_at < ++ tsk_seruntime(worker->task) - worker->current_at < + wq_cpu_intensive_thresh_us * NSEC_PER_USEC) + return; + +@@ -2449,6 +2450,7 @@ bool queue_work_on(int cpu, struct workqueue_struct *wq, + + if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) && + !clear_pending_if_disabled(work)) { ++ muqss_work_set_owner(work); + __queue_work(cpu, wq, work); + ret = true; + } +@@ -2530,6 +2532,7 @@ bool queue_work_node(int node, struct workqueue_struct *wq, + !clear_pending_if_disabled(work)) { + int cpu = select_numa_node_cpu(node); + ++ muqss_work_set_owner(work); + __queue_work(cpu, wq, work); + ret = true; + } +@@ -2617,6 +2620,7 @@ bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq, + + if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) && + !clear_pending_if_disabled(work)) { ++ muqss_work_set_owner(work); + __queue_delayed_work(cpu, wq, dwork, delay); + ret = true; + } +@@ -2652,8 +2656,10 @@ bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq, + + ret = work_grab_pending(&dwork->work, WORK_CANCEL_DELAYED, &irq_flags); + +- if (!clear_pending_if_disabled(&dwork->work)) ++ if (!clear_pending_if_disabled(&dwork->work)) { ++ muqss_work_set_owner(&dwork->work); + __queue_delayed_work(cpu, wq, dwork, delay); ++ } + + local_irq_restore(irq_flags); + return ret; +@@ -2690,6 +2696,7 @@ bool queue_rcu_work(struct workqueue_struct *wq, struct rcu_work *rwork) + */ + if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) && + !WARN_ON_ONCE(clear_pending_if_disabled(work))) { ++ muqss_work_set_owner(work); + rwork->wq = wq; + call_rcu_hurry(&rwork->rcu, rcu_work_rcufn); + return true; +@@ -3223,7 +3230,9 @@ __acquires(&pool->lock) + { + struct pool_workqueue *pwq = get_work_pwq(work); + struct worker_pool *pool = worker->pool; ++ struct muqss_kern_window kern_window; + unsigned long work_data; ++ unsigned int muqss_owner; + int lockdep_start_depth, rcu_start_depth; + bool bh_draining = pool->flags & POOL_BH_DRAINING; + #ifdef CONFIG_LOCKDEP +@@ -3249,11 +3258,23 @@ __acquires(&pool->lock) + worker->current_func = work->func; + worker->current_pwq = pwq; + if (worker->task) +- worker->current_at = worker->task->se.sum_exec_runtime; ++ worker->current_at = tsk_seruntime(worker->task); + worker->current_start = jiffies; + work_data = *work_data_bits(work); + worker->current_color = get_work_color(work_data); + ++ /* ++ * Read while PENDING still holds this work item for us. Once it is ++ * cleared below another CPU may requeue the same item and restamp it ++ * with a different owner, and the one we are about to run belongs to ++ * whoever queued it, not to them. ++ * ++ * A BH pool runs work in softirq context on top of whichever task was ++ * interrupted, so worker->task is NULL and there is no worker runtime ++ * to measure. Charge nobody there. ++ */ ++ muqss_owner = worker->task ? muqss_work_owner(work) : 0; ++ + /* + * Record wq name for cmdline and debug reporting, may get + * overridden through set_worker_desc(). +@@ -3318,6 +3339,13 @@ __acquires(&pool->lock) + * workqueues), so hiding them isn't a problem. + */ + lockdep_invariant_state(true); ++ /* ++ * Everything from here to muqss_kerntime_end() is done on the queueing ++ * task's account: the CPU time it costs this worker is charged back to ++ * it, and so is any I/O the work item submits or any page it dirties, ++ * which would otherwise be lost to a kernel thread. ++ */ ++ muqss_kerntime_begin(&kern_window, muqss_owner); + trace_workqueue_execute_start(work); + worker->current_func(work); + /* +@@ -3325,6 +3353,7 @@ __acquires(&pool->lock) + * point will only record its address. + */ + trace_workqueue_execute_end(work, worker->current_func); ++ muqss_kerntime_end(&kern_window); + + lock_map_release(&lockdep_map); + if (!bh_draining) +diff --git a/mm/khugepaged.c b/mm/khugepaged.c +--- a/mm/khugepaged.c ++++ b/mm/khugepaged.c +@@ -969,7 +969,7 @@ static void khugepaged_alloc_sleep(void) + + add_wait_queue(&khugepaged_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE|TASK_FREEZABLE); +- schedule_timeout(msecs_to_jiffies(khugepaged_alloc_sleep_millisecs)); ++ schedule_msec_hrtimeout(khugepaged_alloc_sleep_millisecs); + remove_wait_queue(&khugepaged_wait, &wait); + } + +diff --git a/mm/page-writeback.c b/mm/page-writeback.c +--- a/mm/page-writeback.c ++++ b/mm/page-writeback.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -2636,6 +2637,12 @@ static void folio_account_dirtied(struct folio *folio, + current->nr_dirtied += nr; + __this_cpu_add(bdp_ratelimits, nr); + ++ /* ++ * Record who dirtied this so the writeback issued for it later ++ * by a flusher thread can still be charged to them. ++ */ ++ muqss_iotime_dirty_folio(folio); ++ + mem_cgroup_track_foreign_dirty(folio, wb); + } + } +diff --git a/mm/page_alloc.c b/mm/page_alloc.c +--- a/mm/page_alloc.c ++++ b/mm/page_alloc.c +@@ -1401,6 +1401,7 @@ static __always_inline bool __free_pages_prepare(struct page *page, + } + + page_cpupid_reset_last(page); ++ page_io_owner_reset(page); + page->flags.f &= ~PAGE_FLAGS_CHECK_AT_PREP; + page->private = 0; + reset_page_owner(page, order); +diff --git a/net/core/pktgen.c b/net/core/pktgen.c +--- a/net/core/pktgen.c ++++ b/net/core/pktgen.c +@@ -1992,7 +1992,7 @@ static ssize_t pktgen_thread_write(struct file *file, + mutex_lock(&pktgen_thread_lock); + t->control |= T_REMDEVALL; + mutex_unlock(&pktgen_thread_lock); +- schedule_timeout_interruptible(msecs_to_jiffies(125)); /* Propagate thread->control */ ++ schedule_msec_hrtimeout_interruptible(125); /* Propagate thread->control */ + ret = count; + sprintf(pg_result, "OK: rem_device_all"); + goto out; +@@ -2064,7 +2064,7 @@ static void pktgen_mark_device(const struct pktgen_net *pn, const char *ifname) + mutex_unlock(&pktgen_thread_lock); + pr_debug("%s: waiting for %s to disappear....\n", + __func__, ifname); +- schedule_timeout_interruptible(msecs_to_jiffies(msec_per_try)); ++ schedule_msec_hrtimeout_interruptible(msec_per_try); + mutex_lock(&pktgen_thread_lock); + + if (++i >= max_tries) { +@@ -3321,7 +3321,7 @@ static void pktgen_run_all_threads(struct pktgen_net *pn) + pktgen_handle_all_threads(pn, T_RUN); + + /* Propagate thread->control */ +- schedule_timeout_interruptible(msecs_to_jiffies(125)); ++ schedule_msec_hrtimeout_interruptible(125); + + pktgen_wait_all_threads_run(pn); + } +@@ -3333,7 +3333,7 @@ static void pktgen_reset_all_threads(struct pktgen_net *pn) + pktgen_handle_all_threads(pn, T_REMDEVALL); + + /* Propagate thread->control */ +- schedule_timeout_interruptible(msecs_to_jiffies(125)); ++ schedule_msec_hrtimeout_interruptible(125); + + pktgen_wait_all_threads_run(pn); + } +diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c +--- a/sound/hda/core/controller.c ++++ b/sound/hda/core/controller.c +@@ -340,7 +340,7 @@ static int snd_hdac_bus_get_response_rirb(struct hdac_bus *bus, + break; + #define LOOP_COUNT_MAX 3000 + if (!bus->polling_mode) { +- schedule_timeout(msecs_to_jiffies(2)); ++ schedule_msec_hrtimeout(2); + } else if (bus->needs_damn_long_delay || + loopcounter > LOOP_COUNT_MAX) { + if (loopcounter > LOOP_COUNT_MAX && !warned) { +diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c +--- a/sound/isa/msnd/msnd_pinnacle.c ++++ b/sound/isa/msnd/msnd_pinnacle.c +@@ -490,7 +490,7 @@ static int snd_msnd_calibrate_adc(struct snd_msnd *chip, u16 srate) + & ~0x0001, chip->SMA + SMA_wCurrHostStatusFlags); + if (snd_msnd_send_word(chip, 0, 0, HDEXAR_CAL_A_TO_D) == 0 && + snd_msnd_send_dsp_cmd_chk(chip, HDEX_AUX_REQ) == 0) { +- schedule_timeout_interruptible(msecs_to_jiffies(333)); ++ schedule_msec_hrtimeout_interruptible(333); + return 0; + } + dev_warn(chip->card->dev, LOGNAME ": ADC calibration failed\n"); +diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c +--- a/sound/pci/maestro3.c ++++ b/sound/pci/maestro3.c +@@ -1999,7 +1999,7 @@ static void snd_m3_ac97_reset(struct snd_m3 *chip) + outw(0, io + GPIO_DATA); + outw(dir | GPO_PRIMARY_AC97, io + GPIO_DIRECTION); + +- schedule_timeout_uninterruptible(msecs_to_jiffies(delay1)); ++ schedule_msec_hrtimeout_uninterruptible(delay1); + + outw(GPO_PRIMARY_AC97, io + GPIO_DATA); + udelay(5); +@@ -2007,7 +2007,7 @@ static void snd_m3_ac97_reset(struct snd_m3 *chip) + outw(IO_SRAM_ENABLE | SERIAL_AC_LINK_ENABLE, io + RING_BUS_CTRL_A); + outw(~0, io + GPIO_MASK); + +- schedule_timeout_uninterruptible(msecs_to_jiffies(delay2)); ++ schedule_msec_hrtimeout_uninterruptible(delay2); + + if (! snd_m3_try_read_vendor(chip)) + break; +@@ -2054,7 +2054,7 @@ static int snd_m3_mixer(struct snd_m3 *chip) + + /* seems ac97 PCM needs initialization.. hack hack.. */ + snd_ac97_write(chip->ac97, AC97_PCM, 0x8000 | (15 << 8) | 15); +- schedule_timeout_uninterruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_uninterruptible(100); + snd_ac97_write(chip->ac97, AC97_PCM, 0); + + #ifndef CONFIG_SND_MAESTRO3_INPUT +diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c +--- a/sound/pci/ymfpci/ymfpci_main.c ++++ b/sound/pci/ymfpci/ymfpci_main.c +@@ -742,7 +742,7 @@ static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip) + init_waitqueue_entry(&wait, current); + add_wait_queue(&chip->interrupt_sleep, &wait); + atomic_inc(&chip->interrupt_sleep_count); +- schedule_timeout_uninterruptible(msecs_to_jiffies(50)); ++ schedule_msec_hrtimeout_uninterruptible(50); + remove_wait_queue(&chip->interrupt_sleep, &wait); + } + } +diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c +--- a/sound/soc/codecs/rt5631.c ++++ b/sound/soc/codecs/rt5631.c +@@ -417,7 +417,7 @@ static void onebit_depop_mute_stage(struct snd_soc_component *component, int ena + hp_zc = snd_soc_component_read(component, RT5631_INT_ST_IRQ_CTRL_2); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc & 0xf7ff); + if (enable) { +- schedule_timeout_uninterruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_uninterruptible(10); + /* config one-bit depop parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x307f); + snd_soc_component_update_bits(component, RT5631_HP_OUT_VOL, +@@ -529,7 +529,7 @@ static void depop_seq_mute_stage(struct snd_soc_component *component, int enable + hp_zc = snd_soc_component_read(component, RT5631_INT_ST_IRQ_CTRL_2); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc & 0xf7ff); + if (enable) { +- schedule_timeout_uninterruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_uninterruptible(10); + + /* config depop sequence parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x302f); +@@ -1496,8 +1496,7 @@ static int rt5631_codec_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + "change PLL in master mode\n"); + snd_soc_component_write(component, RT5631_PLL_CTRL, + codec_master_pll_div[i].reg_val); +- schedule_timeout_uninterruptible( +- msecs_to_jiffies(20)); ++ schedule_msec_hrtimeout_uninterruptible(20); + snd_soc_component_update_bits(component, + RT5631_GLOBAL_CLK_CTRL, + RT5631_SYSCLK_SOUR_SEL_MASK | +@@ -1515,8 +1514,7 @@ static int rt5631_codec_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + "change PLL in slave mode\n"); + snd_soc_component_write(component, RT5631_PLL_CTRL, + codec_slave_pll_div[i].reg_val); +- schedule_timeout_uninterruptible( +- msecs_to_jiffies(20)); ++ schedule_msec_hrtimeout_uninterruptible(20); + snd_soc_component_update_bits(component, + RT5631_GLOBAL_CLK_CTRL, + RT5631_SYSCLK_SOUR_SEL_MASK | +diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c +--- a/sound/soc/codecs/wm8350.c ++++ b/sound/soc/codecs/wm8350.c +@@ -234,11 +234,9 @@ static void wm8350_pga_work(struct work_struct *work) + out2->ramp == WM8350_RAMP_UP) { + /* delay is longer over 0dB as increases are larger */ + if (i >= WM8350_OUTn_0dB) +- schedule_timeout_interruptible(msecs_to_jiffies +- (2)); ++ schedule_msec_hrtimeout_interruptible(2); + else +- schedule_timeout_interruptible(msecs_to_jiffies +- (1)); ++ schedule_msec_hrtimeout_interruptible(1); + } else + udelay(50); /* doesn't matter if we delay longer */ + } +@@ -1122,9 +1120,7 @@ static int wm8350_set_bias_level(struct snd_soc_component *component, + (platform->dis_out4 << 6)); + + /* wait for discharge */ +- schedule_timeout_interruptible(msecs_to_jiffies +- (platform-> +- cap_discharge_msecs)); ++ schedule_msec_hrtimeout_interruptible(platform->cap_discharge_msecs); + + /* enable antipop */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, +@@ -1138,9 +1134,7 @@ static int wm8350_set_bias_level(struct snd_soc_component *component, + WM8350_VBUFEN); + + /* wait for vmid */ +- schedule_timeout_interruptible(msecs_to_jiffies +- (platform-> +- vmid_charge_msecs)); ++ schedule_msec_hrtimeout_interruptible(platform->vmid_charge_msecs); + + /* turn on vmid 300k */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & +@@ -1189,9 +1183,7 @@ static int wm8350_set_bias_level(struct snd_soc_component *component, + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); + + /* wait */ +- schedule_timeout_interruptible(msecs_to_jiffies +- (platform-> +- vmid_discharge_msecs)); ++ schedule_msec_hrtimeout_interruptible(platform->vmid_discharge_msecs); + + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + (platform->vmid_s_curve << 8) | +@@ -1207,8 +1199,7 @@ static int wm8350_set_bias_level(struct snd_soc_component *component, + pm1 | WM8350_OUTPUT_DRAIN_EN); + + /* wait */ +- schedule_timeout_interruptible(msecs_to_jiffies +- (platform->drain_msecs)); ++ schedule_msec_hrtimeout_interruptible(platform->drain_msecs); + + pm1 &= ~WM8350_BIASEN; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); +diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c +--- a/sound/soc/codecs/wm8900.c ++++ b/sound/soc/codecs/wm8900.c +@@ -1057,7 +1057,7 @@ static int wm8900_set_bias_level(struct snd_soc_component *component, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1); + + /* Allow capacitors to charge */ +- schedule_timeout_interruptible(msecs_to_jiffies(400)); ++ schedule_msec_hrtimeout_interruptible(400); + + /* Enable bias */ + snd_soc_component_write(component, WM8900_REG_POWER1, +@@ -1091,7 +1091,7 @@ static int wm8900_set_bias_level(struct snd_soc_component *component, + /* Discharge caps */ + snd_soc_component_write(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); +- schedule_timeout_interruptible(msecs_to_jiffies(500)); ++ schedule_msec_hrtimeout_interruptible(500); + + /* Remove clamp */ + snd_soc_component_write(component, WM8900_REG_HPCTL1, 0); +@@ -1105,7 +1105,7 @@ static int wm8900_set_bias_level(struct snd_soc_component *component, + /* Need to let things settle before stopping the clock + * to ensure that restart works, see "Stopping the + * master clock" in the datasheet. */ +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + snd_soc_component_write(component, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + break; +diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c +--- a/sound/soc/codecs/wm9713.c ++++ b/sound/soc/codecs/wm9713.c +@@ -199,7 +199,7 @@ static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w, + + /* Gracefully shut down the voice interface. */ + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0f00, 0x0200); +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0f00, 0x0f00); + snd_soc_component_update_bits(component, AC97_EXTENDED_MID, 0x1000, 0x1000); + +@@ -864,7 +864,7 @@ static int wm9713_set_pll(struct snd_soc_component *component, + wm9713->pll_in = freq_in; + + /* wait 10ms AC97 link frames for the link to stabilise */ +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + return 0; + } + +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -166,7 +166,7 @@ static void dapm_assert_locked(struct snd_soc_dapm_context *dapm) + static void dapm_pop_wait(void) + { + if (pop_time) +- schedule_timeout_uninterruptible(msecs_to_jiffies(pop_time)); ++ schedule_msec_hrtimeout_uninterruptible(pop_time); + } + + __printf(2, 3) +diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c +--- a/sound/usb/line6/pcm.c ++++ b/sound/usb/line6/pcm.c +@@ -127,7 +127,7 @@ static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm, + if (!alive) + break; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } while (--timeout > 0); + if (alive) + dev_err(line6pcm->line6->ifcdev, +diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile +--- a/tools/objtool/Makefile ++++ b/tools/objtool/Makefile +@@ -101,9 +101,19 @@ LIBOPCODES_LIBS := $(shell \ + HAVE_DISASM_STYLED := $(shell echo '$(pound)include ' | \ + $(HOSTCC) -E -xc - 2>/dev/null | grep -q disassembler_style && echo y) + ++# ++# Linking -lopcodes can succeed when the shared library is present without ++# development headers. That is common on Gentoo without sys-libs/binutils-libs ++# (bfd.h lives under /usr/lib64/binutils///include, not ++# /usr/include). Do not enable DISAS unless the headers actually compile. ++# ++HAVE_DISAS_HEADERS := $(shell printf "$(pound)include \n$(pound)include \nint main(void) { return 0; }\n" | \ ++ $(HOSTCC) $(HOSTCFLAGS) -DPACKAGE='"objtool"' -xc - -c -o /dev/null 2>/dev/null && echo y) ++ + BUILD_DISAS := n + + ifneq ($(LIBOPCODES_LIBS),) ++ifeq ($(HAVE_DISAS_HEADERS),y) + BUILD_DISAS := y + OBJTOOL_CFLAGS += -DDISAS -DPACKAGE='"objtool"' + OBJTOOL_LDFLAGS += $(LIBOPCODES_LIBS) +@@ -111,6 +121,7 @@ ifeq ($(HAVE_DISASM_STYLED),y) + OBJTOOL_CFLAGS += -DDISASM_INIT_STYLED + endif + endif ++endif + + export BUILD_DISAS + diff --git a/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch.sig b/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..8af6db4e58339073292bb0293087d4b2086eca7e GIT binary patch literal 594 zcmV-Y0@xr9Ll}2}QTJ9kXGd z_C9@5C?^e^{CzNq4CRot(zjBZ!N5(~m#t52e^Nl@pP>&;6^T1*!|=T+*0f6Va)MmU zF9LPb?%f8A?RoUszXjJwctIF#q4uVx{lH#MQGDbdC#KWFQv>t*)}5VdyQ}O4oFV$Q?0-gD>WKW5h|? z8~JTU7o^`_nb8bb^eIE=0ytW+6OridEz3s}a4QpLeY6c7L_r-QrxA7Osc)4fn1F0f gV$dl;aJrR8{H~ASaH2|LJ;kZ+NXNHSKmJsgP2a8^P5=M^ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch b/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch new file mode 100644 index 0000000..14e2d46 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch @@ -0,0 +1,582 @@ +diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h +--- a/arch/x86/include/asm/pgtable.h ++++ b/arch/x86/include/asm/pgtable.h +@@ -50,7 +50,7 @@ void ptdump_walk_user_pgd_level_checkwx(void); + extern spinlock_t pgd_lock; + extern struct list_head pgd_list; + +-extern struct mm_struct *pgd_page_get_mm(struct page *page); ++struct mm_struct *pgd_page_get_mm(struct ptdesc *pt); + + extern pmdval_t early_pmd_flags; + +diff --git a/arch/x86/include/asm/pgtable_types.h b/arch/x86/include/asm/pgtable_types.h +--- a/arch/x86/include/asm/pgtable_types.h ++++ b/arch/x86/include/asm/pgtable_types.h +@@ -512,7 +512,7 @@ static inline pgprot_t pgprot_large_2_4k(pgprot_t pgprot) + return __pgprot(protval_large_2_4k(pgprot_val(pgprot))); + } + +- ++struct ptdesc; + typedef struct page *pgtable_t; + + extern pteval_t __supported_pte_mask; +diff --git a/arch/x86/include/asm/tlbflush.h b/arch/x86/include/asm/tlbflush.h +--- a/arch/x86/include/asm/tlbflush.h ++++ b/arch/x86/include/asm/tlbflush.h +@@ -4,6 +4,7 @@ + + #include + #include ++#include + #include + + #include +@@ -211,6 +212,12 @@ extern u16 invlpgb_count_max; + + extern void initialize_tlbstate_and_flush(void); + ++/* ++ * Keep stack-allocated flush_tlb_info cacheline aligned, but cap the ++ * alignment to avoid excessive stack usage on large-cacheline systems. ++ */ ++#define FLUSH_TLB_INFO_ALIGN MIN(SMP_CACHE_BYTES, 64) ++ + /* + * TLB flushing: + * +@@ -249,7 +256,7 @@ struct flush_tlb_info { + u8 stride_shift; + u8 freed_tables; + u8 trim_cpumask; +-}; ++} __aligned(FLUSH_TLB_INFO_ALIGN); + + void flush_tlb_local(void); + void flush_tlb_one_user(unsigned long addr); +diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c +--- a/arch/x86/kernel/kvm.c ++++ b/arch/x86/kernel/kvm.c +@@ -663,8 +663,10 @@ static void kvm_flush_tlb_multi(const struct cpumask *cpumask, + u8 state; + int cpu; + struct kvm_steal_time *src; +- struct cpumask *flushmask = this_cpu_cpumask_var_ptr(__pv_cpu_mask); ++ struct cpumask *flushmask; + ++ guard(preempt)(); ++ flushmask = this_cpu_cpumask_var_ptr(__pv_cpu_mask); + cpumask_copy(flushmask, cpumask); + /* + * We have to call flush only on online vCPUs. And +diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c +--- a/arch/x86/mm/fault.c ++++ b/arch/x86/mm/fault.c +@@ -275,17 +275,17 @@ void arch_sync_kernel_mappings(unsigned long start, unsigned long end) + for (addr = start & PMD_MASK; + addr >= TASK_SIZE_MAX && addr < VMALLOC_END; + addr += PMD_SIZE) { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + spinlock_t *pgt_lock; + + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + + spin_lock(pgt_lock); +- vmalloc_sync_one(page_address(page), addr); ++ vmalloc_sync_one(ptdesc_address(ptdesc), addr); + spin_unlock(pgt_lock); + } + spin_unlock(&pgd_lock); +diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c +--- a/arch/x86/mm/init_64.c ++++ b/arch/x86/mm/init_64.c +@@ -136,7 +136,7 @@ static void sync_global_pgds_l5(unsigned long start, unsigned long end) + + for (addr = start; addr <= end; addr = ALIGN(addr + 1, PGDIR_SIZE)) { + const pgd_t *pgd_ref = pgd_offset_k(addr); +- struct page *page; ++ struct ptdesc *ptdesc; + + /* Check for overflow */ + if (addr < start) +@@ -146,13 +146,13 @@ static void sync_global_pgds_l5(unsigned long start, unsigned long end) + continue; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + spinlock_t *pgt_lock; + +- pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + spin_lock(pgt_lock); + + if (!pgd_none(*pgd_ref) && !pgd_none(*pgd)) +@@ -174,7 +174,7 @@ static void sync_global_pgds_l4(unsigned long start, unsigned long end) + for (addr = start; addr <= end; addr = ALIGN(addr + 1, PGDIR_SIZE)) { + pgd_t *pgd_ref = pgd_offset_k(addr); + const p4d_t *p4d_ref; +- struct page *page; ++ struct ptdesc *ptdesc; + + /* + * With folded p4d, pgd_none() is always false, we need to +@@ -187,15 +187,15 @@ static void sync_global_pgds_l4(unsigned long start, unsigned long end) + continue; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + p4d_t *p4d; + spinlock_t *pgt_lock; + +- pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + p4d = p4d_offset(pgd, addr); + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + spin_lock(pgt_lock); + + if (!p4d_none(*p4d_ref) && !p4d_none(*p4d)) +diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c +--- a/arch/x86/mm/pat/set_memory.c ++++ b/arch/x86/mm/pat/set_memory.c +@@ -88,9 +88,8 @@ static unsigned long direct_pages_count[PG_LEVEL_NUM]; + void update_page_count(int level, unsigned long pages) + { + /* Protect against CPA */ +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + direct_pages_count[level] += pages; +- spin_unlock(&pgd_lock); + } + + static void split_page_count(int level) +@@ -420,7 +419,7 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + int collapsed = 0; + int i; + +- spin_lock(&cpa_lock); ++ guard(spinlock)(&cpa_lock); + + if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) { + for (i = 0; i < cpa->numpages; i++) +@@ -435,10 +434,8 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + collapsed += collapse_large_pages(addr, &pgtables); + } + +- if (!collapsed) { +- spin_unlock(&cpa_lock); ++ if (!collapsed) + return; +- } + + flush_tlb_all(); + +@@ -454,8 +451,6 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + else + pagetable_free(ptdesc); + } +- +- spin_unlock(&cpa_lock); + } + + static void cpa_collapse_large_pages(struct cpa_data *cpa) +@@ -916,24 +911,23 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) + { + /* change init_mm */ + set_pte_atomic(kpte, pte); +-#ifdef CONFIG_X86_32 +- { +- struct page *page; + +- list_for_each_entry(page, &pgd_list, lru) { ++ if (IS_ENABLED(CONFIG_X86_32)) { ++ struct ptdesc *ptdesc; ++ ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; + pmd_t *pmd; + +- pgd = (pgd_t *)page_address(page) + pgd_index(address); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(address); + p4d = p4d_offset(pgd, address); + pud = pud_offset(p4d, address); + pmd = pmd_offset(pud, address); + set_pte_atomic((pte_t *)pmd, pte); + } + } +-#endif + } + + static pgprot_t pgprot_clear_protnone_bits(pgprot_t prot) +@@ -1103,16 +1097,11 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address, + static int should_split_large_page(pte_t *kpte, unsigned long address, + struct cpa_data *cpa) + { +- int do_split; +- + if (cpa->force_split) + return 1; + +- spin_lock(&pgd_lock); +- do_split = __should_split_large_page(kpte, address, cpa); +- spin_unlock(&pgd_lock); +- +- return do_split; ++ guard(spinlock)(&pgd_lock); ++ return __should_split_large_page(kpte, address, cpa); + } + + static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn, +@@ -1162,16 +1151,14 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + bool nx, rw; + pte_t *tmp; + +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + /* + * Check for races, another CPU might have split this page + * up for us already: + */ + tmp = _lookup_address_cpa(cpa, address, &level, &nx, &rw); +- if (tmp != kpte) { +- spin_unlock(&pgd_lock); ++ if (tmp != kpte) + return 1; +- } + + paravirt_alloc_pte(&init_mm, page_to_pfn(base)); + +@@ -1204,7 +1191,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + break; + + default: +- spin_unlock(&pgd_lock); + return 1; + } + +@@ -1252,7 +1238,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + * just split large page entry. + */ + flush_tlb_all(); +- spin_unlock(&pgd_lock); + + return 0; + } +@@ -1327,11 +1312,11 @@ static int collapse_pmd_page(pmd_t *pmd, unsigned long addr, + list_add(&page_ptdesc(pmd_page(old_pmd))->pt_list, pgtables); + + if (IS_ENABLED(CONFIG_X86_32)) { +- struct page *page; ++ struct ptdesc *ptdesc; + + /* Update all PGD tables to use the same large page */ +- list_for_each_entry(page, &pgd_list, lru) { +- pgd_t *pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ pgd_t *pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + p4d_t *p4d = p4d_offset(pgd, addr); + pud_t *pud = pud_offset(p4d, addr); + pmd_t *pmd = pmd_offset(pud, addr); +@@ -1405,7 +1390,7 @@ static int collapse_pud_page(pud_t *pud, unsigned long addr, + */ + static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) + { +- int collapsed = 0; ++ int collapsed; + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; +@@ -1413,26 +1398,24 @@ static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) + + addr &= PMD_MASK; + +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + pgd = pgd_offset_k(addr); + if (pgd_none(*pgd)) +- goto out; ++ return 0; + p4d = p4d_offset(pgd, addr); + if (p4d_none(*p4d)) +- goto out; ++ return 0; + pud = pud_offset(p4d, addr); + if (!pud_present(*pud) || pud_leaf(*pud)) +- goto out; ++ return 0; + pmd = pmd_offset(pud, addr); + if (!pmd_present(*pmd) || pmd_leaf(*pmd)) +- goto out; ++ return 0; + + collapsed = collapse_pmd_page(pmd, addr, pgtables); + if (collapsed) + collapsed += collapse_pud_page(pud, addr, pgtables); + +-out: +- spin_unlock(&pgd_lock); + return collapsed; + } + +diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c +--- a/arch/x86/mm/pgtable.c ++++ b/arch/x86/mm/pgtable.c +@@ -74,9 +74,9 @@ static void pgd_set_mm(pgd_t *pgd, struct mm_struct *mm) + virt_to_ptdesc(pgd)->pt_mm = mm; + } + +-struct mm_struct *pgd_page_get_mm(struct page *page) ++struct mm_struct *pgd_page_get_mm(struct ptdesc *pt) + { +- return page_ptdesc(page)->pt_mm; ++ return pt->pt_mm; + } + + static void pgd_ctor(struct mm_struct *mm, pgd_t *pgd) +diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c +--- a/arch/x86/mm/tlb.c ++++ b/arch/x86/mm/tlb.c +@@ -1373,35 +1373,19 @@ void flush_tlb_multi(const struct cpumask *cpumask, + */ + unsigned long tlb_single_page_flush_ceiling __read_mostly = 33; + +-static DEFINE_PER_CPU_SHARED_ALIGNED(struct flush_tlb_info, flush_tlb_info); +- +-#ifdef CONFIG_DEBUG_VM +-static DEFINE_PER_CPU(unsigned int, flush_tlb_info_idx); +-#endif +- +-static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, +- unsigned long start, unsigned long end, +- unsigned int stride_shift, bool freed_tables, +- u64 new_tlb_gen) ++static void init_flush_tlb_info(struct flush_tlb_info *info, ++ struct mm_struct *mm, ++ unsigned long start, unsigned long end, ++ unsigned int stride_shift, bool freed_tables, ++ u64 new_tlb_gen) + { +- struct flush_tlb_info *info = this_cpu_ptr(&flush_tlb_info); +- +-#ifdef CONFIG_DEBUG_VM +- /* +- * Ensure that the following code is non-reentrant and flush_tlb_info +- * is not overwritten. This means no TLB flushing is initiated by +- * interrupt handlers and machine-check exception handlers. +- */ +- BUG_ON(this_cpu_inc_return(flush_tlb_info_idx) != 1); +-#endif +- + /* + * If the number of flushes is so large that a full flush + * would be faster, do a full flush. + */ + if ((end - start) >> stride_shift > tlb_single_page_flush_ceiling) { +- start = 0; +- end = TLB_FLUSH_ALL; ++ start = 0; ++ end = TLB_FLUSH_ALL; + } + + info->start = start; +@@ -1412,32 +1396,21 @@ static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, + info->new_tlb_gen = new_tlb_gen; + info->initiating_cpu = smp_processor_id(); + info->trim_cpumask = 0; +- +- return info; +-} +- +-static void put_flush_tlb_info(void) +-{ +-#ifdef CONFIG_DEBUG_VM +- /* Complete reentrancy prevention checks */ +- barrier(); +- this_cpu_dec(flush_tlb_info_idx); +-#endif + } + + void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, + unsigned long end, unsigned int stride_shift, + bool freed_tables) + { +- struct flush_tlb_info *info; ++ struct flush_tlb_info info; ++ bool remote_flush = false; + int cpu = get_cpu(); + u64 new_tlb_gen; + + /* This is also a barrier that synchronizes with switch_mm(). */ + new_tlb_gen = inc_mm_tlb_gen(mm); + +- info = get_flush_tlb_info(mm, start, end, stride_shift, freed_tables, +- new_tlb_gen); ++ init_flush_tlb_info(&info, mm, start, end, stride_shift, freed_tables, new_tlb_gen); + + /* + * flush_tlb_multi() is not optimized for the common case in which only +@@ -1445,20 +1418,24 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, + * flush_tlb_func_local() directly in this case. + */ + if (mm_global_asid(mm)) { +- broadcast_tlb_flush(info); ++ broadcast_tlb_flush(&info); + } else if (cpumask_any_but(mm_cpumask(mm), cpu) < nr_cpu_ids) { +- info->trim_cpumask = should_trim_cpumask(mm); +- flush_tlb_multi(mm_cpumask(mm), info); +- consider_global_asid(mm); ++ remote_flush = true; + } else if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) { + lockdep_assert_irqs_enabled(); + local_irq_disable(); +- flush_tlb_func(info); ++ flush_tlb_func(&info); + local_irq_enable(); + } + +- put_flush_tlb_info(); + put_cpu(); ++ ++ if (remote_flush) { ++ info.trim_cpumask = should_trim_cpumask(mm); ++ flush_tlb_multi(mm_cpumask(mm), &info); ++ consider_global_asid(mm); ++ } ++ + mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, end); + } + +@@ -1527,19 +1504,16 @@ static void kernel_tlb_flush_range(struct flush_tlb_info *info) + + void flush_tlb_kernel_range(unsigned long start, unsigned long end) + { +- struct flush_tlb_info *info; ++ struct flush_tlb_info info; + + guard(preempt)(); ++ init_flush_tlb_info(&info, NULL, start, end, PAGE_SHIFT, false, ++ TLB_GENERATION_INVALID); + +- info = get_flush_tlb_info(NULL, start, end, PAGE_SHIFT, false, +- TLB_GENERATION_INVALID); +- +- if (info->end == TLB_FLUSH_ALL) +- kernel_tlb_flush_all(info); ++ if (info.end == TLB_FLUSH_ALL) ++ kernel_tlb_flush_all(&info); + else +- kernel_tlb_flush_range(info); +- +- put_flush_tlb_info(); ++ kernel_tlb_flush_range(&info); + } + + /* +@@ -1707,12 +1681,12 @@ EXPORT_SYMBOL_FOR_KVM(__flush_tlb_all); + + void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) + { +- struct flush_tlb_info *info; +- ++ struct flush_tlb_info info; ++ bool remote_flush = false; + int cpu = get_cpu(); + +- info = get_flush_tlb_info(NULL, 0, TLB_FLUSH_ALL, 0, false, +- TLB_GENERATION_INVALID); ++ init_flush_tlb_info(&info, NULL, 0, TLB_FLUSH_ALL, 0, false, ++ TLB_GENERATION_INVALID); + /* + * flush_tlb_multi() is not optimized for the common case in which only + * a local TLB flush is needed. Optimize this use-case by calling +@@ -1722,18 +1696,20 @@ void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) + invlpgb_flush_all_nonglobals(); + batch->unmapped_pages = false; + } else if (cpumask_any_but(&batch->cpumask, cpu) < nr_cpu_ids) { +- flush_tlb_multi(&batch->cpumask, info); ++ remote_flush = true; + } else if (cpumask_test_cpu(cpu, &batch->cpumask)) { + lockdep_assert_irqs_enabled(); + local_irq_disable(); +- flush_tlb_func(info); ++ flush_tlb_func(&info); + local_irq_enable(); + } + +- cpumask_clear(&batch->cpumask); +- +- put_flush_tlb_info(); + put_cpu(); ++ ++ if (remote_flush) ++ flush_tlb_multi(&batch->cpumask, &info); ++ ++ cpumask_clear(&batch->cpumask); + } + + /* +diff --git a/arch/x86/xen/mmu_pv.c b/arch/x86/xen/mmu_pv.c +--- a/arch/x86/xen/mmu_pv.c ++++ b/arch/x86/xen/mmu_pv.c +@@ -836,15 +836,15 @@ static void xen_pgd_pin(struct mm_struct *mm) + */ + void xen_mm_pin_all(void) + { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&init_mm.page_table_lock); + spin_lock(&pgd_lock); + +- list_for_each_entry(page, &pgd_list, lru) { +- if (!PagePinned(page)) { +- __xen_pgd_pin(&init_mm, (pgd_t *)page_address(page)); +- SetPageSavePinned(page); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ if (!PagePinned(ptdesc_page(ptdesc))) { ++ __xen_pgd_pin(&init_mm, (pgd_t *)ptdesc_address(ptdesc)); ++ SetPageSavePinned(ptdesc_page(ptdesc)); + } + } + +@@ -947,16 +947,16 @@ static void xen_pgd_unpin(struct mm_struct *mm) + */ + void xen_mm_unpin_all(void) + { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&init_mm.page_table_lock); + spin_lock(&pgd_lock); + +- list_for_each_entry(page, &pgd_list, lru) { +- if (PageSavePinned(page)) { +- BUG_ON(!PagePinned(page)); +- __xen_pgd_unpin(&init_mm, (pgd_t *)page_address(page)); +- ClearPageSavePinned(page); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ if (PageSavePinned(ptdesc_page(ptdesc))) { ++ BUG_ON(!PagePinned(ptdesc_page(ptdesc))); ++ __xen_pgd_unpin(&init_mm, (pgd_t *)ptdesc_address(ptdesc)); ++ ClearPageSavePinned(ptdesc_page(ptdesc)); + } + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch.sig b/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..3f44a634b20cb13bc095654d733ceebb64068ad2 GIT binary patch literal 594 zcmV-Y0s=j00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=TILH|94hTyLpAzSRHK>L+OE^ zakS#b$b&nZb17IiKW$mH&COkTunI3a?soHnnACJJn(8hHie!vhw6N@9wjhVIOSt?MA0peGDv#GIF*yxf%r^%(o8WR6i=rqyd#SjcB|k6_Isx+#O~K+ z5^lVe1Lp9vgbt?06YoCbVd&6U zjZZ;l`t7qx>^Ykj&k50rnAx3)t{e+`_sX4R54`x1VT&|rrGa%5ntS%SAFZm%G%2>C zz^48b+%x&N8<9#p;(EaopFHlxQs|`e z*KDl+Uduk8ofAhzv{5y9ubYb5V3)aEqWot=?qtV6r!il}ahkZx!l^Z9 geJQWNh2&-&x*&cZfjo?EEQI4Epvj5uVOZscfs_prim == SCF_PRIM_SINGLE || ++ scfsp->scfs_prim == SCF_PRIM_SINGLE_RPC); + + if (scfsp->scfs_prim == SCF_PRIM_SINGLE || scfsp->scfs_wait) { + scfcp = kmalloc_obj(*scfcp, GFP_ATOMIC); +@@ -364,8 +366,6 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + } + if (use_cpus_read_lock) + cpus_read_lock(); +- else +- preempt_disable(); + switch (scfsp->scfs_prim) { + case SCF_PRIM_RESCHED: + if (IS_BUILTIN(CONFIG_SCF_TORTURE_TEST)) { +@@ -411,13 +411,10 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + if (!ret) { + if (use_cpus_read_lock) + cpus_read_unlock(); +- else +- preempt_enable(); ++ + wait_for_completion(&scfcp->scfc_completion); + if (use_cpus_read_lock) + cpus_read_lock(); +- else +- preempt_disable(); + } else { + scfp->n_single_rpc_ofl++; + scf_add_to_free_list(scfcp); +@@ -452,7 +449,7 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + scfcp->scfc_out = true; + } + if (scfcp && scfsp->scfs_wait) { +- if (WARN_ON_ONCE((num_online_cpus() > 1 || scfsp->scfs_prim == SCF_PRIM_SINGLE) && ++ if (WARN_ON_ONCE(((use_cpus_read_lock && num_online_cpus() > 1) || is_single) && + !scfcp->scfc_out)) { + pr_warn("%s: Memory-ordering failure, scfs_prim: %d.\n", __func__, scfsp->scfs_prim); + atomic_inc(&n_mb_out_errs); // Leak rather than trash! +@@ -463,8 +460,6 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + } + if (use_cpus_read_lock) + cpus_read_unlock(); +- else +- preempt_enable(); + if (allocfail) + schedule_timeout_idle((1 + longwait) * HZ); // Let no-wait handlers complete. + else if (!(torture_random(trsp) & 0xfff)) +diff --git a/kernel/smp.c b/kernel/smp.c +--- a/kernel/smp.c ++++ b/kernel/smp.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -63,7 +64,14 @@ int smpcfd_prepare_cpu(unsigned int cpu) + free_cpumask_var(cfd->cpumask); + return -ENOMEM; + } +- cfd->csd = alloc_percpu(call_single_data_t); ++ ++ /* ++ * Allocate the per-CPU CSD the first time a CPU comes up. It is ++ * not freed when the CPU is offlined, so csd_lock_wait() can access ++ * it even when the CPU was offlined after preemption was re-enabled. ++ */ ++ if (!cfd->csd) ++ cfd->csd = alloc_percpu(call_single_data_t); + if (!cfd->csd) { + free_cpumask_var(cfd->cpumask); + free_cpumask_var(cfd->cpumask_ipi); +@@ -79,7 +87,6 @@ int smpcfd_dead_cpu(unsigned int cpu) + + free_cpumask_var(cfd->cpumask); + free_cpumask_var(cfd->cpumask_ipi); +- free_percpu(cfd->csd); + return 0; + } + +@@ -323,6 +330,8 @@ static void __csd_lock_wait(call_single_data_t *csd) + int bug_id = 0; + u64 ts0, ts1; + ++ guard(preempt)(); ++ + ts1 = ts0 = ktime_get_mono_fast_ns(); + for (;;) { + if (csd_lock_wait_toolong(csd, ts0, &ts1, &bug_id, &nmessages)) +@@ -659,17 +668,9 @@ void flush_smp_call_function_queue(void) + local_irq_restore(flags); + } + +-/** +- * smp_call_function_single - Run a function on a specific CPU +- * @cpu: Specific target CPU for this function. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait until function has completed on other CPUs. +- * +- * Returns: %0 on success, else a negative status code. +- */ +-int smp_call_function_single(int cpu, smp_call_func_t func, void *info, +- int wait) ++static int __smp_call_function_single(int cpu, smp_call_func_t func, ++ void *info, const struct cpumask *mask, ++ bool wait) + { + call_single_data_t *csd; + call_single_data_t csd_stack = { +@@ -686,6 +687,14 @@ int smp_call_function_single(int cpu, smp_call_func_t func, void *info, + */ + this_cpu = get_cpu(); + ++ if (mask) { ++ /* Try for same CPU (cheapest) */ ++ if (!cpumask_test_cpu(this_cpu, mask)) ++ cpu = sched_numa_find_nth_cpu(mask, 0, cpu_to_node(this_cpu)); ++ else ++ cpu = this_cpu; ++ } ++ + /* + * Can deadlock when called with interrupts disabled. + * We allow cpu's that are not yet online though, as no one else can +@@ -718,13 +727,32 @@ int smp_call_function_single(int cpu, smp_call_func_t func, void *info, + + err = generic_exec_single(cpu, csd); + ++ /* ++ * @csd is stack-allocated when @wait is true. No concurrent access ++ * except from the IPI completion path, so we can re-enable preemption ++ * early to reduce latency. ++ */ ++ put_cpu(); ++ + if (wait) + csd_lock_wait(csd); + +- put_cpu(); +- + return err; + } ++ ++/** ++ * smp_call_function_single - Run a function on a specific CPU ++ * @cpu: Specific target CPU for this function. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait until function has completed on other CPUs. ++ * ++ * Returns: %0 on success, else a negative status code. ++ */ ++int smp_call_function_single(int cpu, smp_call_func_t func, void *info, bool wait) ++{ ++ return __smp_call_function_single(cpu, func, info, NULL, wait); ++} + EXPORT_SYMBOL(smp_call_function_single); + + /** +@@ -775,10 +803,10 @@ EXPORT_SYMBOL_GPL(smp_call_function_single_async); + + /** + * smp_call_function_any - Run a function on any of the given cpus +- * @mask: The mask of cpus it can run on. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait until function has completed. ++ * @mask: The mask of cpus it can run on. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait until function has completed. + * + * Selection preference: + * 1) current cpu if in @mask +@@ -789,20 +817,54 @@ EXPORT_SYMBOL_GPL(smp_call_function_single_async); + int smp_call_function_any(const struct cpumask *mask, + smp_call_func_t func, void *info, int wait) + { +- unsigned int cpu; +- int ret; +- +- /* Try for same CPU (cheapest) */ +- cpu = get_cpu(); +- if (!cpumask_test_cpu(cpu, mask)) +- cpu = sched_numa_find_nth_cpu(mask, 0, cpu_to_node(cpu)); +- +- ret = smp_call_function_single(cpu, func, info, wait); +- put_cpu(); +- return ret; ++ return __smp_call_function_single(-1, func, info, mask, wait); + } + EXPORT_SYMBOL_GPL(smp_call_function_any); + ++static DEFINE_STATIC_KEY_FALSE(ipi_mask_inlined); ++ ++#ifdef CONFIG_PREEMPTION ++ ++int smp_task_ipi_mask_alloc(struct task_struct *task) ++{ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return 0; ++ ++ ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr = ++ kmalloc(cpumask_size(), GFP_KERNEL); ++ if (!ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++void smp_task_ipi_mask_free(struct task_struct *task) ++{ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return; ++ ++ kfree(ACCESS_PRIVATE(task, ipi_mask).ipi_mask_ptr); ++} ++ ++static cpumask_t *smp_task_ipi_mask(struct task_struct *cur) ++{ ++ /* ++ * If cpumask_size() is smaller than or equal to the pointer ++ * size, it stashes the cpumask in the pointer itself to ++ * avoid extra memory allocations. ++ */ ++ if (static_branch_unlikely(&ipi_mask_inlined)) ++ return (cpumask_t *)&ACCESS_PRIVATE(cur, ipi_mask).ipi_mask_val; ++ ++ return ACCESS_PRIVATE(cur, ipi_mask).ipi_mask_ptr; ++} ++#else ++static cpumask_t *smp_task_ipi_mask(struct task_struct *cur) ++{ ++ return NULL; ++} ++#endif ++ + /* + * Flags to be used as scf_flags argument of smp_call_function_many_cond(). + * +@@ -817,13 +879,20 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + unsigned int scf_flags, + smp_cond_func_t cond_func) + { +- int cpu, last_cpu, this_cpu = smp_processor_id(); +- struct call_function_data *cfd; ++ struct cpumask *cpumask, *task_mask; + bool wait = scf_flags & SCF_WAIT; +- int nr_cpus = 0; ++ struct call_function_data *cfd; ++ int cpu, last_cpu, this_cpu; + bool run_remote = false; ++ int nr_cpus = 0; + +- lockdep_assert_preemption_disabled(); ++ this_cpu = get_cpu(); ++ cfd = this_cpu_ptr(&cfd_data); ++ task_mask = smp_task_ipi_mask(current); ++ if (task_mask) ++ cpumask = task_mask; ++ else ++ cpumask = cfd->cpumask; + + /* + * Can deadlock when called with interrupts disabled. +@@ -845,16 +914,15 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + + /* Check if we need remote execution, i.e., any CPU excluding this one. */ + if (cpumask_any_and_but(mask, cpu_online_mask, this_cpu) < nr_cpu_ids) { +- cfd = this_cpu_ptr(&cfd_data); +- cpumask_and(cfd->cpumask, mask, cpu_online_mask); +- __cpumask_clear_cpu(this_cpu, cfd->cpumask); ++ cpumask_and(cpumask, mask, cpu_online_mask); ++ __cpumask_clear_cpu(this_cpu, cpumask); + + cpumask_clear(cfd->cpumask_ipi); +- for_each_cpu(cpu, cfd->cpumask) { ++ for_each_cpu(cpu, cpumask) { + call_single_data_t *csd = per_cpu_ptr(cfd->csd, cpu); + + if (cond_func && !cond_func(cpu, info)) { +- __cpumask_clear_cpu(cpu, cfd->cpumask); ++ __cpumask_clear_cpu(cpu, cpumask); + continue; + } + +@@ -904,8 +972,18 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + local_irq_restore(flags); + } + ++ /* ++ * The IPI work has been queued and dispatched. On PREEMPT kernels, ++ * tasks created through dup_task_struct() have task-local wait masks. ++ * The boot init_task can fall back to cfd->cpumask when the mask is ++ * not inlined, but other tasks still use task-local masks and cannot ++ * overwrite it. On !PREEMPT kernels, preempt_enable() cannot schedule ++ * another task, so the per-CPU mask remains protected. ++ */ ++ put_cpu(); ++ + if (run_remote && wait) { +- for_each_cpu(cpu, cfd->cpumask) { ++ for_each_cpu(cpu, cpumask) { + call_single_data_t *csd; + + csd = per_cpu_ptr(cfd->csd, cpu); +@@ -916,15 +994,14 @@ static void smp_call_function_many_cond(const struct cpumask *mask, + + /** + * smp_call_function_many() - Run a function on a set of CPUs. +- * @mask: The set of cpus to run on (only runs on online subset). +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait (atomically) until function has completed +- * on other CPUs. ++ * @mask: The set of cpus to run on (only runs on online subset). ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait (atomically) until function has completed ++ * on other CPUs. + * + * You must not call this function with disabled interrupts or from a +- * hardware interrupt handler or from a bottom half handler. Preemption +- * must be disabled when calling this function. ++ * hardware interrupt handler or from a bottom half handler. + * + * @func is not called on the local CPU even if @mask contains it. Consider + * using on_each_cpu_cond_mask() instead if this is not desirable. +@@ -938,10 +1015,10 @@ EXPORT_SYMBOL(smp_call_function_many); + + /** + * smp_call_function() - Run a function on all other CPUs. +- * @func: The function to run. This must be fast and non-blocking. +- * @info: An arbitrary pointer to pass to the function. +- * @wait: If true, wait (atomically) until function has completed +- * on other CPUs. ++ * @func: The function to run. This must be fast and non-blocking. ++ * @info: An arbitrary pointer to pass to the function. ++ * @wait: If true, wait (atomically) until function has completed ++ * on other CPUs. + * + * If @wait is true, then returns once @func has returned; otherwise + * it returns just before the target cpu calls @func. +@@ -951,9 +1028,8 @@ EXPORT_SYMBOL(smp_call_function_many); + */ + void smp_call_function(smp_call_func_t func, void *info, int wait) + { +- preempt_disable(); +- smp_call_function_many(cpu_online_mask, func, info, wait); +- preempt_enable(); ++ smp_call_function_many_cond(cpu_online_mask, func, info, ++ wait ? SCF_WAIT : 0, NULL); + } + EXPORT_SYMBOL(smp_call_function); + +@@ -1019,6 +1095,9 @@ EXPORT_SYMBOL(nr_cpu_ids); + void __init setup_nr_cpu_ids(void) + { + set_nr_cpu_ids(find_last_bit(cpumask_bits(cpu_possible_mask), NR_CPUS) + 1); ++ ++ if (IS_ENABLED(CONFIG_PREEMPTION) && cpumask_size() <= sizeof(unsigned long)) ++ static_branch_enable(&ipi_mask_inlined); + } + + /* Called by boot processor to activate the rest. */ +@@ -1055,12 +1134,14 @@ void __init smp_init(void) + * @func: The function to run on all applicable CPUs. + * This must be fast and non-blocking. + * @info: An arbitrary pointer to pass to both functions. +- * @wait: If true, wait (atomically) until function has +- * completed on other CPUs. ++ * @wait: If true, wait until function has completed on other CPUs. + * @mask: The set of cpus to run on (only runs on online subset). + * +- * Preemption is disabled to protect against CPUs going offline but not online. +- * CPUs going online during the call will not be seen or sent an IPI. ++ * Target CPU selection and work queueing are done with preemption ++ * disabled. This protects against CPUs going offline, but not against ++ * CPUs coming online concurrently; newly online CPUs are not guaranteed ++ * to be seen or sent an IPI. If @wait is true, the final wait for remote ++ * completion happens after that preemption-disabled section. + * + * You must not call this function with disabled interrupts or + * from a hardware interrupt handler or from a bottom half handler. +@@ -1073,9 +1154,7 @@ void on_each_cpu_cond_mask(smp_cond_func_t cond_func, smp_call_func_t func, + if (wait) + scf_flags |= SCF_WAIT; + +- preempt_disable(); + smp_call_function_many_cond(mask, func, info, scf_flags, cond_func); +- preempt_enable(); + } + EXPORT_SYMBOL(on_each_cpu_cond_mask); + +diff --git a/kernel/up.c b/kernel/up.c +--- a/kernel/up.c ++++ b/kernel/up.c +@@ -9,8 +9,7 @@ + #include + #include + +-int smp_call_function_single(int cpu, void (*func) (void *info), void *info, +- int wait) ++int smp_call_function_single(int cpu, void (*func)(void *info), void *info, bool wait) + { + unsigned long flags; + diff --git a/pkgbuilds/linux-omarchy-muqss/0121-smp-preempt.patch.sig b/pkgbuilds/linux-omarchy-muqss/0121-smp-preempt.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f04310ecd243b21e2e70ac0f14d60304abc8ac53 GIT binary patch literal 594 zcmV-Y0SIDQc<}`Vc6sb?N@-0DY zkdWX49u`io4k3w<4oV|7Re gWWJ}PmkGmE#b?nErdx~K2PLQtu`804Xqy|BuEz5g{Qv*} literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch b/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch new file mode 100644 index 0000000..245f6e1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch @@ -0,0 +1,21 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -13350,12 +13350,15 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, + * still unbalanced. ld_moved simply stays zero, so it is + * correctly treated as an imbalance. + */ +- env.loop_max = min(sysctl_sched_nr_migrate, busiest->nr_running); +- + more_balance: + rq_lock_irqsave(busiest, &rf); + update_rq_clock(busiest); + ++ if (!env.loop_max) ++ env.loop_max = min(sysctl_sched_nr_migrate, busiest->cfs.h_nr_queued); ++ else ++ env.loop_max = min(env.loop_max, busiest->cfs.h_nr_queued); ++ + /* + * cur_ld_moved - load moved in current iteration + * ld_moved - cumulative load moved across iterations diff --git a/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch.sig b/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..b0ccd26e0b7a3cfd11b3b265676ec4445d2e58f7 GIT binary patch literal 594 zcmV-Y0|3F?WhXk?{>#e=hdNlY$ z@Bi>kHQh;i1@)x$VztW{PF2|>m4Uruj53vs{MxP(eed$uyv1~9E~JObIlN$$H0KB~ z+VB7&#MC&#M8&{6bjQp|yFFugpl&5H1#)9S;%36V2|sa@KqNa?H0jtXCYJN`WhEY* zOPlHoOR0AGRBDBL)AoVd{+2mjkJafegsEr@e!X-jfokMVj*3D^zE;l`IrA%!7D>r! zBVNA)O-Ua%XVqDgK-u-glNVXZFHKaq1d<%p=MLRuHUjL@GJHiE zsQ@*UEu5y9oye}O<$=*pQ|FI+?KS?VClREMQp2)+?+(4SODBl9ri3;;W@(yqqc90{ayJbcBeRzBrlWyicO!HHZonHUtYD=uT)WeTFUDB%e0nQ9S1PW7 zesUOBx}<;L3@FMO8*@_w43%WUQak{8>pKuKanJVd}i>fVL gQCMs%khRA2x&Yw-&^}aLf7`YAnz$){M?_C&N;u>Z=l}o! literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch b/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch new file mode 100644 index 0000000..2d03b2d --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch @@ -0,0 +1,367 @@ +diff --git a/arch/arm/include/asm/mmu_context.h b/arch/arm/include/asm/mmu_context.h +--- a/arch/arm/include/asm/mmu_context.h ++++ b/arch/arm/include/asm/mmu_context.h +@@ -80,7 +80,7 @@ static inline void check_and_switch_context(struct mm_struct *mm, + #ifndef MODULE + #define finish_arch_post_lock_switch \ + finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + struct mm_struct *mm = current->mm; + +diff --git a/arch/riscv/include/asm/sync_core.h b/arch/riscv/include/asm/sync_core.h +--- a/arch/riscv/include/asm/sync_core.h ++++ b/arch/riscv/include/asm/sync_core.h +@@ -6,7 +6,7 @@ + * RISC-V implements return to user-space through an xRET instruction, + * which is not core serializing. + */ +-static inline void sync_core_before_usermode(void) ++static __always_inline void sync_core_before_usermode(void) + { + asm volatile ("fence.i" ::: "memory"); + } +diff --git a/arch/s390/include/asm/mmu_context.h b/arch/s390/include/asm/mmu_context.h +--- a/arch/s390/include/asm/mmu_context.h ++++ b/arch/s390/include/asm/mmu_context.h +@@ -93,7 +93,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, + } + + #define finish_arch_post_lock_switch finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; +diff --git a/arch/sparc/include/asm/mmu_context_64.h b/arch/sparc/include/asm/mmu_context_64.h +--- a/arch/sparc/include/asm/mmu_context_64.h ++++ b/arch/sparc/include/asm/mmu_context_64.h +@@ -160,7 +160,7 @@ static inline void arch_start_context_switch(struct task_struct *prev) + } + + #define finish_arch_post_lock_switch finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + /* Restore the state of MCDPER register for the new process + * just switched to. +diff --git a/arch/x86/include/asm/sync_core.h b/arch/x86/include/asm/sync_core.h +--- a/arch/x86/include/asm/sync_core.h ++++ b/arch/x86/include/asm/sync_core.h +@@ -93,7 +93,7 @@ static __always_inline void sync_core(void) + * to user-mode. x86 implements return to user-space through sysexit, + * sysrel, and sysretq, which are not core serializing. + */ +-static inline void sync_core_before_usermode(void) ++static __always_inline void sync_core_before_usermode(void) + { + /* With PTI, we unconditionally serialize before running user code. */ + if (static_cpu_has(X86_FEATURE_PTI)) +diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h +--- a/include/linux/perf_event.h ++++ b/include/linux/perf_event.h +@@ -1632,7 +1632,7 @@ static inline void perf_event_task_migrate(struct task_struct *task) + task->sched_migrated = 1; + } + +-static inline void perf_event_task_sched_in(struct task_struct *prev, ++static __always_inline void perf_event_task_sched_in(struct task_struct *prev, + struct task_struct *task) + { + if (static_branch_unlikely(&perf_sched_events)) +diff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h +--- a/include/linux/sched/mm.h ++++ b/include/linux/sched/mm.h +@@ -32,7 +32,7 @@ extern struct mm_struct *mm_alloc(void); + * See also for an in-depth explanation + * of &mm_struct.mm_count vs &mm_struct.mm_users. + */ +-static inline void mmgrab(struct mm_struct *mm) ++static __always_inline void mmgrab(struct mm_struct *mm) + { + atomic_inc(&mm->mm_count); + } +@@ -44,7 +44,7 @@ static inline void smp_mb__after_mmgrab(void) + + extern void __mmdrop(struct mm_struct *mm); + +-static inline void mmdrop(struct mm_struct *mm) ++static __always_inline void mmdrop(struct mm_struct *mm) + { + /* + * The implicit full barrier implied by atomic_dec_and_test() is +@@ -71,27 +71,27 @@ static inline void __mmdrop_delayed(struct rcu_head *rhp) + * Invoked from finish_task_switch(). Delegates the heavy lifting on RT + * kernels via RCU. + */ +-static inline void mmdrop_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_sched(struct mm_struct *mm) + { + /* Provides a full memory barrier. See mmdrop() */ + if (atomic_dec_and_test(&mm->mm_count)) + call_rcu(&mm->delayed_drop, __mmdrop_delayed); + } + #else +-static inline void mmdrop_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_sched(struct mm_struct *mm) + { + mmdrop(mm); + } + #endif + + /* Helpers for lazy TLB mm refcounting */ +-static inline void mmgrab_lazy_tlb(struct mm_struct *mm) ++static __always_inline void mmgrab_lazy_tlb(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) + mmgrab(mm); + } + +-static inline void mmdrop_lazy_tlb(struct mm_struct *mm) ++static __always_inline void mmdrop_lazy_tlb(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) { + mmdrop(mm); +@@ -104,7 +104,7 @@ static inline void mmdrop_lazy_tlb(struct mm_struct *mm) + } + } + +-static inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) + mmdrop_sched(mm); +@@ -128,12 +128,12 @@ static inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) + * See also for an in-depth explanation + * of &mm_struct.mm_count vs &mm_struct.mm_users. + */ +-static inline void mmget(struct mm_struct *mm) ++static __always_inline void mmget(struct mm_struct *mm) + { + atomic_inc(&mm->mm_users); + } + +-static inline bool mmget_not_zero(struct mm_struct *mm) ++static __always_inline bool mmget_not_zero(struct mm_struct *mm) + { + return atomic_inc_not_zero(&mm->mm_users); + } +@@ -532,7 +532,7 @@ enum { + #include + #endif + +-static inline void membarrier_mm_sync_core_before_usermode(struct mm_struct *mm) ++static __always_inline void membarrier_mm_sync_core_before_usermode(struct mm_struct *mm) + { + /* + * The atomic_read() below prevents CSE. The following should +diff --git a/include/linux/tick.h b/include/linux/tick.h +--- a/include/linux/tick.h ++++ b/include/linux/tick.h +@@ -175,7 +175,7 @@ extern cpumask_var_t tick_nohz_full_mask; + #ifdef CONFIG_NO_HZ_FULL + extern bool tick_nohz_full_running; + +-static inline bool tick_nohz_full_enabled(void) ++static __always_inline bool tick_nohz_full_enabled(void) + { + if (!context_tracking_enabled()) + return false; +@@ -299,7 +299,7 @@ static inline void __tick_nohz_task_switch(void) { } + static inline void tick_nohz_full_setup(cpumask_var_t cpumask) { } + #endif + +-static inline void tick_nohz_task_switch(void) ++static __always_inline void tick_nohz_task_switch(void) + { + if (tick_nohz_full_enabled()) + __tick_nohz_task_switch(); +diff --git a/include/linux/vtime.h b/include/linux/vtime.h +--- a/include/linux/vtime.h ++++ b/include/linux/vtime.h +@@ -89,24 +89,24 @@ static __always_inline void vtime_account_guest_exit(void) + * For now vtime state is tied to context tracking. We might want to decouple + * those later if necessary. + */ +-static inline bool vtime_accounting_enabled(void) ++static __always_inline bool vtime_accounting_enabled(void) + { + return context_tracking_enabled(); + } + +-static inline bool vtime_accounting_enabled_cpu(int cpu) ++static __always_inline bool vtime_accounting_enabled_cpu(int cpu) + { + return vtime_generic_enabled_cpu(cpu); + } + +-static inline bool vtime_accounting_enabled_this_cpu(void) ++static __always_inline bool vtime_accounting_enabled_this_cpu(void) + { + return vtime_generic_enabled_this_cpu(); + } + + extern void vtime_task_switch_generic(struct task_struct *prev); + +-static inline void vtime_task_switch(struct task_struct *prev) ++static __always_inline void vtime_task_switch(struct task_struct *prev) + { + if (vtime_accounting_enabled_this_cpu()) + vtime_task_switch_generic(prev); +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -5098,7 +5098,7 @@ static inline void prepare_task(struct task_struct *next) + WRITE_ONCE(next->on_cpu, 1); + } + +-static inline void finish_task(struct task_struct *prev) ++static __always_inline void finish_task(struct task_struct *prev) + { + /* + * This must be the very last reference to @prev from this CPU. After +@@ -5142,7 +5142,7 @@ static void zap_balance_callbacks(struct rq *rq) + rq->balance_callback = found ? &balance_push_callback : NULL; + } + +-static void do_balance_callbacks(struct rq *rq, struct balance_callback *head) ++static __always_inline void do_balance_callbacks(struct rq *rq, struct balance_callback *head) + { + void (*func)(struct rq *rq); + struct balance_callback *next; +@@ -5177,7 +5177,7 @@ struct balance_callback balance_push_callback = { + .func = balance_push, + }; + +-static inline struct balance_callback * ++static __always_inline struct balance_callback * + __splice_balance_callbacks(struct rq *rq, bool split) + { + struct balance_callback *head = rq->balance_callback; +@@ -5251,7 +5251,7 @@ prepare_lock_switch(struct rq *rq, struct task_struct *next, struct rq_flags *rf + __acquire(__rq_lockp(this_rq())); + } + +-static inline void finish_lock_switch(struct rq *rq) ++static __always_inline void finish_lock_switch(struct rq *rq) + __releases(__rq_lockp(rq)) + { + /* +@@ -5285,7 +5285,7 @@ static inline void kmap_local_sched_out(void) + #endif + } + +-static inline void kmap_local_sched_in(void) ++static __always_inline void kmap_local_sched_in(void) + { + #ifdef CONFIG_KMAP_LOCAL + if (unlikely(current->kmap_ctrl.idx)) +@@ -5339,7 +5339,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev, + * past. 'prev == current' is still correct but we need to recalculate this_rq + * because prev may have moved to another CPU. + */ +-static struct rq *finish_task_switch(struct task_struct *prev) ++static __always_inline struct rq *finish_task_switch(struct task_struct *prev) + __releases(__rq_lockp(this_rq())) + { + struct rq *rq = this_rq(); +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -1464,12 +1464,12 @@ static inline struct cpumask *sched_group_span(struct sched_group *sg); + + DECLARE_STATIC_KEY_FALSE(__sched_core_enabled); + +-static inline bool sched_core_enabled(struct rq *rq) ++static __always_inline bool sched_core_enabled(struct rq *rq) + { + return static_branch_unlikely(&__sched_core_enabled) && rq->core_enabled; + } + +-static inline bool sched_core_disabled(void) ++static __always_inline bool sched_core_disabled(void) + { + return !static_branch_unlikely(&__sched_core_enabled); + } +@@ -1478,7 +1478,7 @@ static inline bool sched_core_disabled(void) + * Be careful with this function; not for general use. The return value isn't + * stable unless you actually hold a relevant rq->__lock. + */ +-static inline raw_spinlock_t *rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *rq_lockp(struct rq *rq) + { + if (sched_core_enabled(rq)) + return &rq->core->__lock; +@@ -1486,7 +1486,7 @@ static inline raw_spinlock_t *rq_lockp(struct rq *rq) + return &rq->__lock; + } + +-static inline raw_spinlock_t *__rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *__rq_lockp(struct rq *rq) + __returns_ctx_lock(rq_lockp(rq)) /* alias them */ + { + if (rq->core_enabled) +@@ -1589,12 +1589,12 @@ static inline bool sched_core_disabled(void) + return true; + } + +-static inline raw_spinlock_t *rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *rq_lockp(struct rq *rq) + { + return &rq->__lock; + } + +-static inline raw_spinlock_t *__rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *__rq_lockp(struct rq *rq) + __returns_ctx_lock(rq_lockp(rq)) /* alias them */ + { + return &rq->__lock; +@@ -1654,33 +1654,33 @@ extern void raw_spin_rq_lock_nested(struct rq *rq, int subclass) + extern bool raw_spin_rq_trylock(struct rq *rq) + __cond_acquires(true, __rq_lockp(rq)); + +-static inline void raw_spin_rq_lock(struct rq *rq) ++static __always_inline void raw_spin_rq_lock(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + raw_spin_rq_lock_nested(rq, 0); + } + +-static inline void raw_spin_rq_unlock(struct rq *rq) ++static __always_inline void raw_spin_rq_unlock(struct rq *rq) + __releases(__rq_lockp(rq)) + { + raw_spin_unlock(rq_lockp(rq)); + } + +-static inline void raw_spin_rq_lock_irq(struct rq *rq) ++static __always_inline void raw_spin_rq_lock_irq(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + local_irq_disable(); + raw_spin_rq_lock(rq); + } + +-static inline void raw_spin_rq_unlock_irq(struct rq *rq) ++static __always_inline void raw_spin_rq_unlock_irq(struct rq *rq) + __releases(__rq_lockp(rq)) + { + raw_spin_rq_unlock(rq); + local_irq_enable(); + } + +-static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) ++static __always_inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + unsigned long flags; +@@ -1691,7 +1691,7 @@ static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) + return flags; + } + +-static inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags) ++static __always_inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags) + __releases(__rq_lockp(rq)) + { + raw_spin_rq_unlock(rq); diff --git a/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch.sig b/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..878f9460d64bce3763f3692b6388147447857383 GIT binary patch literal 594 zcmV-Y07z^Ith`C1j)H$m(AbuC-F0wW_7vIo$`%HkKL`IT9 zIYM*eI3TgDWX!e<$mc?Hx=H2CpM;TDmGe@yMJl+p{i}A8*iFAYwa+NCA{8*Fc?(Xd z_yU`bOAsJ#;kP+ZS{=>CrdVEt39{kY+**Kn1kg|iFkifIYOI(8;QW~^TillQWye>o72OHW|;7%UCvC>H=g-z6^< z=)b*mX_Z!k?Ww;;%Oi#J>-!(VeM1jnKsY`7h4{#Il8&$+&M=|*Azj2VeeVgES%XN& zkV^K=#LF%qt~EGa@ywBH#QQF?>cKIq+f5|z4583o@~Ak=R1C?ELiZ$*{rTVitC>Sy g|A@3CL(8sO)DFNm?>37+HLsn%I~;~lFwaV8Nb!mq3jhEB literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch new file mode 100644 index 0000000..f26a59b --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch @@ -0,0 +1,97 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -10575,17 +10575,40 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu, + return mig_llc; + } + ++static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct *p) ++{ ++ /* ++ * On asymmetric CPU capacity domains, do not let cache-aware ++ * balancing pull the task onto a destination CPU that cannot ++ * accommodate it. Doing so would turn the task into a misfit on ++ * the destination, trading a cache-locality gain for a capacity ++ * loss. If the task already does not fit its source CPU, the move ++ * cannot make things worse, so let the LLC preference decide. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && p && ++ !task_fits_cpu(p, env->dst_cpu) && ++ task_fits_cpu(p, env->src_cpu)) ++ return true; ++ ++ return false; ++} ++ + /* + * Check if task p can migrate from source LLC to + * destination LLC in terms of cache aware load balance. + */ +-static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu, ++static enum llc_mig can_migrate_llc_task(struct lb_env *env, + struct task_struct *p) + { + struct mm_struct *mm; + bool to_pref; +- int cpu; ++ int cpu, src_cpu, dst_cpu; + ++ if (task_misfits_asym_cpu(env, p)) ++ return mig_forbid; ++ ++ src_cpu = env->src_cpu; ++ dst_cpu = env->dst_cpu; + mm = p->mm; + if (!mm) + return mig_unrestricted; +@@ -10642,6 +10665,14 @@ alb_break_llc(struct lb_env *env) + unsigned long util = 0; + struct task_struct *cur; + ++ /* ++ * Migrating misfit tasks from current CPU ++ * to CPU with a better fit. ++ * Prioritize that over LLC preference. ++ */ ++ if (env->migration_type == migrate_misfit) ++ return false; ++ + if (env->src_rq->nr_running <= 1) + return true; + +@@ -10649,7 +10680,8 @@ alb_break_llc(struct lb_env *env) + if (cur && cur->sched_class == &fair_sched_class) + util = task_util(cur); + +- if (can_migrate_llc(env->src_cpu, env->dst_cpu, ++ if (task_misfits_asym_cpu(env, cur) || ++ can_migrate_llc(env->src_cpu, env->dst_cpu, + util, false) == mig_forbid) + return true; + } +@@ -10689,8 +10721,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) + READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) + return true; + +- if (can_migrate_llc_task(env->src_cpu, +- env->dst_cpu, p) != mig_forbid) ++ if (can_migrate_llc_task(env, p) != mig_forbid) + return false; + + return true; +@@ -11753,6 +11784,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, + if (env->sd->flags & SD_SHARE_LLC) + return false; + ++ /* ++ * On asymmetric domains, group_misfit_task_load ++ * should be prioritized to move tasks to CPU that fit them ++ * over aggregating tasks to their preferred LLC. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && ++ sgs->group_misfit_task_load) ++ return false; ++ + /* + * Skip cache aware tagging if nr_balanced_failed is sufficiently high. + * Threshold of cache_nice_tries is set to 1 higher than nr_balance_failed diff --git a/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..0af85feef0b82d15f4f9cef0125f521efc2ab504 GIT binary patch literal 594 zcmV-Y0O#^@i^~_F*kqBYk6hLAa~QkzDMH6yBV_%anLsZdu!!3tn!HtdMEGaY z^%0pkFE(tjfyTOJ9TeWZ&lWQ#9$~t7#Fqy-Ja=ySig0gw%)}_1cT1`oX_0m-`~au zrro%XtDry(2RBw{B6_v-Cl{Fvu%6WJWlyb9MX@sY{7oeC@0=+&q0OsopG|rO>E8LL zvC^GK<~~)|sk=9&K)fs8!0H1U%w()|`*#41E#R=b;;3Pu7vQRZq%1ld7CwIn08JiL z30A+__$SVb!X?oJ0pne3{MwM6Zm)XIM@nSt)mQ%jTjC?IRU&aK{|z~1CQUJ)3!NF6 g)j7XD3=group_misfit_task_load < rq->misfit_task_load) { +- sgs->group_misfit_task_load = rq->misfit_task_load; +- ++ if (rq->misfit_task_load) { ++ /* ++ * Always mark the root domain overloaded so big ++ * CPUs can pick up misfit tasks via newly idle ++ * balance. ++ */ + if (balancing_at_rd) + *sg_overloaded = 1; ++ ++ /* ++ * Only account misfit load if @dst_cpu can ++ * help; otherwise, the group may be classified ++ * as misfit_task and update_sd_pick_busiest() ++ * will skip it. ++ */ ++ if (capacity_greater(capacity_of(env->dst_cpu), ++ group->sgc->max_capacity) && ++ (sgs->group_misfit_task_load < rq->misfit_task_load)) ++ sgs->group_misfit_task_load = rq->misfit_task_load; + } + } else if (env->idle && sched_reduced_capacity(rq, env->sd)) { + /* Check for a task running on a CPU with reduced capacity */ +@@ -13030,9 +13043,24 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env, + * average load. + */ + if (env->sd->flags & SD_ASYM_CPUCAPACITY && +- !capacity_greater(capacity_of(env->dst_cpu), capacity) && +- nr_running == 1) +- continue; ++ nr_running == 1) { ++ bool cluster_equal_cap = static_branch_unlikely(&sched_cluster_active) && ++ (get_actual_cpu_capacity(env->dst_cpu) == ++ get_actual_cpu_capacity(i)); ++ bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i); ++ ++ /* ++ * Busy SMT siblings reduce the capacity of CPU @i. Do ++ * not skip it in this case. ++ * ++ * CONFIG_SCHED_CLUSTER requires balancing load across ++ * clusters of identical capacity, accounting for ++ * hardware and cpufreq pressure. ++ */ ++ if (!smt_degraded_cap && !cluster_equal_cap && ++ !capacity_greater(capacity_of(env->dst_cpu), capacity)) ++ continue; ++ } + + /* + * Make sure we only pull tasks from a CPU of lower priority +diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c +--- a/kernel/sched/topology.c ++++ b/kernel/sched/topology.c +@@ -1995,10 +1995,6 @@ sd_init(struct sched_domain_topology_level *tl, + /* + * Convert topological properties into behaviour. + */ +- /* Don't attempt to spread across CPUs of different capacities. */ +- if ((sd->flags & SD_ASYM_CPUCAPACITY) && sd->child) +- sd->child->flags &= ~SD_PREFER_SIBLING; +- + if (sd->flags & SD_SHARE_CPUCAPACITY) { + sd->imbalance_pct = 110; + diff --git a/pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch.sig b/pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f125b5be4bf58f4d0a7f5833ec6edd24c254f737 GIT binary patch literal 594 zcmV-Y0nQj!JcSc4qCno8PSWEkIcF9 zm3k>T*It5^M>EIt#~B0QfZ6CJ?!FjyB;N=t8f-?5wTuv~InP*gwS^G-SN!$P)4c`p zi8(wUhv>%lTE%yERnpgxJifYfg>qA}MD$kjmR*q~0GQ*_AK~>@!e?Xdk{+X>NG7YD=mp|$ zYjgU@nh#X)c;90@Hmzg`JI09A%QrfUaA?hAMu=v|4fJsO!MT8rw3LCa*8Iw2!<1M^ zw}_a^ahOYba#uTgC= 0) ++ cpumask_andnot(ilb_cpus, ilb_cpus, cpu_smt_mask(ilb_cpu)); + continue; ++ } + +- if (idle_cpu(ilb_cpu)) +- return ilb_cpu; ++ /* ++ * Running the idle load balancer on an idle sibling of a busy ++ * SMT core can reduce the capacity available to its sibling. Prefer ++ * a CPU whose entire core is idle, but retain the first idle CPU as ++ * a fallback so idle balancing can still make progress when no fully ++ * idle core exists. ++ */ ++ if (sched_smt_active() && !is_core_idle(ilb_cpu)) { ++ if (fallback < 0) ++ fallback = ilb_cpu; ++ ++ /* ++ * The core is not idle, so there is no need to check ++ * any of its other SMT siblings. ++ */ ++ cpumask_andnot(ilb_cpus, ilb_cpus, ++ cpu_smt_mask(ilb_cpu)); ++ continue; ++ } ++ ++ return ilb_cpu; + } + +- return -1; ++ return fallback; + } + + /* + * Kick a CPU to do the NOHZ balancing, if it is time for it, via a cross-CPU + * SMP function call (IPI). + * +- * We pick the first idle CPU in the HK_TYPE_KERNEL_NOISE housekeeping set +- * (if there is one). ++ * Prefer a CPU on a fully idle core in the HK_TYPE_KERNEL_NOISE housekeeping ++ * set. Fall back to the first idle CPU when no fully idle core exists. + */ + static void kick_ilb(unsigned int flags) + { diff --git a/pkgbuilds/linux-omarchy-muqss/0144-sched-nohz-idle-core.patch.sig b/pkgbuilds/linux-omarchy-muqss/0144-sched-nohz-idle-core.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..9ac0c5f9d096cd1d6f7a303c4354e9cf93eec0db GIT binary patch literal 594 zcmV-Y0;Xiq zhm6J0`CD*?t^UnD#bcCHT$Gb)$-mx`5)tTSkLyyM@&}hf`{|SBf3Is|;GKoZW`>Q! zZ1;f%)GHZ7>fYj?r$%3>3=i9hfJ;!r=y;CN$CweI zlvOO#hdh&@9)k><-1_(@xqd{+g#f#98>E3#V3{bVW3HMUaZSjXZ$w}ce2yuFB@_e= zX2L4asjdasNWr0=AC=MbfbuAxg4XCpM>as|zeW*bHSM0gt~Hn;)T1**0A;@#Vfo>m z9&Zh!Chdz2V}*5=&SAXHVAx!w4@6h5sQl*06b!g)1Oq~L6+2It-#bMbR^atu_~DVt zt>(jx5pHS5wnEnDJE&_v~P*(f)yOyciSpG1Ey>@}o+jKo4t_${jE)$%3O%o+>wrs>y_TUtBgso&G_eB|o g;rZgTKspGO{d)|WBMGJrVtehXzaoqyOwYfly5ve7Bme*a literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch b/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch new file mode 100644 index 0000000..5110d06 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch @@ -0,0 +1,2365 @@ +diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c +--- a/drivers/acpi/acpi_processor.c ++++ b/drivers/acpi/acpi_processor.c +@@ -994,3 +994,357 @@ end: + } + EXPORT_SYMBOL_NS_GPL(acpi_processor_evaluate_cst, "ACPI_PROCESSOR_IDLE"); + #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ ++ ++#ifdef CONFIG_ACPI_PROCESSOR_IDLE ++struct acpi_lpi_states_array { ++ unsigned int size; ++ unsigned int composite_states_size; ++ struct acpi_lpi_state *entries; ++ struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; ++}; ++ ++static int obj_get_integer(union acpi_object *obj, u32 *value) ++{ ++ if (obj->type != ACPI_TYPE_INTEGER) ++ return -EINVAL; ++ ++ *value = obj->integer.value; ++ return 0; ++} ++ ++#define lpi_state_debug(handle, message, state_idx) \ ++ acpi_handle_debug(handle, message " for _LPI state %u\n", state_idx) ++ ++static void process_lpi_state_package(union acpi_object *lpi_pkg, ++ struct acpi_lpi_state *lpi_state, ++ acpi_handle handle, ++ unsigned int state_idx, bool strict) ++{ ++ union acpi_object *lpi_pkg_elem, *obj; ++ ++ if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) ++ return; ++ ++ lpi_pkg_elem = lpi_pkg->package.elements; ++ ++ /* Get the entry method first and skip the state if that fails. */ ++ obj = &lpi_pkg_elem[6]; ++ if (obj->type == ACPI_TYPE_BUFFER) { ++ struct acpi_power_register *reg; ++ ++ if (obj->buffer.length < sizeof(*reg)) { ++ lpi_state_debug(handle, "Invalid register data", state_idx); ++ return; ++ } ++ ++ reg = (struct acpi_power_register *)obj->buffer.pointer; ++ if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { ++ lpi_state_debug(handle, "Unsupported entry method", state_idx); ++ return; ++ } ++ ++ lpi_state->entry_method = ACPI_CSTATE_FFH; ++ lpi_state->address = reg->address; ++ } else if (obj->type == ACPI_TYPE_INTEGER) { ++ lpi_state->entry_method = ACPI_CSTATE_INTEGER; ++ lpi_state->address = obj->integer.value; ++ } else { ++ lpi_state_debug(handle, "Invalid entry method", state_idx); ++ return; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { ++ if (strict) { ++ lpi_state_debug(handle, "No min. residency", state_idx); ++ return; ++ } ++ ++ lpi_state_debug(handle, "Assuming 10 us min. residency", state_idx); ++ lpi_state->min_residency = 10; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { ++ if (strict) { ++ lpi_state_debug(handle, "No wake latency", state_idx); ++ return; ++ } ++ ++ lpi_state_debug(handle, "Assuming 10 us wake latency", state_idx); ++ lpi_state->wake_latency = 10; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) ++ lpi_state->flags = 0; ++ ++ if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) ++ lpi_state->arch_flags = 0; ++ ++ if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) ++ lpi_state->res_cnt_freq = 1; ++ ++ if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) ++ lpi_state->enable_parent_state = 0; ++ ++ /* Skip elements [7-8] i.e. Residency/Usage counters. */ ++ ++ /* ++ * Avoid out-of-bounds access if the size of the package is less than ++ * expected. ++ */ ++ if (lpi_pkg->package.count < 10) ++ return; ++ ++ obj = &lpi_pkg_elem[9]; ++ if (obj->type == ACPI_TYPE_STRING) ++ strscpy(lpi_state->desc, obj->string.pointer, ACPI_CX_DESC_LEN); ++} ++ ++static int acpi_processor_evaluate_lpi(acpi_handle handle, ++ struct acpi_lpi_states_array *info, ++ bool strict) ++{ ++ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ union acpi_object *lpi_data, *lpi_pkg; ++ unsigned int lpi_pkg_count, state_idx; ++ struct acpi_lpi_state *lpi_state; ++ acpi_status status; ++ int ret = 0; ++ ++ status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); ++ if (ACPI_FAILURE(status)) { ++ acpi_handle_debug(handle, "No _LPI, giving up\n"); ++ return -ENODEV; ++ } ++ ++ lpi_data = buffer.pointer; ++ ++ /* There must be at least 4 elements = 3 elements + 1 package */ ++ if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || ++ lpi_data->package.count < 4) { ++ acpi_handle_debug(handle, "Not enough elements in _LPI\n"); ++ ret = -ENODATA; ++ goto end; ++ } ++ ++ lpi_pkg_count = lpi_data->package.elements[2].integer.value; ++ ++ /* Validate number of power states. */ ++ if (!lpi_pkg_count || lpi_pkg_count != lpi_data->package.count - 3) { ++ acpi_handle_debug(handle, "Invalid _LPI state count\n"); ++ ret = -ENODATA; ++ goto end; ++ } ++ ++ lpi_state = kzalloc_objs(*lpi_state, lpi_pkg_count); ++ if (!lpi_state) { ++ ret = -ENOMEM; ++ goto end; ++ } ++ ++ info->size = lpi_pkg_count; ++ info->entries = lpi_state; ++ ++ /* _LPI State packages start at index 3. */ ++ lpi_pkg = &lpi_data->package.elements[3]; ++ ++ for (state_idx = 1; state_idx <= lpi_pkg_count; state_idx++) { ++ lpi_state->index = state_idx; ++ process_lpi_state_package(lpi_pkg++, lpi_state++, handle, ++ state_idx, strict); ++ } ++ ++ acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); ++end: ++ kfree(buffer.pointer); ++ return ret; ++} ++ ++/** ++ * combine_lpi_states - combine local and parent LPI states to form a composite LPI state ++ * ++ * @local: local LPI state ++ * @parent: parent LPI state ++ * @result: composite LPI state ++ */ ++static bool combine_lpi_states(struct acpi_lpi_state *local, ++ struct acpi_lpi_state *parent, ++ struct acpi_lpi_state *result) ++{ ++ if (parent->entry_method == ACPI_CSTATE_INTEGER) { ++ if (!parent->address) /* 0 means autopromotable */ ++ return false; ++ result->address = local->address + parent->address; ++ } else { ++ result->address = parent->address; ++ } ++ ++ result->min_residency = max(local->min_residency, parent->min_residency); ++ result->wake_latency = local->wake_latency + parent->wake_latency; ++ result->enable_parent_state = parent->enable_parent_state; ++ result->entry_method = local->entry_method; ++ ++ result->flags = parent->flags; ++ result->arch_flags = parent->arch_flags; ++ result->index = parent->index; ++ ++ scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); ++ return true; ++} ++ ++#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) ++ ++static void stash_composite_state(struct acpi_lpi_states_array *curr_level, ++ struct acpi_lpi_state *t) ++{ ++ curr_level->composite_states[curr_level->composite_states_size++] = t; ++} ++ ++static bool too_many_states(acpi_handle handle, unsigned int state_count) ++{ ++ if (state_count < ACPI_PROCESSOR_MAX_POWER) ++ return false; ++ ++ acpi_handle_info(handle, "No space for more _LPI states than %d\n", ++ ACPI_PROCESSOR_MAX_POWER); ++ return true; ++} ++ ++static unsigned int flatten_lpi_states(acpi_handle handle, ++ struct acpi_lpi_state *lpi_states, ++ unsigned int state_count, ++ struct acpi_lpi_states_array *curr, ++ struct acpi_lpi_states_array *prev) ++{ ++ struct acpi_lpi_state *parent_lpi = curr->entries; ++ unsigned int j; ++ ++ /* ++ * Combine each of the "raw" _LPI states from the current (processor ++ * container) level with all of the composite _LPI states from the ++ * previous (processor or processor container) level. ++ */ ++ for (j = 0; j < curr->size; j++, parent_lpi++) { ++ struct acpi_lpi_state *flpi; ++ int i; ++ ++ if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) ++ continue; ++ ++ if (too_many_states(handle, state_count)) ++ break; ++ ++ flpi = &lpi_states[state_count]; ++ ++ for (i = 0; i < prev->composite_states_size; i++) { ++ struct acpi_lpi_state *local_lpi = prev->composite_states[i]; ++ ++ if (parent_lpi->index > local_lpi->enable_parent_state) ++ continue; ++ ++ if (!combine_lpi_states(local_lpi, parent_lpi, flpi)) ++ continue; ++ ++ stash_composite_state(curr, flpi); ++ state_count++; ++ flpi++; ++ ++ if (state_count >= ACPI_PROCESSOR_MAX_POWER) ++ break; ++ } ++ } ++ ++ return state_count; ++} ++ ++int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict) ++{ ++ struct acpi_lpi_states_array info[2], *prev, *curr; ++ acpi_handle handle = pr_handle; ++ unsigned int state_count = 0; ++ unsigned int i; ++ int ret; ++ ++ if (!osc_pc_lpi_support_confirmed) ++ return -EOPNOTSUPP; ++ ++ curr = &info[0]; ++ curr->composite_states_size = 0; ++ ++ ret = acpi_processor_evaluate_lpi(handle, curr, strict); ++ if (ret) ++ return ret; ++ ++ /* Copy all of the usable first-level states to power.lpi_states[]. */ ++ for (i = 0; i < curr->size; i++) { ++ struct acpi_lpi_state *lpi = &curr->entries[i]; ++ struct acpi_lpi_state *flpi; ++ ++ /* ++ * Skip states that are not enabled or have an inadequate entry ++ * method for this level. ++ */ ++ if (!(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) || ++ lpi->entry_method == ACPI_CSTATE_INTEGER) ++ continue; ++ ++ if (too_many_states(pr_handle, state_count)) ++ break; ++ ++ flpi = &pr_power->lpi_states[state_count++]; ++ memcpy(flpi, lpi, sizeof(*lpi)); ++ stash_composite_state(curr, flpi); ++ } ++ ++ kfree(curr->entries); ++ ++ /* ++ * If there are no _LPI states at the first level, there are no _LPI ++ * states at all. ++ */ ++ if (!state_count) ++ return -ENODATA; ++ ++ prev = curr; ++ curr = &info[1]; ++ ++ for (;;) { ++ struct acpi_lpi_states_array *tmp; ++ struct acpi_device *d; ++ ++ if (ACPI_FAILURE(acpi_get_parent(handle, &handle))) ++ break; ++ ++ d = acpi_fetch_acpi_dev(handle); ++ if (!d) ++ break; ++ ++ if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) ++ break; ++ ++ curr->composite_states_size = 0; ++ ++ ret = acpi_processor_evaluate_lpi(handle, curr, strict); ++ if (ret) ++ break; ++ ++ /* flatten all the LPI states in this level of hierarchy */ ++ state_count = flatten_lpi_states(pr_handle, pr_power->lpi_states, ++ state_count, curr, prev); ++ ++ kfree(curr->entries); ++ ++ tmp = prev, prev = curr, curr = tmp; ++ } ++ ++ /* reset the index after flattening */ ++ for (i = 0; i < state_count; i++) ++ pr_power->lpi_states[i].index = i; ++ ++ pr_power->count = state_count; ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(acpi_processor_extract_lpi_info, "ACPI_PROCESSOR_IDLE"); ++#endif /* CONFIG_ACPI_PROCESSOR_IDLE */ +diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c +--- a/drivers/acpi/processor_idle.c ++++ b/drivers/acpi/processor_idle.c +@@ -853,237 +853,6 @@ static int acpi_processor_setup_cstates(struct acpi_processor *pr) + + #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ + +-struct acpi_lpi_states_array { +- unsigned int size; +- unsigned int composite_states_size; +- struct acpi_lpi_state *entries; +- struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; +-}; +- +-static int obj_get_integer(union acpi_object *obj, u32 *value) +-{ +- if (obj->type != ACPI_TYPE_INTEGER) +- return -EINVAL; +- +- *value = obj->integer.value; +- return 0; +-} +- +-static int acpi_processor_evaluate_lpi(acpi_handle handle, +- struct acpi_lpi_states_array *info) +-{ +- acpi_status status; +- int ret = 0; +- int pkg_count, state_idx = 1, loop; +- struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; +- union acpi_object *lpi_data; +- struct acpi_lpi_state *lpi_state; +- +- status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); +- if (ACPI_FAILURE(status)) { +- acpi_handle_debug(handle, "No _LPI, giving up\n"); +- return -ENODEV; +- } +- +- lpi_data = buffer.pointer; +- +- /* There must be at least 4 elements = 3 elements + 1 package */ +- if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || +- lpi_data->package.count < 4) { +- pr_debug("not enough elements in _LPI\n"); +- ret = -ENODATA; +- goto end; +- } +- +- pkg_count = lpi_data->package.elements[2].integer.value; +- +- /* Validate number of power states. */ +- if (pkg_count < 1 || pkg_count != lpi_data->package.count - 3) { +- pr_debug("count given by _LPI is not valid\n"); +- ret = -ENODATA; +- goto end; +- } +- +- lpi_state = kzalloc_objs(*lpi_state, pkg_count); +- if (!lpi_state) { +- ret = -ENOMEM; +- goto end; +- } +- +- info->size = pkg_count; +- info->entries = lpi_state; +- +- /* LPI States start at index 3 */ +- for (loop = 3; state_idx <= pkg_count; loop++, state_idx++, lpi_state++) { +- union acpi_object *element, *pkg_elem, *obj; +- +- element = &lpi_data->package.elements[loop]; +- if (element->type != ACPI_TYPE_PACKAGE || element->package.count < 7) +- continue; +- +- pkg_elem = element->package.elements; +- +- obj = pkg_elem + 6; +- if (obj->type == ACPI_TYPE_BUFFER) { +- struct acpi_power_register *reg; +- +- if (obj->buffer.length < sizeof(*reg)) { +- acpi_handle_debug(handle, +- "Invalid register data for _LPI state %d\n", +- state_idx); +- continue; +- } +- +- reg = (struct acpi_power_register *)obj->buffer.pointer; +- if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_IO && +- reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) +- continue; +- +- lpi_state->address = reg->address; +- lpi_state->entry_method = +- reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE ? +- ACPI_CSTATE_FFH : ACPI_CSTATE_SYSTEMIO; +- } else if (obj->type == ACPI_TYPE_INTEGER) { +- lpi_state->entry_method = ACPI_CSTATE_INTEGER; +- lpi_state->address = obj->integer.value; +- } else { +- pr_debug("Entry method of state-%d is invalid, disable it.\n", +- state_idx); +- continue; +- } +- +- lpi_state->index = state_idx; +- if (obj_get_integer(pkg_elem + 0, &lpi_state->min_residency)) { +- pr_debug("No min. residency found, assuming 10 us\n"); +- lpi_state->min_residency = 10; +- } +- +- if (obj_get_integer(pkg_elem + 1, &lpi_state->wake_latency)) { +- pr_debug("No wakeup residency found, assuming 10 us\n"); +- lpi_state->wake_latency = 10; +- } +- +- if (obj_get_integer(pkg_elem + 2, &lpi_state->flags)) +- lpi_state->flags = 0; +- +- if (obj_get_integer(pkg_elem + 3, &lpi_state->arch_flags)) +- lpi_state->arch_flags = 0; +- +- if (obj_get_integer(pkg_elem + 4, &lpi_state->res_cnt_freq)) +- lpi_state->res_cnt_freq = 1; +- +- if (obj_get_integer(pkg_elem + 5, &lpi_state->enable_parent_state)) +- lpi_state->enable_parent_state = 0; +- +- /* Skip elements [7-8] i.e. Residency/Usage counters. */ +- +- /* +- * Avoid out-of-bounds access if the size of the package is less +- * than expected. +- */ +- if (element->package.count < 10) +- continue; +- +- obj = pkg_elem + 9; +- if (obj->type == ACPI_TYPE_STRING) +- strscpy(lpi_state->desc, obj->string.pointer, +- ACPI_CX_DESC_LEN); +- } +- +- acpi_handle_debug(handle, "Found %d power states\n", state_idx); +-end: +- kfree(buffer.pointer); +- return ret; +-} +- +-/** +- * combine_lpi_states - combine local and parent LPI states to form a composite LPI state +- * +- * @local: local LPI state +- * @parent: parent LPI state +- * @result: composite LPI state +- */ +-static bool combine_lpi_states(struct acpi_lpi_state *local, +- struct acpi_lpi_state *parent, +- struct acpi_lpi_state *result) +-{ +- if (parent->entry_method == ACPI_CSTATE_INTEGER) { +- if (!parent->address) /* 0 means autopromotable */ +- return false; +- result->address = local->address + parent->address; +- } else { +- result->address = parent->address; +- } +- +- result->min_residency = max(local->min_residency, parent->min_residency); +- result->wake_latency = local->wake_latency + parent->wake_latency; +- result->enable_parent_state = parent->enable_parent_state; +- result->entry_method = local->entry_method; +- +- result->flags = parent->flags; +- result->arch_flags = parent->arch_flags; +- result->index = parent->index; +- +- scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); +- return true; +-} +- +-#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) +- +-static void stash_composite_state(struct acpi_lpi_states_array *curr_level, +- struct acpi_lpi_state *t) +-{ +- curr_level->composite_states[curr_level->composite_states_size++] = t; +-} +- +-static unsigned int flatten_lpi_states(struct acpi_processor *pr, +- unsigned int flat_state_cnt, +- struct acpi_lpi_states_array *curr_level, +- struct acpi_lpi_states_array *prev_level) +-{ +- int i, j, state_count = curr_level->size; +- struct acpi_lpi_state *p, *t = curr_level->entries; +- +- curr_level->composite_states_size = 0; +- for (j = 0; j < state_count; j++, t++) { +- struct acpi_lpi_state *flpi; +- +- if (!(t->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) +- continue; +- +- if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) { +- pr_warn("Limiting number of LPI states to max (%d)\n", +- ACPI_PROCESSOR_MAX_POWER); +- pr_warn("Please increase ACPI_PROCESSOR_MAX_POWER if needed.\n"); +- break; +- } +- +- flpi = &pr->power.lpi_states[flat_state_cnt]; +- +- if (!prev_level) { /* leaf/processor node */ +- memcpy(flpi, t, sizeof(*t)); +- stash_composite_state(curr_level, flpi); +- flat_state_cnt++; +- continue; +- } +- +- for (i = 0; i < prev_level->composite_states_size; i++) { +- p = prev_level->composite_states[i]; +- if (t->index <= p->enable_parent_state && +- combine_lpi_states(p, t, flpi)) { +- stash_composite_state(curr_level, flpi); +- flat_state_cnt++; +- flpi++; +- if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) +- break; +- } +- } +- } +- +- kfree(curr_level->entries); +- return flat_state_cnt; +-} +- + int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) + { + return -EOPNOTSUPP; +@@ -1091,64 +860,16 @@ int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) + + static int acpi_processor_get_lpi_info(struct acpi_processor *pr) + { +- int ret, i; +- acpi_status status; +- acpi_handle handle = pr->handle, pr_ahandle; +- struct acpi_device *d = NULL; +- struct acpi_lpi_states_array info[2], *tmp, *prev, *curr; +- unsigned int state_count; ++ int ret; + + /* make sure our architecture has support */ + ret = acpi_processor_ffh_lpi_probe(pr->id); + if (ret == -EOPNOTSUPP) + return ret; + +- if (!osc_pc_lpi_support_confirmed) +- return -EOPNOTSUPP; +- +- if (!acpi_has_method(handle, "_LPI")) +- return -EINVAL; +- +- prev = &info[0]; +- curr = &info[1]; +- handle = pr->handle; +- ret = acpi_processor_evaluate_lpi(handle, prev); ++ ret = acpi_processor_extract_lpi_info(pr->handle, &pr->power, false); + if (ret) + return ret; +- state_count = flatten_lpi_states(pr, 0, prev, NULL); +- +- status = acpi_get_parent(handle, &pr_ahandle); +- while (ACPI_SUCCESS(status)) { +- d = acpi_fetch_acpi_dev(pr_ahandle); +- if (!d) +- break; +- +- handle = pr_ahandle; +- +- if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) +- break; +- +- /* can be optional ? */ +- if (!acpi_has_method(handle, "_LPI")) +- break; +- +- ret = acpi_processor_evaluate_lpi(handle, curr); +- if (ret) +- break; +- +- /* flatten all the LPI states in this level of hierarchy */ +- state_count = flatten_lpi_states(pr, state_count, curr, prev); +- +- tmp = prev, prev = curr, curr = tmp; +- +- status = acpi_get_parent(handle, &pr_ahandle); +- } +- +- /* reset the index after flattening */ +- for (i = 0; i < state_count; i++) +- pr->power.lpi_states[i].index = i; +- +- pr->power.count = state_count; + + /* Tell driver that _LPI is supported. */ + pr->flags.has_lpi = 1; +diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c +--- a/drivers/base/power/main.c ++++ b/drivers/base/power/main.c +@@ -41,6 +41,9 @@ + #include "../base.h" + #include "power.h" + ++#undef MODULE_PARAM_PREFIX ++#define MODULE_PARAM_PREFIX "pm." ++ + typedef int (*pm_callback_t)(struct device *); + + /* +@@ -532,6 +535,11 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644); + MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace, + "Backtrace all CPUs on DPM watchdog timeout"); + ++static bool __read_mostly dpm_watchdog_enabled = ++ IS_ENABLED(CONFIG_DPM_WATCHDOG_ENABLED); ++module_param(dpm_watchdog_enabled, bool, 0644); ++MODULE_PARM_DESC(dpm_watchdog_enabled, "Enable DPM watchdog"); ++ + static unsigned int __read_mostly dpm_watchdog_timeout = CONFIG_DPM_WATCHDOG_TIMEOUT; + static unsigned int __read_mostly dpm_watchdog_warning_timeout = + CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT; +@@ -627,6 +635,9 @@ static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev) + { + struct timer_list *timer = &wd->timer; + ++ if (!dpm_watchdog_enabled) ++ return; ++ + wd->dev = dev; + wd->tsk = current; + wd->fatal = dpm_watchdog_timeout == dpm_watchdog_warning_timeout; +@@ -645,6 +656,9 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd) + { + struct timer_list *timer = &wd->timer; + ++ if (!dpm_watchdog_enabled) ++ return; ++ + timer_delete_sync(timer); + timer_destroy_on_stack(timer); + } +diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c +--- a/drivers/base/power/runtime.c ++++ b/drivers/base/power/runtime.c +@@ -469,9 +469,6 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev) + if (retval == -EACCES) + retval = -EAGAIN; + +- if (retval != -EAGAIN && retval != -EBUSY) +- dev->power.runtime_error = retval; +- + return retval; + } + +@@ -751,6 +748,9 @@ static int rpm_suspend(struct device *dev, int rpmflags) + dev->power.deferred_resume = false; + wake_up_all(&dev->power.wait_queue); + ++ if (retval != -EAGAIN && retval != -EBUSY) ++ dev->power.runtime_error = retval; ++ + /* + * On transient errors, if the callback routine failed an autosuspend, + * and if the last_busy time has been updated so that there is a new +diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c +--- a/drivers/cpufreq/amd-pstate-ut.c ++++ b/drivers/cpufreq/amd-pstate-ut.c +@@ -59,6 +59,7 @@ static int amd_pstate_ut_check_freq(u32 index); + static int amd_pstate_ut_epp(u32 index); + static int amd_pstate_ut_check_driver(u32 index); + static int amd_pstate_ut_check_freq_attrs(u32 index); ++static int amd_pstate_ut_check_floor_freq(u32 index); + + static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { + {"amd_pstate_ut_acpi_cpc_valid", amd_pstate_ut_acpi_cpc_valid }, +@@ -68,6 +69,7 @@ static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { + {"amd_pstate_ut_epp", amd_pstate_ut_epp }, + {"amd_pstate_ut_check_driver", amd_pstate_ut_check_driver }, + {"amd_pstate_ut_check_freq_attrs", amd_pstate_ut_check_freq_attrs }, ++ {"amd_pstate_ut_check_floor_freq", amd_pstate_ut_check_floor_freq }, + }; + + static bool test_in_list(const char *list, const char *name) +@@ -275,6 +277,7 @@ static int amd_pstate_set_mode(enum amd_pstate_mode mode) + static int amd_pstate_ut_epp(u32 index) + { + static const char * const epp_strings[] = { ++ "dynamic", + "power", + "balance_power", + "balance_performance", +@@ -282,10 +285,10 @@ static int amd_pstate_ut_epp(u32 index) + }; + char *buf __free(cleanup_page) = NULL; + struct cpufreq_policy *policy = NULL; ++ unsigned long orig_dynamic_epp = 0; + enum amd_pstate_mode orig_mode; + struct amd_cpudata *cpudata; + unsigned long orig_policy; +- bool orig_dynamic_epp; + int ret, cpu = 0; + u16 epp; + int i; +@@ -294,9 +297,11 @@ static int amd_pstate_ut_epp(u32 index) + if (!policy) + return -ENODEV; + +- cpudata = policy->driver_data; + orig_mode = amd_pstate_get_status(); +- orig_dynamic_epp = cpudata->dynamic_epp; ++ if (policy->driver_data) { ++ cpudata = policy->driver_data; ++ orig_dynamic_epp = cpudata->dynamic_epp; ++ } + + /* Drop reference before potential driver change. */ + cpufreq_cpu_put(policy); +@@ -321,16 +326,6 @@ static int amd_pstate_ut_epp(u32 index) + orig_policy = cpudata->policy; + cpudata->policy = CPUFREQ_POLICY_POWERSAVE; + +- /* +- * Disable dynamic EPP before running test. If "orig_dynamic_epp" is +- * true, the driver will do a redundant switch at the end and there +- * is no need for enabling it again at the end of the test. +- */ +- if (cpudata->dynamic_epp) { +- pr_debug("Dynamic EPP is enabled, disabling it\n"); +- amd_pstate_clear_dynamic_epp(policy); +- } +- + for (epp = 0; epp <= U8_MAX; epp++) { + u8 val; + +@@ -367,6 +362,11 @@ static int amd_pstate_ut_epp(u32 index) + if (ret < 0) + goto out; + strreplace(buf, '\n', '\0'); ++ /* ++ * "dynamic" mode reports the EPP as "dynamic(profile:X)" ++ * Trim at "(" and just compare tie the epp string. ++ */ ++ strreplace(buf, '(', '\0'); + + if (strcmp(buf, epp_strings[i])) { + pr_err("String EPP value mismatch: %s != %s\n", buf, epp_strings[i]); +@@ -380,18 +380,23 @@ static int amd_pstate_ut_epp(u32 index) + out: + if (policy) { + cpudata->policy = orig_policy; ++ /* ++ * If the driver had enabled dynamic_epp to begin with, ++ * restore it here before dropping policy reference. ++ */ ++ if (orig_dynamic_epp) { ++ int ret2; ++ ++ ret2 = store_energy_performance_preference(policy, ++ epp_strings[0], ++ strlen(epp_strings[0])); ++ if (!ret && (ret2 < 0)) ++ ret = ret2; ++ } + up_write(&policy->rwsem); + cpufreq_cpu_put(policy); + } + +- if (orig_dynamic_epp) { +- int ret2; +- +- ret2 = amd_pstate_set_mode(AMD_PSTATE_DISABLE); +- if (!ret && ret2) +- ret = ret2; +- } +- + if (orig_mode != amd_pstate_get_status()) { + int ret2; + +@@ -557,6 +562,80 @@ out: + return ret; + } + ++static int amd_pstate_ut_check_floor_freq(u32 index) ++{ ++ struct cpufreq_policy *policy __free(put_cpufreq_policy) = NULL; ++ char *buf __free(cleanup_page) = NULL; ++ unsigned int orig_floor_freq; ++ unsigned int floor_freq; ++ int ret, cpu = 0; ++ ++ if (!cpu_feature_enabled(X86_FEATURE_CPPC_PERF_PRIO)) ++ return -EOPNOTSUPP; ++ ++ policy = cpufreq_cpu_get(cpu); ++ if (!policy) ++ return -ENODEV; ++ ++ buf = (char *)__get_free_page(GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ guard(rwsem_write)(&policy->rwsem); ++ ++ if (!policy->driver_data) ++ return -ENODEV; ++ ++ /* Retrieve original floor frequency */ ++ memset(buf, 0, PAGE_SIZE); ++ ret = show_amd_pstate_floor_freq(policy, buf); ++ if (ret < 0) ++ return ret; ++ ++ ret = kstrtou32(buf, 0, &orig_floor_freq); ++ if (ret) ++ return ret; ++ ++ memset(buf, 0, PAGE_SIZE); ++ snprintf(buf, PAGE_SIZE, "%u", policy->cpuinfo.min_freq); ++ ++ /* Set floor frequency to cpuinfo.min_freq */ ++ ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_err("Failed to set floor frequency to %s\n", buf); ++ return ret; ++ } ++ ++ memset(buf, 0, PAGE_SIZE); ++ ret = show_amd_pstate_floor_freq(policy, buf); ++ if (ret < 0) ++ return ret; ++ ++ strreplace(buf, '\n', '\0'); ++ ret = kstrtou32(buf, 0, &floor_freq); ++ if (ret) ++ return ret; ++ ++ /* Confirm sysfs reflects the change correctly. */ ++ if (floor_freq != policy->cpuinfo.min_freq) { ++ pr_err("Floor frequency value mismatch: %u != %u\n", ++ floor_freq, policy->cpuinfo.min_freq); ++ return -EINVAL; ++ } ++ ++ memset(buf, 0, PAGE_SIZE); ++ snprintf(buf, PAGE_SIZE, "%u", orig_floor_freq); ++ ++ /* Restore the original value. */ ++ ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_err("Failed to restore floor frequency to %s\n", buf); ++ return ret; ++ } ++ ++ return 0; ++} ++ + static int __init amd_pstate_ut_init(void) + { + u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); +@@ -575,10 +654,16 @@ static int __init amd_pstate_ut_init(void) + + ret = amd_pstate_ut_cases[i].func(i); + +- if (ret) ++ if (ret) { ++ /* Platform does not support the feature being tested. */ ++ if (ret == -EOPNOTSUPP) { ++ pr_err("%-4d %-20s\t skipped!\n", i+1, amd_pstate_ut_cases[i].name); ++ continue; ++ } + pr_err("%-4d %-20s\t fail: %d!\n", i+1, amd_pstate_ut_cases[i].name, ret); +- else ++ } else { + pr_info("%-4d %-20s\t success!\n", i+1, amd_pstate_ut_cases[i].name); ++ } + } + + return 0; +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -75,7 +75,7 @@ const char *amd_pstate_get_mode_string(enum amd_pstate_mode mode) + mode = AMD_PSTATE_UNDEFINED; + return amd_pstate_mode_string[mode]; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_mode_string); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_mode_string); + + struct quirk_entry { + u32 nominal_freq; +@@ -87,7 +87,6 @@ static struct cpufreq_driver amd_pstate_driver; + static struct cpufreq_driver amd_pstate_epp_driver; + static int cppc_state = AMD_PSTATE_UNDEFINED; + static bool amd_pstate_prefcore = true; +-static bool dynamic_epp; + static struct quirk_entry *quirks; + + /* +@@ -106,6 +105,7 @@ static struct quirk_entry *quirks; + * 3 balance_power + * 4 power + * 5 custom (for raw EPP values) ++ * 6 dynamic (platform profile driven selection) + */ + enum energy_perf_value_index { + EPP_INDEX_DEFAULT = 0, +@@ -114,6 +114,7 @@ enum energy_perf_value_index { + EPP_INDEX_BALANCE_POWERSAVE, + EPP_INDEX_POWERSAVE, + EPP_INDEX_CUSTOM, ++ EPP_INDEX_DYNAMIC, + EPP_INDEX_MAX, + }; + +@@ -124,6 +125,7 @@ static const char * const energy_perf_strings[] = { + [EPP_INDEX_BALANCE_POWERSAVE] = "balance_power", + [EPP_INDEX_POWERSAVE] = "power", + [EPP_INDEX_CUSTOM] = "custom", ++ [EPP_INDEX_DYNAMIC] = "dynamic", + }; + static_assert(ARRAY_SIZE(energy_perf_strings) == EPP_INDEX_MAX); + +@@ -134,7 +136,7 @@ static unsigned int epp_values[] = { + [EPP_INDEX_BALANCE_POWERSAVE] = AMD_CPPC_EPP_BALANCE_POWERSAVE, + [EPP_INDEX_POWERSAVE] = AMD_CPPC_EPP_POWERSAVE, + }; +-static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 1); ++static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 2); + + typedef int (*cppc_mode_transition_fn)(int); + +@@ -462,7 +464,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + { + union perf_cached perf = READ_ONCE(cpudata->perf); + u64 cap1, numerator, cppc_req; +- u8 min_perf; + + int ret = rdmsrq_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, + &cap1); +@@ -478,16 +479,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + return ret; + + WRITE_ONCE(cpudata->cppc_req_cached, cppc_req); +- min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); +- +- /* +- * Clear out the min_perf part to check if the rest of the MSR is 0, if yes, this is an +- * indication that the min_perf value is the one specified through the BIOS option +- */ +- cppc_req &= ~(AMD_CPPC_MIN_PERF_MASK); +- +- if (!cppc_req) +- perf.bios_min_perf = min_perf; + + perf.highest_perf = numerator; + perf.max_limit_perf = numerator; +@@ -495,6 +486,7 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + perf.nominal_perf = FIELD_GET(AMD_CPPC_NOMINAL_PERF_MASK, cap1); + perf.lowest_nonlinear_perf = FIELD_GET(AMD_CPPC_LOWNONLIN_PERF_MASK, cap1); + perf.lowest_perf = FIELD_GET(AMD_CPPC_LOWEST_PERF_MASK, cap1); ++ perf.bios_min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); + WRITE_ONCE(cpudata->perf, perf); + WRITE_ONCE(cpudata->prefcore_ranking, FIELD_GET(AMD_CPPC_HIGHEST_PERF_MASK, cap1)); + WRITE_ONCE(cpudata->floor_perf_cnt, FIELD_GET(AMD_CPPC_FLOOR_PERF_CNT_MASK, cap1)); +@@ -789,6 +781,7 @@ static unsigned int amd_pstate_fast_switch(struct cpufreq_policy *policy, + static void amd_pstate_adjust_perf(struct cpufreq_policy *policy, + unsigned long _min_perf, + unsigned long target_perf, ++ unsigned long _max_perf, + unsigned long capacity) + { + u8 max_perf, min_perf, des_perf, cap_perf; +@@ -1044,6 +1037,13 @@ static int amd_pstate_init_freq(struct amd_cpudata *cpudata) + return -EINVAL; + } + ++ if (perf.bios_min_perf) { ++ u32 bios_min_freq = perf_to_freq(perf, cpudata->nominal_freq, perf.bios_min_perf); ++ ++ pr_debug("Found Requested CPU Min Frequency of %uKHz on CPU%d\n", ++ bios_min_freq, cpudata->cpu); ++ } ++ + return 0; + } + +@@ -1192,6 +1192,24 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, + return NOTIFY_OK; + } + ++static int amd_pstate_get_epp_from_platform_profile(struct cpufreq_policy *policy, ++ enum platform_profile_option profile) ++{ ++ switch (profile) { ++ case PLATFORM_PROFILE_PERFORMANCE: ++ return AMD_CPPC_EPP_PERFORMANCE; ++ case PLATFORM_PROFILE_BALANCED: ++ return amd_pstate_get_balanced_epp(policy); ++ case PLATFORM_PROFILE_LOW_POWER: ++ return AMD_CPPC_EPP_POWERSAVE; ++ default: ++ break; ++ } ++ ++ pr_err("Unknown Platform Profile %d\n", profile); ++ return -EOPNOTSUPP; ++} ++ + static int amd_pstate_profile_probe(void *drvdata, unsigned long *choices) + { + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); +@@ -1217,31 +1235,19 @@ static int amd_pstate_profile_set(struct device *dev, + struct amd_cpudata *cpudata = dev_get_drvdata(dev); + struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); + int ret; ++ u8 epp; + + if (!policy) + return -ENODEV; + +- switch (profile) { +- case PLATFORM_PROFILE_LOW_POWER: +- ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); +- if (ret) +- return ret; +- break; +- case PLATFORM_PROFILE_BALANCED: +- ret = amd_pstate_set_epp(policy, +- amd_pstate_get_balanced_epp(policy)); +- if (ret) +- return ret; +- break; +- case PLATFORM_PROFILE_PERFORMANCE: +- ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_PERFORMANCE); +- if (ret) +- return ret; +- break; +- default: +- pr_err("Unknown Platform Profile %d\n", profile); +- return -EOPNOTSUPP; +- } ++ ret = amd_pstate_get_epp_from_platform_profile(policy, profile); ++ if (ret < 0) ++ return ret; ++ ++ epp = (u8)ret; ++ ret = amd_pstate_set_epp(policy, epp); ++ if (ret) ++ return ret; + + cpudata->current_profile = profile; + +@@ -1267,28 +1273,20 @@ void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy) + kfree(cpudata->profile_name); + cpudata->dynamic_epp = false; + } +-EXPORT_SYMBOL_GPL(amd_pstate_clear_dynamic_epp); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_clear_dynamic_epp); + + static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; ++ u64 prev = READ_ONCE(cpudata->cppc_req_cached); + int ret; + u8 epp; + +- switch (cpudata->current_profile) { +- case PLATFORM_PROFILE_PERFORMANCE: +- epp = AMD_CPPC_EPP_PERFORMANCE; +- break; +- case PLATFORM_PROFILE_LOW_POWER: +- epp = AMD_CPPC_EPP_POWERSAVE; +- break; +- case PLATFORM_PROFILE_BALANCED: +- epp = amd_pstate_get_balanced_epp(policy); +- break; +- default: +- pr_err("Unknown Platform Profile %d\n", cpudata->current_profile); +- return -EOPNOTSUPP; +- } ++ ret = amd_pstate_get_epp_from_platform_profile(policy, cpudata->current_profile); ++ if (ret < 0) ++ return ret; ++ ++ epp = (u8)ret; + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; +@@ -1321,6 +1319,9 @@ static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) + cleanup: + amd_pstate_clear_dynamic_epp(policy); + ++ epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, prev); ++ /* Restore previous EPP if toggling Dynamic EPP failed. */ ++ amd_pstate_set_epp(policy, epp); + return ret; + } + +@@ -1391,7 +1392,7 @@ static ssize_t show_amd_pstate_hw_prefcore(struct cpufreq_policy *policy, + static ssize_t show_energy_performance_available_preferences( + struct cpufreq_policy *policy, char *buf) + { +- int offset = 0, i; ++ int i, offset = 0; + struct amd_cpudata *cpudata = policy->driver_data; + + if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) +@@ -1414,11 +1415,6 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + bool raw_epp = false; + u8 epp; + +- if (cpudata->dynamic_epp) { +- pr_debug("EPP cannot be set when dynamic EPP is enabled\n"); +- return -EBUSY; +- } +- + /* + * if the value matches a number, use that, otherwise see if + * matches an index in the energy_perf_strings array +@@ -1429,6 +1425,25 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + ret = sysfs_match_string(energy_perf_strings, buf); + if (ret < 0 || ret == EPP_INDEX_CUSTOM) + return -EINVAL; ++ ++ if (ret == EPP_INDEX_DYNAMIC) { ++ if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) ++ return -EBUSY; ++ /* ++ * Dynamic EPP was already enabled for this CPU. ++ * Nothing to do. ++ */ ++ if (cpudata->dynamic_epp) ++ return count; ++ ++ cpudata->current_profile = PLATFORM_PROFILE_BALANCED; ++ ret = amd_pstate_set_dynamic_epp(policy); ++ if (ret) ++ return ret; ++ ++ return count; ++ } ++ + if (ret) + epp = epp_values[ret]; + else +@@ -1440,6 +1455,13 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + return -EBUSY; + } + ++ /* ++ * Dynamic EPP was enabled previously! ++ * Switch back to the static EPP mode. ++ */ ++ if (cpudata->dynamic_epp) ++ amd_pstate_clear_dynamic_epp(policy); ++ + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; +@@ -1448,7 +1470,7 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + + return count; + } +-EXPORT_SYMBOL_GPL(store_energy_performance_preference); ++EXPORT_SYMBOL_FOR_PSTATE_UT(store_energy_performance_preference); + + ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf) + { +@@ -1457,7 +1479,7 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * + + epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, cpudata->cppc_req_cached); + +- if (cpudata->raw_epp) ++ if (!cpudata->dynamic_epp && cpudata->raw_epp) + return sysfs_emit(buf, "%u\n", epp); + + switch (epp) { +@@ -1477,12 +1499,14 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * + return -EINVAL; + } + ++ if (cpudata->dynamic_epp) ++ return sysfs_emit(buf, "dynamic(profile:%s)\n", energy_perf_strings[preference]); ++ + return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]); + } +-EXPORT_SYMBOL_GPL(show_energy_performance_preference); ++EXPORT_SYMBOL_FOR_PSTATE_UT(show_energy_performance_preference); + +-static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, +- const char *buf, size_t count) ++ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count) + { + struct amd_cpudata *cpudata = policy->driver_data; + union perf_cached perf = READ_ONCE(cpudata->perf); +@@ -1505,13 +1529,15 @@ static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, + + return ret ?: count; + } ++EXPORT_SYMBOL_FOR_PSTATE_UT(store_amd_pstate_floor_freq); + +-static ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) ++ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) + { + struct amd_cpudata *cpudata = policy->driver_data; + + return sysfs_emit(buf, "%u\n", cpudata->floor_freq); + } ++EXPORT_SYMBOL_FOR_PSTATE_UT(show_amd_pstate_floor_freq); + + static ssize_t show_amd_pstate_floor_count(struct cpufreq_policy *policy, char *buf) + { +@@ -1579,7 +1605,7 @@ struct freq_attr **amd_pstate_get_current_attrs(void) + return NULL; + return current_pstate_driver->attr; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_current_attrs); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_current_attrs); + + static struct freq_attr **get_freq_attrs(void) + { +@@ -1764,7 +1790,7 @@ int amd_pstate_get_status(void) + { + return cppc_state; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_status); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status); + + int amd_pstate_update_status(const char *buf, size_t size) + { +@@ -1784,7 +1810,7 @@ int amd_pstate_update_status(const char *buf, size_t size) + + return 0; + } +-EXPORT_SYMBOL_GPL(amd_pstate_update_status); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_update_status); + + static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +@@ -1812,50 +1838,12 @@ static ssize_t prefcore_show(struct device *dev, + return sysfs_emit(buf, "%s\n", str_enabled_disabled(amd_pstate_prefcore)); + } + +-static ssize_t dynamic_epp_show(struct device *dev, +- struct device_attribute *attr, char *buf) +-{ +- return sysfs_emit(buf, "%s\n", str_enabled_disabled(dynamic_epp)); +-} +- +-static ssize_t dynamic_epp_store(struct device *a, struct device_attribute *b, +- const char *buf, size_t count) +-{ +- bool enabled; +- int ret; +- +- ret = kstrtobool(buf, &enabled); +- if (ret) +- return ret; +- +- guard(mutex)(&amd_pstate_driver_lock); +- +- if (cppc_state != AMD_PSTATE_ACTIVE) { +- pr_debug("dynamic_epp can only be toggled in active mode\n"); +- return -EINVAL; +- } +- +- /* Nothing to do */ +- if (dynamic_epp == enabled) +- return count; +- +- /* reinitialize with desired dynamic EPP value */ +- dynamic_epp = enabled; +- ret = amd_pstate_change_driver_mode(cppc_state); +- if (ret) +- dynamic_epp = false; +- +- return ret ? ret : count; +-} +- + static DEVICE_ATTR_RW(status); + static DEVICE_ATTR_RO(prefcore); +-static DEVICE_ATTR_RW(dynamic_epp); + + static struct attribute *pstate_global_attributes[] = { + &dev_attr_status.attr, + &dev_attr_prefcore.attr, +- &dev_attr_dynamic_epp.attr, + NULL + }; + +@@ -1938,12 +1926,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + + policy->boost_supported = READ_ONCE(cpudata->boost_supported); + +- /* Fetch the firmware programmed default EPP value */ ++ /* Cache the firmware programmed EPP */ + default_epp = amd_pstate_get_epp(cpudata); + if (default_epp < 0) { + ret = default_epp; + goto free_cpudata1; + } ++ FIELD_MODIFY(AMD_CPPC_EPP_PERF_MASK, &cpudata->cppc_req_cached, default_epp); + + /* + * Set the policy to provide a valid fallback value in case +@@ -1961,10 +1950,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + cpudata->current_profile = PLATFORM_PROFILE_BALANCED; + } + +- if (dynamic_epp) +- ret = amd_pstate_set_dynamic_epp(policy); +- else +- ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); ++ ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); + if (ret) + goto free_cpudata1; + +@@ -2036,6 +2022,18 @@ static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy) + if (!policy->cpuinfo.max_freq) + return -ENODEV; + ++ /* Must be a switch between PERFORMANCE and POWERSAVE */ ++ if (cpudata->policy != policy->policy) { ++ /* ++ * Disable dynamic_epp when switching ++ * out of CPUFREQ_POLICY_POWERSAVE. ++ */ ++ if (cpudata->dynamic_epp) { ++ WARN_ON_ONCE(cpudata->policy != CPUFREQ_POLICY_POWERSAVE); ++ amd_pstate_clear_dynamic_epp(policy); ++ } ++ } ++ + cpudata->policy = policy->policy; + + ret = amd_pstate_epp_update_limit(policy, true); +@@ -2363,19 +2361,8 @@ static int __init amd_prefcore_param(char *str) + return 0; + } + +-static int __init amd_dynamic_epp_param(char *str) +-{ +- if (!strcmp(str, "disable")) +- dynamic_epp = false; +- if (!strcmp(str, "enable")) +- dynamic_epp = true; +- +- return 0; +-} +- + early_param("amd_pstate", amd_pstate_param); + early_param("amd_prefcore", amd_prefcore_param); +-early_param("amd_dynamic_epp", amd_dynamic_epp_param); + + MODULE_AUTHOR("Huang Rui "); + MODULE_DESCRIPTION("AMD Processor P-state Frequency Driver"); +diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h +--- a/drivers/cpufreq/amd-pstate.h ++++ b/drivers/cpufreq/amd-pstate.h +@@ -11,6 +11,13 @@ + #include + #include + ++#if IS_MODULE(CONFIG_X86_AMD_PSTATE_UT) ++#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) \ ++ EXPORT_SYMBOL_FOR_MODULES(symbol, "amd-pstate-ut") ++#else ++#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) ++#endif ++ + /********************************************************************* + * AMD P-state INTERFACE * + *********************************************************************/ +@@ -153,6 +160,8 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + const char *buf, size_t count); + ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf); + void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy); ++ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count); ++ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf); + + struct freq_attr; + +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2225,14 +2225,17 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + * @policy: cpufreq policy object of the target CPU. + * @min_perf: Minimum (required) performance level (units of @capacity). + * @target_perf: Target (desired) performance level (units of @capacity). ++ * @max_perf: Maximum (allowed) performance level (units of @capacity). + * @capacity: Capacity of the target CPU. + * +- * Carry out a fast performance level switch of @cpu without sleeping. ++ * Carry out a fast performance level adjustment for the CPU represented by ++ * @policy without sleeping. + * + * The driver's ->adjust_perf() callback invoked by this function must be +- * suitable for being called from within RCU-sched read-side critical sections +- * and it is expected to select a suitable performance level equal to or above +- * @min_perf and preferably equal to or below @target_perf. ++ * suitable for calling from within RCU-sched read-side critical sections and ++ * it is expected to program the processor to select suitable performance ++ * levels between @min_perf and @max_perf inclusive and preferably close to ++ * @target_perf going forward for the CPU represented by @policy. + * + * This function must not be called if policy->fast_switch_enabled is unset. + * +@@ -2244,9 +2247,10 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { +- cpufreq_driver->adjust_perf(policy, min_perf, target_perf, capacity); ++ cpufreq_driver->adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /** +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -299,7 +299,6 @@ struct pstate_funcs { + static struct pstate_funcs pstate_funcs __read_mostly; + + static bool hwp_active __ro_after_init; +-static int hwp_mode_bdw __ro_after_init; + static bool per_cpu_limits __ro_after_init; + static bool hwp_forced __ro_after_init; + static bool hwp_boost __read_mostly; +@@ -587,21 +586,14 @@ static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) + + hwp_is_hybrid = true; + +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_pstate * scaling, +- perf_ctl_scaling); +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_pstate * scaling, +- perf_ctl_scaling); +- + freq = perf_ctl_max_phys * perf_ctl_scaling; + cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); + +- freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.min_freq = freq; + /* + * Cast the min P-state value retrieved via pstate_funcs.get_min() to + * the effective range of HWP performance levels. + */ +- cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, freq); ++ cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, cpu->pstate.min_freq); + } + + static bool turbo_is_disabled(void) +@@ -979,12 +971,10 @@ static int hybrid_get_cost(struct device *dev, unsigned long freq, + * capacity. Similarly, P-cores start to be populated when E-cores are + * utilized above 60% of the capacity. + */ +- if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_ATOM) { +- if (hybrid_has_l3(dev->id)) /* E-core */ +- *cost += 1; +- } else { /* P-core */ ++ if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_CORE) /* P-core */ + *cost += 2; +- } ++ else if (hybrid_has_l3(dev->id)) /* E-core */ ++ *cost += 1; + + return 0; + } +@@ -1185,6 +1175,22 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static void intel_pstate_update_freq_limits(struct cpudata *cpu) ++{ ++ int scaling = cpu->pstate.scaling; ++ unsigned int turbo_freq = cpu->pstate.turbo_pstate * scaling; ++ unsigned int max_freq = cpu->pstate.max_pstate * scaling; ++ int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; ++ ++ if (scaling != perf_ctl_scaling) { ++ turbo_freq = rounddown(turbo_freq, perf_ctl_scaling); ++ max_freq = rounddown(max_freq, perf_ctl_scaling); ++ } ++ ++ cpu->pstate.turbo_freq = turbo_freq; ++ cpu->pstate.max_freq = max_freq; ++} ++ + static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + { + u64 cap; +@@ -1197,20 +1203,8 @@ static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + + static void intel_pstate_get_hwp_cap(struct cpudata *cpu) + { +- int scaling = cpu->pstate.scaling; +- + __intel_pstate_get_hwp_cap(cpu); +- +- cpu->pstate.max_freq = cpu->pstate.max_pstate * scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * scaling; +- if (scaling != cpu->pstate.perf_ctl_scaling) { +- int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; +- +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_freq, +- perf_ctl_scaling); +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_freq, +- perf_ctl_scaling); +- } ++ intel_pstate_update_freq_limits(cpu); + } + + static void hybrid_update_capacity(struct cpudata *cpu) +@@ -2299,33 +2293,16 @@ static int hwp_get_cpu_scaling(int cpu) + return intel_pstate_cppc_get_scaling(cpu); + } + +-static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) +-{ +- trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); +- cpu->pstate.current_pstate = pstate; +- /* +- * Generally, there is no guarantee that this code will always run on +- * the CPU being updated, so force the register update to run on the +- * right CPU. +- */ +- wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, +- pstate_funcs.get_val(cpu, pstate)); +-} +- +-static void intel_pstate_set_min_pstate(struct cpudata *cpu) +-{ +- intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); +-} +- + static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + { + int perf_ctl_scaling = pstate_funcs.get_scaling(); + + cpu->pstate.max_pstate_physical = pstate_funcs.get_max_physical(cpu->cpu); + cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); ++ cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; + cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; + +- if (hwp_active && !hwp_mode_bdw) { ++ if (hwp_active) { + __intel_pstate_get_hwp_cap(cpu); + + if (pstate_funcs.get_cpu_scaling) { +@@ -2345,11 +2322,7 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); + } + +- if (cpu->pstate.scaling == perf_ctl_scaling) { +- cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.max_freq = cpu->pstate.max_pstate * perf_ctl_scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * perf_ctl_scaling; +- } ++ intel_pstate_update_freq_limits(cpu); + + if (pstate_funcs.get_aperf_mperf_shift) + cpu->aperf_mperf_shift = pstate_funcs.get_aperf_mperf_shift(); +@@ -2882,8 +2855,22 @@ static void intel_pstate_update_perf_limits(struct cpudata *cpu, + cpu->min_perf_ratio); + } + ++static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) ++{ ++ trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); ++ cpu->pstate.current_pstate = pstate; ++ /* ++ * Generally, there is no guarantee that this code will always run on ++ * the CPU being updated, so force the register update to run on the ++ * right CPU. ++ */ ++ wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, ++ pstate_funcs.get_val(cpu, pstate)); ++} ++ + static int intel_pstate_set_policy(struct cpufreq_policy *policy) + { ++ unsigned int freq = policy->min; + struct cpudata *cpu; + + if (!policy->cpuinfo.max_freq) +@@ -2899,7 +2886,23 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + + intel_pstate_update_perf_limits(cpu, policy->min, policy->max); + +- if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ if (hwp_active) { ++ /* ++ * The active mode only requires an update util hook if HWP ++ * boost is used and the policy is not "performance". ++ */ ++ if (hwp_boost && cpu->policy != CPUFREQ_POLICY_PERFORMANCE) { ++ intel_pstate_set_update_util_hook(policy->cpu); ++ } else { ++ intel_pstate_clear_update_util_hook(policy->cpu); ++ if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ freq = cpu->max_perf_ratio * cpu->pstate.scaling; ++ if (cpu->pstate.scaling != cpu->pstate.perf_ctl_scaling) ++ freq = rounddown(freq, cpu->pstate.perf_ctl_scaling); ++ } ++ } ++ intel_pstate_hwp_set(policy->cpu); ++ } else if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { + int pstate = max(cpu->pstate.min_pstate, cpu->max_perf_ratio); + + /* +@@ -2908,25 +2911,17 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + */ + intel_pstate_clear_update_util_hook(policy->cpu); + intel_pstate_set_pstate(cpu, pstate); ++ freq = pstate * cpu->pstate.scaling; + } else { + intel_pstate_set_update_util_hook(policy->cpu); + } +- +- if (hwp_active) { +- /* +- * When hwp_boost was active before and dynamically it +- * was turned off, in that case we need to clear the +- * update util hook. +- */ +- if (!hwp_boost) +- intel_pstate_clear_update_util_hook(policy->cpu); +- intel_pstate_hwp_set(policy->cpu); +- } + /* +- * policy->cur is never updated with the intel_pstate driver, but it +- * is used as a stale frequency value. So, keep it within limits. ++ * policy->cur is never updated in the intel_pstate driver, but it is ++ * used as a stale frequency value, so set it to reflect the actual ++ * requested P-state in the "performance" policy case and to the min ++ * otherwise. + */ +- policy->cur = policy->min; ++ policy->cur = freq; + + mutex_unlock(&intel_pstate_limits_lock); + +@@ -2969,6 +2964,11 @@ static int intel_pstate_verify_policy(struct cpufreq_policy_data *policy) + return 0; + } + ++static void intel_pstate_set_min_pstate(struct cpudata *cpu) ++{ ++ intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); ++} ++ + static int intel_cpufreq_cpu_offline(struct cpufreq_policy *policy) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3242,6 +3242,7 @@ static unsigned int intel_cpufreq_fast_switch(struct cpufreq_policy *policy, + static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3272,7 +3273,13 @@ static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + if (min_pstate > cpu->max_perf_ratio) + min_pstate = cpu->max_perf_ratio; + +- max_pstate = min(cap_pstate, cpu->max_perf_ratio); ++ max_pstate = cap_pstate; ++ if (max_perf < capacity) ++ max_pstate = DIV_ROUND_UP(cap_pstate * max_perf, capacity); ++ ++ if (max_pstate > cpu->max_perf_ratio) ++ max_pstate = cpu->max_perf_ratio; ++ + if (max_pstate < min_pstate) + max_pstate = min_pstate; + +@@ -3679,14 +3686,14 @@ static inline bool intel_pstate_has_acpi_ppc(void) { return false; } + static inline void intel_pstate_request_control_from_smm(void) {} + #endif /* CONFIG_ACPI */ + +-#define INTEL_PSTATE_HWP_BROADWELL 0x01 ++#define INTEL_PSTATE_HWP_NOT_HYBRID 0x01 + + #define X86_MATCH_HWP(vfm, hwp_mode) \ + X86_MATCH_VFM_FEATURE(vfm, X86_FEATURE_HWP, hwp_mode) + + static const struct x86_cpu_id hwp_support_ids[] __initconst = { +- X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_BROADWELL), +- X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_BROADWELL), ++ X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_NOT_HYBRID), ++ X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_NOT_HYBRID), + X86_MATCH_HWP(INTEL_ANY, 0), + {} + }; +@@ -3811,7 +3818,6 @@ static int __init intel_pstate_init(void) + + if (!no_hwp) { + hwp_active = true; +- hwp_mode_bdw = id->driver_data; + intel_pstate.attr = hwp_cpufreq_attrs; + intel_cpufreq.attr = hwp_cpufreq_attrs; + intel_cpufreq.flags |= CPUFREQ_NEED_UPDATE_LIMITS; +@@ -3819,7 +3825,8 @@ static int __init intel_pstate_init(void) + if (!default_driver) + default_driver = &intel_pstate; + +- pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; ++ if (!id->driver_data) ++ pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; + + goto hwp_cpu_matched; + } +diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c +--- a/drivers/idle/intel_idle.c ++++ b/drivers/idle/intel_idle.c +@@ -1780,6 +1780,7 @@ module_param_named(no_native, no_native, bool, 0444); + MODULE_PARM_DESC(no_native, "Ignore cpu specific (native) idle states in lieu of ACPI idle states"); + + static struct acpi_processor_power acpi_state_table __initdata; ++static bool acpi_lpi_available __initdata; + + /** + * intel_idle_cst_usable - Check if the _CST information can be used. +@@ -1804,18 +1805,37 @@ static bool __init intel_idle_cst_usable(void) + return true; + } + +-static bool __init intel_idle_acpi_cst_extract(void) ++static bool __init intel_idle_acpi_extract_lpi_cstates(void) + { + unsigned int cpu; + +- if (no_acpi) { +- pr_debug("Not allowed to use ACPI _CST\n"); +- return false; ++ for_each_possible_cpu(cpu) { ++ struct acpi_processor *pr; ++ ++ pr = per_cpu(processors, cpu); ++ if (!pr) ++ continue; ++ ++ if (acpi_processor_extract_lpi_info(pr->handle, ++ &acpi_state_table, true)) ++ continue; ++ ++ acpi_lpi_available = true; ++ return true; + } + +- for_each_possible_cpu(cpu) { +- struct acpi_processor *pr = per_cpu(processors, cpu); ++ pr_debug("No ACPI _LPI idle states\n"); ++ return false; ++} + ++static bool __init intel_idle_acpi_extract_cst_cstates(void) ++{ ++ unsigned int cpu; ++ ++ for_each_possible_cpu(cpu) { ++ struct acpi_processor *pr; ++ ++ pr = per_cpu(processors, cpu); + if (!pr) + continue; + +@@ -1827,18 +1847,96 @@ static bool __init intel_idle_acpi_cst_extract(void) + if (!intel_idle_cst_usable()) + continue; + +- if (!acpi_processor_claim_cst_control()) +- break; +- + return true; + } + +- acpi_state_table.count = 0; + pr_debug("ACPI _CST not found or not usable\n"); + return false; + } + +-static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) ++static bool __init intel_idle_acpi_extract_cstates(void) ++{ ++ if (intel_idle_acpi_extract_lpi_cstates()) ++ return true; ++ ++ if (intel_idle_acpi_extract_cst_cstates()) ++ return true; ++ ++ return false; ++} ++ ++static bool __init intel_idle_acpi_probe(void) ++{ ++ if (no_acpi) { ++ pr_debug("Not allowed to use ACPI for C-states extraction\n"); ++ return false; ++ } ++ ++ if (intel_idle_acpi_extract_cstates() && ++ acpi_processor_claim_cst_control()) ++ return true; ++ ++ acpi_state_table.count = 0; ++ return false; ++} ++ ++static void __init intel_idle_complete_state_init(struct cpuidle_state *state) ++{ ++ if (intel_idle_state_needs_timer_stop(state)) ++ state->flags |= CPUIDLE_FLAG_TIMER_STOP; ++ ++ state->enter = intel_idle; ++ state->enter_dead = intel_idle_enter_dead; ++ state->enter_s2idle = intel_idle_s2idle; ++} ++ ++static void __init intel_idle_init_cstates_acpi_lpi(struct cpuidle_driver *drv) ++{ ++ int index; ++ ++ for (index = 0; index < acpi_state_table.count; index++) { ++ struct acpi_lpi_state *lpi_state; ++ struct cpuidle_state *state; ++ ++ if (intel_idle_max_cstate_reached(index)) ++ break; ++ ++ lpi_state = &acpi_state_table.lpi_states[index]; ++ ++ state = &drv->states[drv->state_count++]; ++ ++ scnprintf(state->name, CPUIDLE_NAME_LEN, "C%d_LPI", index + 1); ++ strscpy(state->desc, lpi_state->desc, CPUIDLE_DESC_LEN); ++ state->exit_latency = lpi_state->wake_latency; ++ state->target_residency = lpi_state->min_residency; ++ state->flags = MWAIT2flg(lpi_state->address); ++ /* ++ * Assume that entering any of the idle states extracted from ++ * _LPI except for the first two will cause the TLB to be ++ * flushed and let the core call leave_mm() for them upfront ++ * to avoid unnecessary wakeups due to TLB shootdowns. ++ */ ++ if (index > 1) ++ state->flags |= CPUIDLE_FLAG_TLB_FLUSHED; ++ ++ if (disabled_states_mask & BIT(index + 1)) ++ state->flags |= CPUIDLE_FLAG_OFF; ++ ++ intel_idle_complete_state_init(state); ++ ++ pr_info("%s: MWAIT hint 0x%x\n", state->name, flg2MWAIT(state->flags)); ++ } ++ ++ /* ++ * Assume the first idle state in the table to be C1 and if any deeper ++ * idle states are exposed while X86_FEATURE_NONSTOP_TSC is unset, mark ++ * the TSC as unstable. ++ */ ++ if (index > 1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) ++ mark_tsc_unstable("TSC halts in idle"); ++} ++ ++static void __init intel_idle_init_cstates_acpi_cst(struct cpuidle_driver *drv) + { + int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + +@@ -1880,49 +1978,76 @@ static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) + if (disabled_states_mask & BIT(cstate)) + state->flags |= CPUIDLE_FLAG_OFF; + +- if (intel_idle_state_needs_timer_stop(state)) +- state->flags |= CPUIDLE_FLAG_TIMER_STOP; +- + if (cx->type > ACPI_STATE_C1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) + mark_tsc_unstable("TSC halts in idle"); + +- state->enter = intel_idle; +- state->enter_dead = intel_idle_enter_dead; +- state->enter_s2idle = intel_idle_s2idle; ++ intel_idle_complete_state_init(state); + } + } + +-static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) ++static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) + { +- int cstate, limit; ++ if (acpi_lpi_available) ++ intel_idle_init_cstates_acpi_lpi(drv); ++ else ++ intel_idle_init_cstates_acpi_cst(drv); ++} + +- /* +- * If there are no _CST C-states, do not disable any C-states by +- * default. +- */ +- if (!acpi_state_table.count) +- return false; ++static bool __init intel_idle_acpi_hint_match(unsigned int flags, u32 acpi_hint, ++ u32 table_hint) ++{ ++ if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { ++ acpi_hint &= ~MWAIT_SUBSTATE_MASK; ++ table_hint &= ~MWAIT_SUBSTATE_MASK; ++ } ++ return acpi_hint == table_hint; ++} ++ ++static bool __init intel_idle_off_by_default_lpi(unsigned int flags, u32 mwait_hint) ++{ ++ int index; ++ ++ for (index = 0; index < acpi_state_table.count; index++) { ++ u32 acpi_hint = acpi_state_table.lpi_states[index].address; ++ ++ if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) ++ return false; ++ } ++ return true; ++} ++ ++static bool __init intel_idle_off_by_default_cst(unsigned int flags, u32 mwait_hint) ++{ ++ int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + +- limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + /* + * If limit > 0, intel_idle_cst_usable() has returned 'true', so all of + * the interesting states are ACPI_CSTATE_FFH. + */ + for (cstate = 1; cstate < limit; cstate++) { + u32 acpi_hint = acpi_state_table.states[cstate].address; +- u32 table_hint = mwait_hint; + +- if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { +- acpi_hint &= ~MWAIT_SUBSTATE_MASK; +- table_hint &= ~MWAIT_SUBSTATE_MASK; +- } +- +- if (acpi_hint == table_hint) ++ if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) + return false; + } + return true; + } + ++static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) ++{ ++ /* ++ * If there is no C-states information in the ACPI tables, do not ++ * disable any C-states by default. ++ */ ++ if (!acpi_state_table.count) ++ return false; ++ ++ if (acpi_lpi_available) ++ return intel_idle_off_by_default_lpi(flags, mwait_hint); ++ ++ return intel_idle_off_by_default_cst(flags, mwait_hint); ++} ++ + static inline bool ignore_native(void) + { + return no_native && !no_acpi; +@@ -1930,7 +2055,7 @@ static inline bool ignore_native(void) + #else /* !CONFIG_ACPI_PROCESSOR_CSTATE */ + #define force_use_acpi (false) + +-static inline bool intel_idle_acpi_cst_extract(void) { return false; } ++static inline bool intel_idle_acpi_probe(void) { return false; } + static inline void intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) { } + static inline bool intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) + { +@@ -2745,7 +2870,7 @@ static int __init intel_idle_init(void) + if (icpu) { + if (icpu->state_table) + cpuidle_state_table = icpu->state_table; +- else if (!intel_idle_acpi_cst_extract()) ++ else if (!intel_idle_acpi_probe()) + return -ENODEV; + + auto_demotion_disable_flags = icpu->auto_demotion_disable_flags; +@@ -2754,8 +2879,8 @@ static int __init intel_idle_init(void) + if (icpu->c1_demotion_supported) + c1_demotion_supported = true; + if (icpu->use_acpi || force_use_acpi) +- intel_idle_acpi_cst_extract(); +- } else if (!intel_idle_acpi_cst_extract()) { ++ intel_idle_acpi_probe(); ++ } else if (!intel_idle_acpi_probe()) { + return -ENODEV; + } + +diff --git a/drivers/opp/core.c b/drivers/opp/core.c +--- a/drivers/opp/core.c ++++ b/drivers/opp/core.c +@@ -1581,8 +1581,6 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) + { +- int ret; +- + /* + * Return early if we don't need to get clk or we have already done it + * earlier. +@@ -1591,39 +1589,35 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + opp_table->clks) + return opp_table; + +- /* Find clk for the device */ +- opp_table->clk = clk_get(dev, NULL); ++ /* ++ * There are few platforms which don't want the OPP core to manage ++ * device's clock settings. In such cases neither the platform ++ * provides the clks explicitly to us, nor the DT contains a valid ++ * clk entry. The OPP nodes in DT may still contain "opp-hz" property ++ * though, which we need to parse and allow the platform to find an ++ * OPP based on freq later on. ++ * ++ * This is a simple solution to take care of such corner cases, i.e. ++ * make the clk_count 1, which lets us allocate space for frequency ++ * in opp->rates and also parse the entries in DT. Use ++ * clk_get_optional() instead of clk_get() so opp_table->clk stays ++ * NULL for such devices, instead of holding an ERR_PTR(-ENOENT) that ++ * consumers must remember to special-case. ++ */ ++ opp_table->clk = clk_get_optional(dev, NULL); + +- ret = PTR_ERR_OR_ZERO(opp_table->clk); +- if (!ret) { ++ if (IS_ERR(opp_table->clk)) { ++ dev_pm_opp_put_opp_table(opp_table); ++ dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); ++ return ERR_CAST(opp_table->clk); ++ } ++ ++ if (opp_table->clk) + opp_table->config_clks = _opp_config_clk_single; +- opp_table->clk_count = 1; +- return opp_table; +- } + +- if (ret == -ENOENT) { +- /* +- * There are few platforms which don't want the OPP core to +- * manage device's clock settings. In such cases neither the +- * platform provides the clks explicitly to us, nor the DT +- * contains a valid clk entry. The OPP nodes in DT may still +- * contain "opp-hz" property though, which we need to parse and +- * allow the platform to find an OPP based on freq later on. +- * +- * This is a simple solution to take care of such corner cases, +- * i.e. make the clk_count 1, which lets us allocate space for +- * frequency in opp->rates and also parse the entries in DT. +- */ +- opp_table->clk_count = 1; ++ opp_table->clk_count = 1; + +- dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); +- return opp_table; +- } +- +- dev_pm_opp_put_opp_table(opp_table); +- dev_err_probe(dev, ret, "Couldn't find clock\n"); +- +- return ERR_PTR(ret); ++ return opp_table; + } + + /* +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -315,6 +315,19 @@ static inline int acpi_processor_evaluate_cst(acpi_handle handle, u32 cpu, + } + #endif + ++#ifdef CONFIG_ACPI_PROCESSOR_IDLE ++int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict); ++#else ++static inline int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict) ++{ ++ return -ENODEV; ++} ++#endif ++ + #ifdef CONFIG_ACPI_HOTPLUG_CPU + /* Arch dependent functions for cpu hotplug support */ + int acpi_map_cpu(acpi_handle handle, phys_cpuid_t physid, u32 acpi_id, +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -379,6 +379,7 @@ struct cpufreq_driver { + void (*adjust_perf)(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + + /* +@@ -624,6 +625,7 @@ unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + bool cpufreq_driver_has_adjust_perf(void); + int cpufreq_driver_target(struct cpufreq_policy *policy, +diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig +--- a/kernel/power/Kconfig ++++ b/kernel/power/Kconfig +@@ -268,6 +268,16 @@ config DPM_WATCHDOG + captured in pstore device for inspection in subsequent + boot session. + ++config DPM_WATCHDOG_ENABLED ++ bool "Enable DPM watchdog by default" ++ depends on DPM_WATCHDOG ++ default y ++ help ++ If you say Y here, the DPM watchdog will be enabled by default. ++ If you say N, it will be compiled in but disabled. It can be ++ enabled at boot time via the "pm.dpm_watchdog_enabled" kernel ++ parameter or at runtime via sysfs. ++ + config DPM_WATCHDOG_TIMEOUT + int "Watchdog timeout to panic in seconds" + range 1 120 +diff --git a/kernel/power/swap.c b/kernel/power/swap.c +--- a/kernel/power/swap.c ++++ b/kernel/power/swap.c +@@ -430,19 +430,22 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + + if (!handle->cur) + return -EINVAL; +- offset = alloc_swapdev_block(root_swap); +- error = write_page(buf, offset, hb); +- if (error) +- return error; +- handle->cur->entries[handle->k++] = offset; ++ ++ /* ++ * If the current map page is full, allocate and link next one first. ++ * Delaying this until here avoids writing an empty swap map page when ++ * the image size is an exact MAP_PAGE_ENTRIES multiple. ++ */ + if (handle->k >= MAP_PAGE_ENTRIES) { + offset = alloc_swapdev_block(root_swap); + if (!offset) + return -ENOSPC; ++ + handle->cur->next_swap = offset; + error = write_page(handle->cur, handle->cur_swap, hb); + if (error) +- goto out; ++ return error; ++ + clear_page(handle->cur); + handle->cur_swap = offset; + handle->k = 0; +@@ -450,7 +453,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + if (hb && low_free_pages() <= handle->reqd_free_pages) { + error = hib_wait_io(hb); + if (error) +- goto out; ++ return error; + /* + * Recalculate the number of required free pages, to + * make sure we never take more than half. +@@ -458,14 +461,21 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + handle->reqd_free_pages = reqd_free_pages(); + } + } +- out: +- return error; ++ ++ offset = alloc_swapdev_block(root_swap); ++ error = write_page(buf, offset, hb); ++ if (error) ++ return error; ++ handle->cur->entries[handle->k++] = offset; ++ return 0; + } + + static int flush_swap_writer(struct swap_map_handle *handle) + { +- if (handle->cur && handle->cur_swap) ++ if (handle->cur && handle->cur_swap && handle->k) + return write_page(handle->cur, handle->cur_swap, NULL); ++ else if (handle->cur && handle->cur_swap) ++ return 0; + else + return -EINVAL; + } +@@ -739,7 +749,7 @@ static int save_compressed_image(struct swap_map_handle *handle, + + data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR_OR_NULL(data[thr].cc)) { +- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); ++ pr_err("Could not allocate comp stream %pe\n", data[thr].cc); + ret = -EFAULT; + goto out_clean; + } +@@ -1243,7 +1253,7 @@ static int load_compressed_image(struct swap_map_handle *handle, + + data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR_OR_NULL(data[thr].cc)) { +- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); ++ pr_err("Could not allocate comp stream %pe\n", data[thr].cc); + ret = -EFAULT; + goto out_clean; + } +diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c +--- a/kernel/sched/cpufreq_schedutil.c ++++ b/kernel/sched/cpufreq_schedutil.c +@@ -50,6 +50,7 @@ struct sugov_cpu { + + unsigned long util; + unsigned long bw_min; ++ unsigned long bw_max; + + /* The field below is for single-CPU policies only: */ + #ifdef CONFIG_NO_HZ_COMMON +@@ -243,6 +244,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) + util = effective_cpu_util(sg_cpu->cpu, util, &min, &max); + util = max(util, boost); + sg_cpu->bw_min = min; ++ sg_cpu->bw_max = max; + sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max); + } + +@@ -495,7 +497,7 @@ static void sugov_update_single_perf(struct update_util_data *hook, u64 time, + sg_cpu->util = prev_util; + + cpufreq_driver_adjust_perf(sg_policy->policy, sg_cpu->bw_min, +- sg_cpu->util, max_cap); ++ sg_cpu->util, sg_cpu->bw_max, max_cap); + + sg_policy->need_freq_update = false; + sg_policy->last_freq_update_time = time; +@@ -601,7 +603,7 @@ static ssize_t rate_limit_us_show(struct gov_attr_set *attr_set, char *buf) + { + struct sugov_tunables *tunables = to_sugov_tunables(attr_set); + +- return sprintf(buf, "%u\n", tunables->rate_limit_us); ++ return sysfs_emit(buf, "%u\n", tunables->rate_limit_us); + } + + static ssize_t +diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs +--- a/rust/kernel/cpufreq.rs ++++ b/rust/kernel/cpufreq.rs +@@ -797,7 +797,13 @@ pub trait Driver { + } + + /// Driver's `adjust_perf` callback. +- fn adjust_perf(_policy: &mut Policy, _min_perf: usize, _target_perf: usize, _capacity: usize) { ++ fn adjust_perf( ++ _policy: &mut Policy, ++ _min_perf: usize, ++ _target_perf: usize, ++ _max_perf: usize, ++ _capacity: usize, ++ ) { + build_error!(VTABLE_DEFAULT_ERROR) + } + +@@ -1270,12 +1276,13 @@ impl Registration { + ptr: *mut bindings::cpufreq_policy, + min_perf: c_ulong, + target_perf: c_ulong, ++ max_perf: c_ulong, + capacity: c_ulong, + ) { + // SAFETY: The `ptr` is guaranteed to be valid by the contract with the C code for the + // lifetime of `policy`. + let policy = unsafe { Policy::from_raw_mut(ptr) }; +- T::adjust_perf(policy, min_perf, target_perf, capacity); ++ T::adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /// Driver's `get_intermediate` callback. diff --git a/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..8ffea460ab29b339aa65fc4713ccd786469c7fa6 GIT binary patch literal 594 zcmV-Y0?)L`dEiO4 zc1;c(q$#)P4d%(U0Q||)h=Gy^_9$OnCoz4H0u888(4QeXk&cy|AyO=Yj+H%>Hg~t7 zG;nDG>AA{K@M(fDq^e_bqzcYK;DGd-8`f&T-PX~1Y*~^dTFI=S4C7E~t?GHk&+bsv ztY50N9vSu96+-?cX{c+!M1GPGF$nc+PcMM@t(U2;yH?AyVv%!uOD2+yD>g@utQ}D? z<8to_nZz6YT)0kq5oj~qCs0NMGlAivx1?`p7@)!v?&F9G{M>)y-V#&wz{puCTa*Dz zPN|;{Zpe-1>!89+EYs;U40*Z(mQy9nqh6+=^4;%J_zeWlSN!v@ePN3!iwq3EHv&N? z`!;u7G9@}GO$E@PKL0R-JCxzM)?EnA( literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch b/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch new file mode 100644 index 0000000..ed2d0f5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch @@ -0,0 +1,104 @@ +diff --git a/drivers/opp/core.c b/drivers/opp/core.c +--- a/drivers/opp/core.c ++++ b/drivers/opp/core.c +@@ -453,8 +453,8 @@ int dev_pm_opp_get_opp_count(struct device *dev) + _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_dbg(dev, "%s: OPP table not found (%ld)\n", +- __func__, PTR_ERR(opp_table)); ++ dev_dbg(dev, "%s: OPP table not found (%pe)\n", ++ __func__, opp_table); + return PTR_ERR(opp_table); + } + +@@ -611,8 +611,8 @@ _find_key(struct device *dev, unsigned long *key, int index, bool available, + _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, ++ opp_table); + return ERR_CAST(opp_table); + } + +@@ -722,8 +722,8 @@ struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev, + struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, ++ opp_table); + return ERR_CAST(opp_table); + } + +@@ -1036,8 +1036,8 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg, + + /* Regulator not available for device */ + if (IS_ERR(reg)) { +- dev_dbg(dev, "%s: regulator not available: %ld\n", __func__, +- PTR_ERR(reg)); ++ dev_dbg(dev, "%s: regulator not available: %pe\n", __func__, ++ reg); + return 0; + } + +@@ -1448,8 +1448,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) + temp_freq = freq; + opp = _find_freq_ceil(opp_table, &temp_freq); + if (IS_ERR(opp)) { +- dev_err(dev, "%s: failed to find OPP for freq %lu (%ld)\n", +- __func__, freq, PTR_ERR(opp)); ++ dev_err(dev, "%s: failed to find OPP for freq %lu (%pe)\n", ++ __func__, freq, opp); + return PTR_ERR(opp); + } + +@@ -1581,6 +1581,8 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) + { ++ int ret; ++ + /* + * Return early if we don't need to get clk or we have already done it + * earlier. +@@ -1607,9 +1609,9 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + opp_table->clk = clk_get_optional(dev, NULL); + + if (IS_ERR(opp_table->clk)) { ++ ret = dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); + dev_pm_opp_put_opp_table(opp_table); +- dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); +- return ERR_CAST(opp_table->clk); ++ return ERR_PTR(ret); + } + + if (opp_table->clk) +@@ -2869,8 +2871,8 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, + struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; + + if (IS_ERR(opp_table)) { +- dev_warn(dev, "%s: Device OPP not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_warn(dev, "%s: Device OPP not found (%pe)\n", __func__, ++ opp_table); + return PTR_ERR(opp_table); + } + +diff --git a/drivers/opp/of.c b/drivers/opp/of.c +--- a/drivers/opp/of.c ++++ b/drivers/opp/of.c +@@ -1345,8 +1345,8 @@ int of_get_required_opp_performance_state(struct device_node *np, int index) + _find_table_of_opp_np(required_np); + + if (IS_ERR(opp_table)) { +- pr_err("%s: Failed to find required OPP table %pOF: %ld\n", +- __func__, np, PTR_ERR(opp_table)); ++ pr_err("%s: Failed to find required OPP table %pOF: %pe\n", ++ __func__, np, opp_table); + return PTR_ERR(opp_table); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..b6284eb6e7f48c7e8139555ff5c8ddd342cae2f2 GIT binary patch literal 594 zcmV-Y00Fs(C2>7iqGxR>hGz&3f zq=Z<8&dO)H_BbC4zrlG4-p6GmI(PJ^11^ZSMBz~h_Kd>7SD%g1@BPryrexaVpo^C-Oagek!%Hm zyzAQ{0GJmHAs<^9v`M(wLsAh$+1Bn~3+XG>hkzr3PQt$Sl~`)c7=ZQNnjbRXm??&Y z)d(^&Ys3?G@4HzrwH6G39AjIW5tV-f^-0+_3mVwHdLv1WZ_E>yL8E3~%5(>;ZcT#{ zZ})G#RaXj&9`-L$_=XCwLW;z(7sAr~;2ko_lA1G4DN;g%%DW`*XO*ozQ*ftdY)ayg z@*C>2?6=gHM;leNGX4dU|N7#q8CI|}rEMVov%Gl#e^Ruvz_&{rWj(j^i*5a7w$Hvr zq#V}}E%E1}iNr>uabzHENX8u!K5ItkX`GrmKRIUz@NR}S5B}^_0Nrack8$f1;wP98 z#%v?>+3fcP$GS4V+94 zHh6w)@=T)!5ubMqNBt&IQBr`M9#97yO8WVJ=Dp_HH!kwQTirS_R7@gEe*Fv^H*H)_ z%&^|{_g!je!{AHsalJ{37(lj*at*fvBO7R<0y{oN^KmMi6^!X#dO z-}CV8!*NYsf)pH3b3e}7T|A@Ku=6CKk#?^A6J9mo5BE+z7r%Sw&ci-MTyvAFQ7u?)dS8U5rjym25M3Jv4Nj0egK~gCnN%WBprelated_cpus); + max_freq = arch_scale_freq_ref(cpu); +- if (!max_freq) +- max_freq = policy->cpuinfo.max_freq; ++ if (!max_freq && cpufreq_driver->scale_freq_ref) ++ max_freq = cpufreq_driver->scale_freq_ref(policy); + + capped_freq = policy->max; + +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -1175,6 +1175,14 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static unsigned int intel_pstate_scale_freq_ref(struct cpufreq_policy *policy) ++{ ++ if (READ_ONCE(all_cpu_data[policy->cpu]->capacity_perf)) ++ return policy->cpuinfo.max_freq; ++ ++ return 0; ++} ++ + static void intel_pstate_update_freq_limits(struct cpudata *cpu) + { + int scaling = cpu->pstate.scaling; +@@ -3092,6 +3100,7 @@ static struct cpufreq_driver intel_pstate = { + .offline = intel_pstate_cpu_offline, + .online = intel_pstate_cpu_online, + .update_limits = intel_pstate_update_limits, ++ .scale_freq_ref = intel_pstate_scale_freq_ref, + .name = "intel_pstate", + }; + +@@ -3415,6 +3424,7 @@ static struct cpufreq_driver intel_cpufreq = { + .suspend = intel_cpufreq_suspend, + .resume = intel_pstate_resume, + .update_limits = intel_pstate_update_limits, ++ .scale_freq_ref = intel_pstate_scale_freq_ref, + .name = "intel_cpufreq", + }; + +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -420,6 +420,9 @@ struct cpufreq_driver { + /* Will be called after the driver is fully initialized */ + void (*ready)(struct cpufreq_policy *policy); + ++ /* Return the capacity reference frequency for policy. */ ++ unsigned int (*scale_freq_ref)(struct cpufreq_policy *policy); ++ + struct freq_attr **attr; + + /* platform specific boost support code */ diff --git a/pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..840091517c0ba104b7db6202ab11b50f6304bac6 GIT binary patch literal 594 zcmV-Y0`JXgnQCfGM8$2ly$8O*Pu4ZA*jBd=9Qw@De9CE3xczI;e2{^i}wh$@7_ zw3>U+A4A-&-W;%7Back2Z9sU1>K>LBkSW_j= zBGtbKFr?B|Xx3#zYe+MB?_so`C$$#!>Me$44iN6Z5T4uT#t3?*A7QS=t1hm2fCG6E(HTe6F zG8U0PBBs?RW(+s_#Mpv5&J_Sgxrk(N;=V*Llb`m6$bB15(_>N1c-<~YI!a8KY<}i4 zyag_@OI7b`BPD>w^^qI=%CWOf{`lO$sP-R1DCyOLAs$MKa{E}fPD zHNch-(%1&q(zj5OcJem@6x5g8*=PF7UI?q+@%$R|SC3g?!lkRcm^fcotNyA@^fLA^ gK=U2;ju(1$x@v4Tji7EgbD_@_duA8Or1INbKHPH|4FCWD literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch b/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch new file mode 100644 index 0000000..1c5c5b3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch @@ -0,0 +1,95 @@ +diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h +--- a/arch/x86/include/asm/cpufeatures.h ++++ b/arch/x86/include/asm/cpufeatures.h +@@ -471,6 +471,8 @@ + #define X86_FEATURE_AUTOIBRS (20*32+ 8) /* Automatic IBRS */ + #define X86_FEATURE_NO_SMM_CTL_MSR (20*32+ 9) /* SMM_CTL MSR is not present */ + ++#define X86_FEATURE_L2_TLB_SIZE_X32 (20*32+14) /* L2 TLB sizes are encoded as multiples of 32 */ ++ + #define X86_FEATURE_GP_ON_USER_CPUID (20*32+17) /* User CPUID faulting */ + + #define X86_FEATURE_PREFETCHI (20*32+20) /* Prefetch Data/Instruction to Cache Level */ +diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c +--- a/arch/x86/kernel/cpu/amd.c ++++ b/arch/x86/kernel/cpu/amd.c +@@ -1190,7 +1190,7 @@ static unsigned int amd_size_cache(struct cpuinfo_x86 *c, unsigned int size) + + static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + { +- u32 ebx, eax, ecx, edx; ++ u32 ebx, eax, ecx, edx, shift, tmp; + u16 mask = 0xfff; + + if (c->x86 < 0xf) +@@ -1199,10 +1199,12 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + if (c->extended_cpuid_level < 0x80000006) + return; + ++ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; ++ + cpuid(0x80000006, &eax, &ebx, &ecx, &edx); + +- tlb_lld_4k = (ebx >> 16) & mask; +- tlb_lli_4k = ebx & mask; ++ tlb_lld_4k = ((ebx >> 16) & mask) << shift; ++ tlb_lli_4k = (ebx & mask) << shift; + + /* + * K8 doesn't have 2M/4M entries in the L2 TLB so read out the L1 TLB +@@ -1214,16 +1216,18 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + } + + /* Handle DTLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ +- if (!((eax >> 16) & mask)) ++ tmp = ((eax >> 16) & mask) << shift; ++ if (!tmp) + tlb_lld_2m = (cpuid_eax(0x80000005) >> 16) & 0xff; + else +- tlb_lld_2m = (eax >> 16) & mask; ++ tlb_lld_2m = tmp; + + /* a 4M entry uses two 2M entries */ + tlb_lld_4m = tlb_lld_2m >> 1; + + /* Handle ITLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ +- if (!(eax & mask)) { ++ tmp = (eax & mask) << shift; ++ if (!tmp) { + /* Erratum 658 */ + if (c->x86 == 0x15 && c->x86_model <= 0x1f) { + tlb_lli_2m = 1024; +@@ -1231,8 +1235,9 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + cpuid(0x80000005, &eax, &ebx, &ecx, &edx); + tlb_lli_2m = eax & 0xff; + } +- } else +- tlb_lli_2m = eax & mask; ++ } else { ++ tlb_lli_2m = tmp; ++ } + + tlb_lli_4m = tlb_lli_2m >> 1; + +diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c +--- a/arch/x86/kernel/cpu/common.c ++++ b/arch/x86/kernel/cpu/common.c +@@ -857,7 +857,7 @@ static void get_model_name(struct cpuinfo_x86 *c) + + void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) + { +- unsigned int n, dummy, ebx, ecx, edx, l2size; ++ unsigned int n, dummy, ebx, ecx, edx, l2size, shift __maybe_unused; + + n = c->extended_cpuid_level; + +@@ -877,7 +877,9 @@ void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) + l2size = ecx >> 16; + + #ifdef CONFIG_X86_64 ++ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; + c->x86_tlbsize += ((ebx >> 16) & 0xfff) + (ebx & 0xfff); ++ c->x86_tlbsize <<= shift; + #else + /* do processor-specific cache resizing */ + if (this_cpu->legacy_cache_size) diff --git a/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..aa440e0b4a3cb0ac7e4f8b90495ec0ba15d24f98 GIT binary patch literal 594 zcmV-Y0*W?+oyfq=l}(DpY=4;cWz zoIWj#pmd;t4vT4?B617o>6DJ4oA20vz=S3>|4;DQ@j4pUF!f?JmCr>*401Tb+Eiq1 zf5QY+$Mr8-%C`+dta%6M>b6bVK=msWNM;6xCbdY|1D;t3~2}8 zqHvaLRZ?Q+3H9JA0FVaRtyPBYOIeYMZ8XaD>;(?0v;R^s8a+y%!BYXTCp;u`j3Plk zWImi+2MJ&Pg*LU={7K~1&{ik~?oiN5=#!3!amr*;ANvi6RJ#iWKIaDq*~ZIOB*{6xPadRzQu-kZqfL&VO6lock + * class->lock + * zspage->lock ++ * ++ * When ZS_OBJ_CLASS_BITS > 0, zs_free() skips pool->lock; it picks ++ * the size_class from obj's encoded class_idx and serializes against ++ * page migration via class->lock. + */ + + #include +@@ -67,8 +71,8 @@ + #define MAX_POSSIBLE_PHYSMEM_BITS MAX_PHYSMEM_BITS + #else + /* +- * If this definition of MAX_PHYSMEM_BITS is used, OBJ_INDEX_BITS will just +- * be PAGE_SHIFT ++ * If this definition of MAX_PHYSMEM_BITS is used, ZS_OBJ_PFN_SHIFT will ++ * just be PAGE_SHIFT + */ + #define MAX_POSSIBLE_PHYSMEM_BITS BITS_PER_LONG + #endif +@@ -88,8 +92,23 @@ + #define OBJ_TAG_BITS 1 + #define OBJ_TAG_MASK OBJ_ALLOCATED_TAG + +-#define OBJ_INDEX_BITS (BITS_PER_LONG - _PFN_BITS) +-#define OBJ_INDEX_MASK ((_AC(1, UL) << OBJ_INDEX_BITS) - 1) ++/* ++ * obj is encoded as [PFN | class_idx | obj_idx] within an unsigned long: ++ * ++ * |<-- _PFN_BITS -->|<-- ZS_OBJ_CLASS_BITS -->|<-- ZS_OBJ_IDX_BITS -->| ++ * +-----------------+-------------------------+-----------------------+ ++ * | PFN | class_idx | obj_idx | ++ * +-----------------+-------------------------+-----------------------+ ++ * MSB ^ LSB ++ * | ++ * +-- ZS_OBJ_PFN_SHIFT ++ * ++ * Encoding class_idx into obj lets zs_free() locate the size_class ++ * without holding pool->lock; class_idx is invariant across page ++ * migration (only PFN changes), so a lockless read of the obj value ++ * always yields a valid class_idx. ++ */ ++#define ZS_OBJ_PFN_SHIFT (BITS_PER_LONG - _PFN_BITS) + + #define HUGE_BITS 1 + #define FULLNESS_BITS 4 +@@ -98,9 +117,61 @@ + + #define ZS_MAX_PAGES_PER_ZSPAGE (_AC(CONFIG_ZSMALLOC_CHAIN_SIZE, UL)) + ++/* ++ * Bits to index a page within a zspage = ceil(log2(ZS_MAX_PAGES_PER_ZSPAGE)). ++ * Computed at preprocessor time, for use in #if below. Kconfig ++ * restricts ZSMALLOC_CHAIN_SIZE to [4, 16]. ++ */ ++#if ZS_MAX_PAGES_PER_ZSPAGE <= 4 ++#define ZS_PAGES_PER_ZSPAGE_BITS 2 ++#elif ZS_MAX_PAGES_PER_ZSPAGE <= 8 ++#define ZS_PAGES_PER_ZSPAGE_BITS 3 ++#elif ZS_MAX_PAGES_PER_ZSPAGE <= 16 ++#define ZS_PAGES_PER_ZSPAGE_BITS 4 ++#else ++#error "ZSMALLOC_CHAIN_SIZE out of expected range [4,16]" ++#endif ++ ++/* ++ * Bits to index an object within a single PAGE_SIZE at the smallest ++ * possible object size: log2(PAGE_SIZE / 32) = PAGE_SHIFT - 5. ++ * 32 is the hard floor of ZS_MIN_ALLOC_SIZE. ++ */ ++#define ZS_OBJS_PER_PAGE_BITS (PAGE_SHIFT - 5) ++ ++/* ++ * Bits to index any object in the densest possible zspage. Below this, ++ * ZS_MIN_ALLOC_SIZE is auto-raised by the MAX(32, ...) formula -- still ++ * correct, but objects are coarser. ++ */ ++#define ZS_OBJS_PER_ZSPAGE_BITS \ ++ (ZS_PAGES_PER_ZSPAGE_BITS + ZS_OBJS_PER_PAGE_BITS) ++ ++/* ++ * Encode class_idx only when obj has spare bits; otherwise ++ * ZS_OBJ_CLASS_BITS folds to 0 (32-bit, or 64-bit UML/fallback). ++ */ ++#if BITS_PER_LONG >= 64 && \ ++ ZS_OBJ_PFN_SHIFT >= (CLASS_BITS + 1) + ZS_OBJS_PER_ZSPAGE_BITS ++#define ZS_OBJ_CLASS_BITS (CLASS_BITS + 1) ++#else ++#define ZS_OBJ_CLASS_BITS 0 ++#endif ++#define ZS_OBJ_CLASS_MASK ((_AC(1, UL) << ZS_OBJ_CLASS_BITS) - 1) ++ ++#define ZS_OBJ_IDX_BITS (ZS_OBJ_PFN_SHIFT - ZS_OBJ_CLASS_BITS) ++#define ZS_OBJ_IDX_MASK ((_AC(1, UL) << ZS_OBJ_IDX_BITS) - 1) ++ ++/* ++ * Belt-and-suspenders: the #if above already guarantees this when ++ * class_idx is enabled. Catches future tweaks that bypass it. ++ */ ++static_assert(ZS_OBJ_IDX_BITS >= ZS_PAGES_PER_ZSPAGE_BITS, ++ "zsmalloc: ZS_MIN_ALLOC_SIZE would exceed ZS_MAX_ALLOC_SIZE"); ++ + /* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */ + #define ZS_MIN_ALLOC_SIZE \ +- MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> OBJ_INDEX_BITS)) ++ MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> ZS_OBJ_IDX_BITS)) + /* each chunk includes extra space to keep handle */ + #define ZS_MAX_ALLOC_SIZE PAGE_SIZE + +@@ -396,10 +467,13 @@ static void cache_free_zspage(struct zspage *zspage) + kmem_cache_free(zspage_cachep, zspage); + } + +-/* class->lock(which owns the handle) synchronizes races */ ++/* ++ * Pairs with READ_ONCE() in handle_to_obj(): zs_free() may read the ++ * handle locklessly, so prevent store tearing here. ++ */ + static void record_obj(unsigned long handle, unsigned long obj) + { +- *(unsigned long *)handle = obj; ++ WRITE_ONCE(*(unsigned long *)handle, obj); + } + + static inline bool __maybe_unused is_first_zpdesc(struct zpdesc *zpdesc) +@@ -725,33 +799,36 @@ static struct zpdesc *get_next_zpdesc(struct zpdesc *zpdesc) + static void obj_to_location(unsigned long obj, struct zpdesc **zpdesc, + unsigned int *obj_idx) + { +- *zpdesc = pfn_zpdesc(obj >> OBJ_INDEX_BITS); +- *obj_idx = (obj & OBJ_INDEX_MASK); ++ *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); ++ *obj_idx = (obj & ZS_OBJ_IDX_MASK); + } + + static void obj_to_zpdesc(unsigned long obj, struct zpdesc **zpdesc) + { +- *zpdesc = pfn_zpdesc(obj >> OBJ_INDEX_BITS); ++ *zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT); + } + + /** +- * location_to_obj - get obj value encoded from (, ) ++ * location_to_obj - encode (, , ) into obj value + * @zpdesc: zpdesc object resides in zspage + * @obj_idx: object index ++ * @class_idx: size class index; ignored when ZS_OBJ_CLASS_BITS == 0 + */ +-static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx) ++static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx, ++ unsigned int class_idx) + { + unsigned long obj; + +- obj = zpdesc_pfn(zpdesc) << OBJ_INDEX_BITS; +- obj |= obj_idx & OBJ_INDEX_MASK; ++ obj = zpdesc_pfn(zpdesc) << ZS_OBJ_PFN_SHIFT; ++ obj |= (unsigned long)(class_idx & ZS_OBJ_CLASS_MASK) << ZS_OBJ_IDX_BITS; ++ obj |= obj_idx & ZS_OBJ_IDX_MASK; + + return obj; + } + + static unsigned long handle_to_obj(unsigned long handle) + { +- return *(unsigned long *)handle; ++ return READ_ONCE(*(unsigned long *)handle); + } + + static inline bool obj_allocated(struct zpdesc *zpdesc, void *obj, +@@ -805,13 +882,26 @@ static int trylock_zspage(struct zspage *zspage) + return 0; + } + +-static void __free_zspage(struct zs_pool *pool, struct size_class *class, +- struct zspage *zspage) ++/* ++ * Three free helpers, kept apart here: ++ * ++ * __free_zspage_lockless(): bare core; walks zpdescs and returns pages ++ * to the buddy allocator. Caller owns all zpdesc locks and has ++ * removed the zspage from its class list. Used by zs_free() outside ++ * class->lock so the buddy-side work does not stall the class. ++ * ++ * __free_zspage(): __free_zspage_lockless() + per-class accounting, ++ * under class->lock. Used by async_free_zspage(), the worker for ++ * zspages whose trylock_zspage() failed. ++ * ++ * free_zspage(): full wrapper - trylock zpdescs, remove from class ++ * list, call __free_zspage(); kicks deferred free on contention. ++ * Used by compaction. ++ */ ++static inline void __free_zspage_lockless(struct zspage *zspage) + { + struct zpdesc *zpdesc, *next; + +- assert_spin_locked(&class->lock); +- + VM_BUG_ON(get_zspage_inuse(zspage)); + VM_BUG_ON(zspage->fullness != ZS_INUSE_RATIO_0); + +@@ -827,7 +917,13 @@ static void __free_zspage(struct zs_pool *pool, struct size_class *class, + } while (zpdesc != NULL); + + cache_free_zspage(zspage); ++} + ++static void __free_zspage(struct zs_pool *pool, struct size_class *class, ++ struct zspage *zspage) ++{ ++ assert_spin_locked(&class->lock); ++ __free_zspage_lockless(zspage); + class_stat_sub(class, ZS_OBJS_ALLOCATED, class->objs_per_zspage); + atomic_long_sub(class->pages_per_zspage, &pool->pages_allocated); + } +@@ -1280,7 +1376,7 @@ static unsigned long obj_malloc(struct zs_pool *pool, + kunmap_local(vaddr); + mod_zspage_inuse(zspage, 1); + +- obj = location_to_obj(m_zpdesc, obj); ++ obj = location_to_obj(m_zpdesc, obj, zspage->class); + record_obj(handle, obj); + + return obj; +@@ -1383,37 +1479,97 @@ static void obj_free(int class_size, unsigned long obj) + mod_zspage_inuse(zspage, -1); + } + ++#if (ZS_OBJ_CLASS_BITS > 0) || defined(CONFIG_COMPACTION) ++/* Folds to 0 when ZS_OBJ_CLASS_BITS == 0; no ifdef needed at callers. */ ++static unsigned int obj_to_class_idx(unsigned long obj) ++{ ++ return (obj >> ZS_OBJ_IDX_BITS) & ZS_OBJ_CLASS_MASK; ++} ++#endif ++ ++/* ++ * Resolve @handle to its zspage / size_class and acquire class->lock. ++ * ++ * When class_idx is encoded in obj (ZS_OBJ_CLASS_BITS > 0), it is ++ * invariant under page migration, so the handle can be read locklessly ++ * to pick the size_class. Once class->lock is held migration is ++ * blocked and the handle is re-read to obtain a stable PFN. ++ * ++ * Otherwise (32-bit, or 64-bit fallback paths like UML where the ++ * encoding is disabled), fall back to pool->lock for the lookup. ++ */ ++#if ZS_OBJ_CLASS_BITS > 0 ++static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long handle, ++ unsigned long *objp, struct zspage **zspagep, ++ struct size_class **classp) ++ __acquires(&(*classp)->lock) ++{ ++ struct zpdesc *f_zpdesc; ++ unsigned long obj; ++ ++ obj = handle_to_obj(handle); ++ *classp = pool->size_class[obj_to_class_idx(obj)]; ++ spin_lock(&(*classp)->lock); ++ /* Re-read under class->lock: PFN is now stable vs migration. */ ++ obj = handle_to_obj(handle); ++ obj_to_zpdesc(obj, &f_zpdesc); ++ *zspagep = get_zspage(f_zpdesc); ++ *objp = obj; ++} ++#else ++static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long handle, ++ unsigned long *objp, struct zspage **zspagep, ++ struct size_class **classp) ++ __acquires(&(*classp)->lock) ++{ ++ struct zpdesc *f_zpdesc; ++ unsigned long obj; ++ ++ read_lock(&pool->lock); ++ obj = handle_to_obj(handle); ++ obj_to_zpdesc(obj, &f_zpdesc); ++ *zspagep = get_zspage(f_zpdesc); ++ *classp = zspage_class(pool, *zspagep); ++ spin_lock(&(*classp)->lock); ++ read_unlock(&pool->lock); ++ *objp = obj; ++} ++#endif ++ + void zs_free(struct zs_pool *pool, unsigned long handle) + { + struct zspage *zspage; +- struct zpdesc *f_zpdesc; + unsigned long obj; + struct size_class *class; + int fullness; ++ struct zspage *zspage_to_free = NULL; + + if (IS_ERR_OR_NULL((void *)handle)) + return; + +- /* +- * The pool->lock protects the race with zpage's migration +- * so it's safe to get the page from handle. +- */ +- read_lock(&pool->lock); +- obj = handle_to_obj(handle); +- obj_to_zpdesc(obj, &f_zpdesc); +- zspage = get_zspage(f_zpdesc); +- class = zspage_class(pool, zspage); +- spin_lock(&class->lock); +- read_unlock(&pool->lock); ++ obj_class_get_and_lock(pool, handle, &obj, &zspage, &class); + + class_stat_sub(class, ZS_OBJS_INUSE, 1); + obj_free(class->size, obj); + + fullness = fix_fullness_group(class, zspage); +- if (fullness == ZS_INUSE_RATIO_0) +- free_zspage(pool, class, zspage); ++ if (fullness == ZS_INUSE_RATIO_0) { ++ if (trylock_zspage(zspage)) { ++ remove_zspage(class, zspage); ++ class_stat_sub(class, ZS_OBJS_ALLOCATED, ++ class->objs_per_zspage); ++ zspage_to_free = zspage; ++ } else { ++ kick_deferred_free(pool); ++ } ++ } + + spin_unlock(&class->lock); ++ ++ if (zspage_to_free) { ++ __free_zspage_lockless(zspage_to_free); ++ atomic_long_sub(class->pages_per_zspage, &pool->pages_allocated); ++ } + cache_free_handle(handle); + } + EXPORT_SYMBOL_GPL(zs_free); +@@ -1646,9 +1802,6 @@ static void lock_zspage(struct zspage *zspage) + } + zspage_read_unlock(zspage); + } +-#endif /* CONFIG_COMPACTION */ +- +-#ifdef CONFIG_COMPACTION + + static void replace_sub_page(struct size_class *class, struct zspage *zspage, + struct zpdesc *newzpdesc, struct zpdesc *oldzpdesc) +@@ -1715,8 +1868,8 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + pool = zspage->pool; + + /* +- * The pool migrate_lock protects the race between zpage migration +- * and zs_free. ++ * The pool migrate_lock protects against races between zpage migration ++ * and zs_free(), but only when ZS_OBJ_CLASS_BITS does not apply. + */ + write_lock(&pool->lock); + class = zspage_class(pool, zspage); +@@ -1764,7 +1917,8 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + + old_obj = handle_to_obj(handle); + obj_to_location(old_obj, &dummy, &obj_idx); +- new_obj = (unsigned long)location_to_obj(newzpdesc, obj_idx); ++ new_obj = location_to_obj(newzpdesc, obj_idx, ++ obj_to_class_idx(old_obj)); + record_obj(handle, new_obj); + } + } +@@ -1775,9 +1929,9 @@ static int zs_page_migrate(struct page *newpage, struct page *page, + * Since we complete the data copy and set up new zspage structure, + * it's okay to release migration_lock. + */ +- write_unlock(&pool->lock); +- spin_unlock(&class->lock); + zspage_write_unlock(zspage); ++ spin_unlock(&class->lock); ++ write_unlock(&pool->lock); + + zpdesc_get(newzpdesc); + if (zpdesc_zone(newzpdesc) != zpdesc_zone(zpdesc)) { +@@ -1894,8 +2048,9 @@ static unsigned long __zs_compact(struct zs_pool *pool, + unsigned long pages_freed = 0; + + /* +- * protect the race between zpage migration and zs_free +- * as well as zpage allocation/free ++ * Protect against races between zpage migration and zs_free() ++ * (only when ZS_OBJ_CLASS_BITS does not apply), as well as ++ * zpage allocation and free. + */ + write_lock(&pool->lock); + spin_lock(&class->lock); diff --git a/pkgbuilds/linux-omarchy-muqss/0250-zsmalloc.patch.sig b/pkgbuilds/linux-omarchy-muqss/0250-zsmalloc.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..d6828f56e54179c481dafb5ceff307743cd4f818 GIT binary patch literal 594 zcmV-Y0>Jmqd)b{fGH?NdaG(Mk0owzEj>Xxv~^qLl^tC@A-_4u!Rb%{O3M=UC z4`F557up96r-&_P-+Yupc#HzI33>&Nv{eXPJS;dQhr{mv>jPp+r!L#-MUl5Jt-Wwn zaEM6-53!wh1gu*Kd@bgOFN1}TDXzO=M65u_1_WPAC%s?2+Z;Jr*tNLf^Iiu|kl%Ea zEn#eE0SLD0uak!FCKP77JW@z6v#4N+-XowQx@-u80;x-Nl@ZI>E!j{1SL={`9HGOO zSj3fj_x~K_8^=)p>bH9*NL{FHq|zPJ{mt9)IB-@cDlNbQKyUiq6s(DPC zbfP_p`qtbbgtrT>NzsXRX_o@;P%cttGJmAbAO2;-xz>NcD-jAeog7u_cj(zn$b#3f zQbxEWMH1vcl5$E{)y@}0G;Tadf-Xs=me2GJe8E{XkR)N1)RkcX@kw1P!W=w&xAbeR zQvFXzi18aq!ImtRW~47E(}Q||6l}ULb+EG+l^g8HC~LJ%l^u!_`KAoDEeq^G4)@=J g$g^Y^hh-q;`p=n-o!}XygTgEHi6+x2CJ)UQ@`kMs^Z)<= literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch b/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch new file mode 100644 index 0000000..f3b0e56 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch @@ -0,0 +1,313 @@ +diff --git a/include/linux/rmap.h b/include/linux/rmap.h +--- a/include/linux/rmap.h ++++ b/include/linux/rmap.h +@@ -843,7 +843,7 @@ static inline int folio_try_share_anon_rmap_pmd(struct folio *folio, + * Called from mm/vmscan.c to handle paging out + */ + int folio_referenced(struct folio *, int is_locked, +- struct mem_cgroup *memcg, vm_flags_t *vm_flags); ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags); + + void try_to_migrate(struct folio *folio, enum ttu_flags flags); + void try_to_unmap(struct folio *, enum ttu_flags flags); +@@ -975,10 +975,9 @@ struct anon_vma *folio_lock_anon_vma_read(const struct folio *folio, + #define anon_vma_prepare(vma) (0) + + static inline int folio_referenced(struct folio *folio, int is_locked, +- struct mem_cgroup *memcg, +- vm_flags_t *vm_flags) ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags) + { +- *vm_flags = 0; ++ vma_flags_clear_all(vma_flags); + return 0; + } + +diff --git a/mm/rmap.c b/mm/rmap.c +--- a/mm/rmap.c ++++ b/mm/rmap.c +@@ -907,7 +907,7 @@ pmd_t *mm_find_pmd(struct mm_struct *mm, unsigned long address) + struct folio_referenced_arg { + int mapcount; + int referenced; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + struct mem_cgroup *memcg; + }; + +@@ -926,7 +926,7 @@ static bool folio_referenced_one(struct folio *folio, + address = pvmw.address; + nr = 1; + +- if (vma->vm_flags & VM_LOCKED) { ++ if (vma_test(vma, VMA_LOCKED_BIT)) { + ptes++; + pra->mapcount--; + +@@ -947,7 +947,7 @@ static bool folio_referenced_one(struct folio *folio, + /* Restore the mlock which got missed */ + mlock_vma_folio(folio, vma); + page_vma_mapped_walk_done(&pvmw); +- pra->vm_flags |= VM_LOCKED; ++ vma_flags_set(&pra->vma_flags, VMA_LOCKED_BIT); + return false; /* To break the loop */ + } + +@@ -1015,8 +1015,11 @@ static bool folio_referenced_one(struct folio *folio, + referenced++; + + if (referenced) { ++ vma_flags_t vma_flags = vma->flags; ++ + pra->referenced++; +- pra->vm_flags |= vma->vm_flags & ~VM_LOCKED; ++ vma_flags_clear(&vma_flags, VMA_LOCKED_BIT); ++ vma_flags_set_mask(&pra->vma_flags, vma_flags); + } + + if (!pra->mapcount) +@@ -1054,7 +1057,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) + * @folio: The folio to test. + * @is_locked: Caller holds lock on the folio. + * @memcg: target memory cgroup +- * @vm_flags: A combination of all the vma->vm_flags which referenced the folio. ++ * @vma_flags: A combination of all the vma->flags which referenced the folio. + * + * Quick test_and_clear_referenced for all mappings of a folio, + * +@@ -1062,7 +1065,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) + * the function bailed out due to rmap lock contention. + */ + int folio_referenced(struct folio *folio, int is_locked, +- struct mem_cgroup *memcg, vm_flags_t *vm_flags) ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags) + { + bool we_locked = false; + struct folio_referenced_arg pra = { +@@ -1078,7 +1081,7 @@ int folio_referenced(struct folio *folio, int is_locked, + }; + + VM_WARN_ON_ONCE_FOLIO(folio_is_zone_device(folio), folio); +- *vm_flags = 0; ++ vma_flags_clear_all(vma_flags); + if (!pra.mapcount) + return 0; + +@@ -1092,7 +1095,7 @@ int folio_referenced(struct folio *folio, int is_locked, + } + + rmap_walk(folio, &rwc); +- *vm_flags = pra.vm_flags; ++ vma_flags_set_mask(vma_flags, pra.vma_flags); + + if (we_locked) + folio_unlock(folio); +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -262,6 +262,12 @@ static bool writeback_throttling_sane(struct scan_control *sc) + } + #endif + ++static inline bool is_exec_file_folio(const struct folio *folio, ++ const vma_flags_t *vma_flags) ++{ ++ return vma_flags_test(vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio); ++} ++ + static void set_task_reclaim_state(struct task_struct *task, + struct reclaim_state *rs) + { +@@ -830,10 +836,16 @@ enum folio_references { + * with PG_active set. In contrast, the aging (page table walk) path uses + * folio_update_gen(). + */ +-static bool lru_gen_set_refs(struct folio *folio) ++static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) + { + /* see the comment on LRU_REFS_FLAGS */ + if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { ++ /* Activate file-backed executable folios after first usage. */ ++ if (is_exec_file_folio(folio, vma_flags)) { ++ set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset)); ++ return true; ++ } ++ + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); + return false; + } +@@ -846,7 +858,7 @@ static bool lru_gen_set_refs(struct folio *folio) + return true; + } + #else +-static bool lru_gen_set_refs(struct folio *folio) ++static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) + { + return false; + } +@@ -856,16 +868,16 @@ static enum folio_references folio_check_references(struct folio *folio, + struct scan_control *sc) + { + int referenced_ptes, referenced_folio; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + + referenced_ptes = folio_referenced(folio, 1, sc->target_mem_cgroup, +- &vm_flags); ++ &vma_flags); + + /* + * The supposedly reclaimable folio was found to be in a VM_LOCKED vma. + * Let the folio, now marked Mlocked, be moved to the unevictable list. + */ +- if (vm_flags & VM_LOCKED) ++ if (vma_flags_test(&vma_flags, VMA_LOCKED_BIT)) + return FOLIOREF_ACTIVATE; + + /* +@@ -881,7 +893,7 @@ static enum folio_references folio_check_references(struct folio *folio, + if (!referenced_ptes) + return FOLIOREF_RECLAIM; + +- return lru_gen_set_refs(folio) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; ++ return lru_gen_set_refs(folio, &vma_flags) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; + } + + referenced_folio = folio_test_clear_referenced(folio); +@@ -909,7 +921,7 @@ static enum folio_references folio_check_references(struct folio *folio, + /* + * Activate file-backed executable folios after first usage. + */ +- if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) ++ if (is_exec_file_folio(folio, &vma_flags)) + return FOLIOREF_ACTIVATE; + + return FOLIOREF_KEEP; +@@ -2067,7 +2079,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + { + unsigned long nr_taken; + unsigned long nr_scanned; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + LIST_HEAD(l_hold); /* The folios which were snipped off */ + LIST_HEAD(l_active); + LIST_HEAD(l_inactive); +@@ -2111,7 +2123,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + + /* Referenced or rmap lock contention: rotate */ + if (folio_referenced(folio, 0, sc->target_mem_cgroup, +- &vm_flags) != 0) { ++ &vma_flags) != 0) { + /* + * Identify referenced, file-backed active folios and + * give them one more trip around the active list. So +@@ -2121,7 +2133,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + * IO, plus JVM can create lots of anon VM_EXEC folios, + * so we ignore them here. + */ +- if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) { ++ if (is_exec_file_folio(folio, &vma_flags)) { + nr_rotated += folio_nr_pages(folio); + list_add(&folio->lru, &l_active); + continue; +@@ -3190,14 +3202,19 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv) + ******************************************************************************/ + + /* promote pages accessed through page tables */ +-static int folio_update_gen(struct folio *folio, int gen) ++static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags) + { + unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f); + + VM_WARN_ON_ONCE(gen >= MAX_NR_GENS); + +- /* see the comment on LRU_REFS_FLAGS */ +- if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { ++ /* ++ * See the comment on LRU_REFS_FLAGS, and activate file-backed ++ * executable folios after first usage to avoid typical IO ++ * thrashing from reclaiming. ++ */ ++ if (!folio_test_referenced(folio) && !folio_test_workingset(folio) && ++ !is_exec_file_folio(folio, vma_flags)) { + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); + return -1; + } +@@ -3430,8 +3447,8 @@ static bool suitable_to_scan(int total, int young) + return young * n >= total; + } + +-static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, +- int new_gen, bool dirty) ++static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma, ++ struct folio *folio, int new_gen, bool dirty) + { + int old_gen; + +@@ -3444,10 +3461,10 @@ static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, + folio_mark_dirty(folio); + + if (walk) { +- old_gen = folio_update_gen(folio, new_gen); ++ old_gen = folio_update_gen(folio, new_gen, &vma->flags); + if (old_gen >= 0 && old_gen != new_gen) + update_batch_size(walk, folio, old_gen, new_gen); +- } else if (lru_gen_set_refs(folio)) { ++ } else if (lru_gen_set_refs(folio, &vma->flags)) { + old_gen = folio_lru_gen(folio); + if (old_gen >= 0 && old_gen != new_gen) + folio_activate(folio); +@@ -3520,7 +3537,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, + continue; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, args->vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -3533,7 +3550,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, + walk->mm_stats[MM_LEAF_YOUNG] += nr; + } + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, args->vma, last, gen, dirty); + last = NULL; + + if (i < PTRS_PER_PTE && get_next_vma(PMD_MASK, PAGE_SIZE, args, &start, &end)) +@@ -3611,7 +3628,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area + goto next; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -3625,7 +3642,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area + i = i > MIN_LRU_BATCH ? 0 : find_next_bit(bitmap, MIN_LRU_BATCH, i) + 1; + } while (i <= MIN_LRU_BATCH); + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + lazy_mmu_mode_disable(); + spin_unlock(ptl); +@@ -4260,7 +4277,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + continue; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -4272,7 +4289,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + young += nr; + } + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + lazy_mmu_mode_disable(); + diff --git a/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch.sig b/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..7023e215561fa9e1b008337875dd42e397811f44 GIT binary patch literal 594 zcmV-Y0w$Ea0Q>D3=ux$CU6S0ySM~i8EwZ>cS4B4`#!l zT}}tOaT(G{+3$2z71plU?to0VZ9}Qj|4N%t=aqIwtvN5r7@`4o%`|)ApWySann8$Gi zJ9?s7(dM5+R14klMYY8;ZDI#7+$xM!%9K05$Y%DXncwHEjjLNeX?rUd43sI0+Wkfm zXTe^x@^=Q}s~P(N06o=oagO?CCZa<0>VYLo_wU4JU4yYJwnTHwFF~qS#z*}A;c%so z->XZH?R6z@GhunKIT5@^!1#1fY?PGh93+YlZF$c^Q_WGMm ga{;uV2aBWJA4;UDfm9fyTGXddBtd!g?%^4hm4p%!FaQ7m literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch b/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch new file mode 100644 index 0000000..afe8241 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch @@ -0,0 +1,161 @@ +diff --git a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -195,22 +195,28 @@ struct ksm_stable_node { + * @node: rb node of this rmap_item in the unstable tree + * @head: pointer to stable_node heading this list in the stable tree + * @hlist: link into hlist of rmap_items hanging off that stable_node +- * @age: number of scan iterations since creation +- * @remaining_skips: how many scans to skip ++ * @age: number of scan iterations since creation (unstable node) ++ * @remaining_skips: how many scans to skip (unstable node) ++ * @linear_page_index: the original page's index before merged by KSM (stable node) + */ + struct ksm_rmap_item { + struct ksm_rmap_item *rmap_list; + union { +- struct anon_vma *anon_vma; /* when stable */ ++ struct anon_vma *anon_vma; /* for reverse mapping, when stable */ + #ifdef CONFIG_NUMA + int nid; /* when node of unstable tree */ + #endif + }; + struct mm_struct *mm; + unsigned long address; /* + low bits used for flags below */ +- unsigned int oldchecksum; /* when unstable */ +- rmap_age_t age; +- rmap_age_t remaining_skips; ++ union { ++ struct { ++ unsigned int oldchecksum; ++ rmap_age_t age; ++ rmap_age_t remaining_skips; ++ }; /* when unstable */ ++ unsigned long linear_page_index; /* for reverse mapping, when stable */ ++ }; + union { + struct rb_node node; /* when node of unstable tree */ + struct { /* when listed from stable tree */ +@@ -776,6 +782,11 @@ static struct vm_area_struct *find_mergeable_vma(struct mm_struct *mm, + return vma; + } + ++/* ++ * break_cow: actively break COW, replacing the KSM page by a fresh anonymous ++ * page. This is called when rmap_item has not yet become stable, but page ++ * has been merged. ++ */ + static void break_cow(struct ksm_rmap_item *rmap_item) + { + struct mm_struct *mm = rmap_item->mm; +@@ -787,6 +798,11 @@ static void break_cow(struct ksm_rmap_item *rmap_item) + * to undo, we also need to drop a reference to the anon_vma. + */ + put_anon_vma(rmap_item->anon_vma); ++ /* ++ * Reset linear_page_index that might overlay age-related ++ * information. (it's still unstable node) ++ */ ++ rmap_item->linear_page_index = 0; + + mmap_read_lock(mm); + vma = find_mergeable_vma(mm, addr); +@@ -899,6 +915,8 @@ static void remove_node_from_stable_tree(struct ksm_stable_node *stable_node) + VM_BUG_ON(stable_node->rmap_hlist_len <= 0); + stable_node->rmap_hlist_len--; + put_anon_vma(rmap_item->anon_vma); ++ /* Reset linear_page_index that might overlay age-related information. */ ++ rmap_item->linear_page_index = 0; + rmap_item->address &= PAGE_MASK; + cond_resched(); + } +@@ -1052,6 +1070,8 @@ static void remove_rmap_item_from_tree(struct ksm_rmap_item *rmap_item) + stable_node->rmap_hlist_len--; + + put_anon_vma(rmap_item->anon_vma); ++ /* Reset linear_page_index that might overlay age-related information. */ ++ rmap_item->linear_page_index = 0; + rmap_item->head = NULL; + rmap_item->address &= PAGE_MASK; + +@@ -1598,8 +1618,15 @@ static int try_to_merge_with_ksm_page(struct ksm_rmap_item *rmap_item, + /* Unstable nid is in union with stable anon_vma: remove first */ + remove_rmap_item_from_tree(rmap_item); + +- /* Must get reference to anon_vma while still holding mmap_lock */ ++ /* ++ * We can consider the VMA only while still holding the mmap lock, ++ * so lock, so reference the anon_vma and calculate the linear ++ * page index early, before stable_tree_append(). If anything goes ++ * wrong that prevents the rmap_item from being added to the ++ * stable_tree, break_cow() will clean it up. ++ */ + rmap_item->anon_vma = vma->anon_vma; ++ rmap_item->linear_page_index = linear_page_index(vma, rmap_item->address); + get_anon_vma(vma->anon_vma); + out: + mmap_read_unlock(mm); +@@ -2458,6 +2485,13 @@ static bool should_skip_rmap_item(struct folio *folio, + if (folio_test_ksm(folio)) + return false; + ++ /* ++ * There is no age information in stable-tree nodes. We might end up ++ * here without a KSM page for example after COW. ++ */ ++ if (rmap_item->address & STABLE_FLAG) ++ return false; ++ + age = rmap_item->age; + if (age != U8_MAX) + rmap_item->age++; +@@ -3173,6 +3207,7 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) + hlist_for_each_entry(rmap_item, &stable_node->hlist, hlist) { + /* Ignore the stable/unstable/sqnr flags */ + const unsigned long addr = rmap_item->address & PAGE_MASK; ++ const unsigned long index = rmap_item->linear_page_index; + struct anon_vma *anon_vma = rmap_item->anon_vma; + struct anon_vma_chain *vmac; + struct vm_area_struct *vma; +@@ -3186,8 +3221,18 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) + anon_vma_lock_read(anon_vma); + } + ++ /* ++ * Currently, KSM folios are always small folios, so it's ++ * sufficient to search for a single page. We can simply use ++ * the linear_page_index of the original de-duplicate ++ * anonymous page that we remembered in the rmap_item while ++ * de-duplicating. Note that mremap() always de-duplicates KSM ++ * folios: so if there was mremap() in our parent or our child, ++ * we wouldn't have the KSM folio mapped in these processes ++ * anymore. ++ */ + anon_vma_interval_tree_foreach(vmac, &anon_vma->rb_root, +- 0, ULONG_MAX) { ++ index, index) { + + cond_resched(); + vma = vmac->vma; +@@ -3243,17 +3288,17 @@ void collect_procs_ksm(const struct folio *folio, const struct page *page, + rcu_read_lock(); + for_each_process(tsk) { + struct anon_vma_chain *vmac; +- unsigned long addr; ++ const unsigned long addr = rmap_item->address & PAGE_MASK; ++ const unsigned long index = rmap_item->linear_page_index; + struct task_struct *t = + task_early_kill(tsk, force_early); + if (!t) + continue; +- anon_vma_interval_tree_foreach(vmac, &av->rb_root, 0, +- ULONG_MAX) ++ anon_vma_interval_tree_foreach(vmac, &av->rb_root, index, ++ index) + { + vma = vmac->vma; + if (vma->vm_mm == t->mm) { +- addr = rmap_item->address & PAGE_MASK; + add_to_kill_ksm(t, page, vma, to_kill, + addr); + } diff --git a/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch.sig b/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..32623479925aebb423e2f2d46d312e7e78179f15 GIT binary patch literal 594 zcmV-Y0DRQbzgvC7T>|OSZXF{>l`&;a2p%NE+8&@8$7Itqs3IRH^n>Wt+jGcr8|v$ z@4a$7a+)m}C(YZm#+_+DrFVoFu?S{KdJK_m4|QXnmIH@G>xXMKFNqjyK81k@{g|e9 zW&kKWM3}K+c!qgwwTVaHm8JTYVuB{ykV4@Z(mfosW+YipbyKuZ6KePEbp)NyCMX4# zL5^G9Am;qAK*}e4?i|u8$z0uewwIG4K(}Pe zvh{K0+7tw-;-LO@>NOg+a`%UN~g<%f*2OpS&h3NrAmw6&3N8Rn`2 z)Yw!che}%$)=BM)Xa3Y}4Sysemr-WrX0m`#h5Pxsh9o---9M zCF^(V{dfXeOzG;7>I7Y&)7~@;zEI)aL^PPWtqT gaGDx28wsV`af=DCW!R|^-jA6_6A~lRh8pxR+6bQ$r2qf` literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch b/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch new file mode 100644 index 0000000..e0bf148 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch @@ -0,0 +1,1162 @@ +diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c +--- a/drivers/block/zram/backend_842.c ++++ b/drivers/block/zram/backend_842.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "842: " fmt ++ + #include + #include + #include +@@ -13,6 +15,14 @@ static void release_params_842(struct zcomp_params *params) + + static int setup_params_842(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c +--- a/drivers/block/zram/backend_deflate.c ++++ b/drivers/block/zram/backend_deflate.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "deflate: " fmt ++ + #include + #include + #include +@@ -22,15 +24,26 @@ static void deflate_release_params(struct zcomp_params *params) + + static int deflate_setup_params(struct zcomp_params *params) + { +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = Z_DEFAULT_COMPRESSION; ++ } else if (params->level < Z_DEFAULT_COMPRESSION || ++ params->level > Z_BEST_COMPRESSION) { ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } ++ + if (params->deflate.winbits == ZCOMP_PARAM_NOT_SET) { + params->deflate.winbits = DEFLATE_DEF_WINBITS; + } else { + s32 wb = params->deflate.winbits; + + if ((wb < -15 || wb > -9) && (wb < 9 || wb > 15)) { +- pr_err("invalid deflate winbits: %d\n", wb); ++ pr_err("invalid winbits %d\n", wb); + return -EINVAL; + } + } +diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c +--- a/drivers/block/zram/backend_lz4.c ++++ b/drivers/block/zram/backend_lz4.c +@@ -1,3 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#define pr_fmt(fmt) "lz4: " fmt ++ + #include + #include + #include +@@ -28,8 +32,12 @@ static int lz4_setup_params(struct zcomp_params *params) + LZ4_stream_t *dict_stream; + int ret; + +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = LZ4_ACCELERATION_DEFAULT; ++ } else if (params->level < LZ4_ACCELERATION_DEFAULT) { ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } + + if (!params->dict || !params->dict_sz) + return 0; +diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c +--- a/drivers/block/zram/backend_lz4hc.c ++++ b/drivers/block/zram/backend_lz4hc.c +@@ -1,3 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#define pr_fmt(fmt) "lz4hc: " fmt ++ + #include + #include + #include +@@ -18,8 +22,18 @@ static void lz4hc_release_params(struct zcomp_params *params) + + static int lz4hc_setup_params(struct zcomp_params *params) + { +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = LZ4HC_DEFAULT_CLEVEL; ++ } else if (params->level < 1 || params->level > LZ4HC_MAX_CLEVEL) { ++ /* ++ * Use < 1 rather than < LZ4HC_MIN_CLEVEL here because ++ * LZ4HC_compress_generic() only clamps levels below 1 ++ * (levels 1 and 2 are valid). LZ4HC_MIN_CLEVEL (3) is ++ * advisory and not enforced by the library. ++ */ ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } + + return 0; + } +diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c +--- a/drivers/block/zram/backend_lzo.c ++++ b/drivers/block/zram/backend_lzo.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "lzo: " fmt ++ + #include + #include + #include +@@ -12,6 +14,14 @@ static void lzo_release_params(struct zcomp_params *params) + + static int lzo_setup_params(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c +--- a/drivers/block/zram/backend_lzorle.c ++++ b/drivers/block/zram/backend_lzorle.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "lzo-rle: " fmt ++ + #include + #include + #include +@@ -12,6 +14,14 @@ static void lzorle_release_params(struct zcomp_params *params) + + static int lzorle_setup_params(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c +--- a/drivers/block/zram/backend_zstd.c ++++ b/drivers/block/zram/backend_zstd.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "zstd: " fmt ++ + #include + #include + #include +@@ -58,8 +60,13 @@ static int zstd_setup_params(struct zcomp_params *params) + return -ENOMEM; + + params->drv_data = zp; +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = zstd_default_clevel(); ++ } else if (params->level < zstd_min_clevel() || ++ params->level > zstd_max_clevel()) { ++ pr_err("invalid compression level %d\n", params->level); ++ goto error; ++ } + + zp->cprm = zstd_get_params(params->level, PAGE_SIZE); + +@@ -85,7 +92,6 @@ static int zstd_setup_params(struct zcomp_params *params) + return 0; + + error: +- zstd_release_params(params); + return -EINVAL; + } + +@@ -161,7 +167,6 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx) + return 0; + + error: +- zstd_release_params(params); + zstd_destroy(ctx); + return -EINVAL; + } +diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c +--- a/drivers/block/zram/zram_drv.c ++++ b/drivers/block/zram/zram_drv.c +@@ -1664,6 +1664,17 @@ static void comp_algorithm_set(struct zram *zram, u32 prio, const char *alg) + zram->comp_algs[prio] = alg; + } + ++static void comp_params_reset(struct zram *zram, u32 prio) ++{ ++ struct zcomp_params *params = &zram->params[prio]; ++ ++ vfree(params->dict); ++ params->level = ZCOMP_PARAM_NOT_SET; ++ params->deflate.winbits = ZCOMP_PARAM_NOT_SET; ++ params->dict_sz = 0; ++ params->dict = NULL; ++} ++ + static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) + { + const char *alg; +@@ -1684,20 +1695,10 @@ static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) + } + + comp_algorithm_set(zram, prio, alg); ++ comp_params_reset(zram, prio); + return 0; + } + +-static void comp_params_reset(struct zram *zram, u32 prio) +-{ +- struct zcomp_params *params = &zram->params[prio]; +- +- vfree(params->dict); +- params->level = ZCOMP_PARAM_NOT_SET; +- params->deflate.winbits = ZCOMP_PARAM_NOT_SET; +- params->dict_sz = 0; +- params->dict = NULL; +-} +- + static int comp_params_store(struct zram *zram, u32 prio, s32 level, + const char *dict_path, + struct deflate_params *deflate_params) +@@ -1712,8 +1713,16 @@ static int comp_params_store(struct zram *zram, u32 prio, s32 level, + INT_MAX, + NULL, + READING_POLICY); +- if (sz < 0) ++ if (sz < 0) { ++ pr_err("failed to load dictionary %s (err=%zd)\n", ++ dict_path, sz); ++ return sz; ++ } ++ if (sz == 0) { ++ pr_err("failed to load dictionary %s (empty file)\n", ++ dict_path); + return -EINVAL; ++ } + } + + zram->params[prio].dict_sz = sz; +diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h +--- a/include/linux/memcontrol.h ++++ b/include/linux/memcontrol.h +@@ -952,6 +952,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item); + bool memcg_stat_item_valid(int idx); + bool memcg_vm_event_item_valid(enum vm_event_item idx); + unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx); ++unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx); + unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx); + +@@ -1404,6 +1406,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec, + return node_page_state(lruvec_pgdat(lruvec), idx); + } + ++static inline unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx) ++{ ++ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); ++} ++ + static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx) + { +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -323,6 +323,8 @@ enum node_stat_item { + PGSCAN_PROACTIVE, + PGSCAN_ANON, + PGSCAN_FILE, ++ PGROTATE_ANON, ++ PGROTATE_FILE, + PGREFILL, + #ifdef CONFIG_HUGETLB_PAGE + NR_HUGETLB, +@@ -755,6 +757,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, + + #endif /* CONFIG_LRU_GEN */ + ++struct lru_cost { ++ unsigned long count; ++ unsigned long last_rotated; ++ unsigned long last_io; ++}; ++ + struct lruvec { + struct list_head lists[NR_LRU_LISTS]; + /* per lruvec lru_lock for memcg */ +@@ -763,9 +771,12 @@ struct lruvec { + * These track the cost of reclaiming one LRU - file or anon - + * over the other. As the observed cost of reclaiming one LRU + * increases, the reclaim scan balance tips toward the other. ++ * Updated and decayed at prepare_scan_control() time; cost_lock ++ * serialises that update. + */ +- unsigned long anon_cost; +- unsigned long file_cost; ++ struct lru_cost cost[ANON_AND_FILE]; ++ /* Protects cost[]. */ ++ spinlock_t cost_lock; + /* Non-resident age, driven by LRU movement */ + atomic_long_t nonresident_age; + /* Refaults at the time of last reclaim cycle */ +diff --git a/include/linux/swap.h b/include/linux/swap.h +--- a/include/linux/swap.h ++++ b/include/linux/swap.h +@@ -309,9 +309,6 @@ extern unsigned long totalreserve_pages; + + + /* linux/mm/swap.c */ +-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, +- unsigned int nr_io, unsigned int nr_rotated); +-void lru_note_cost_refault(struct folio *); + void folio_add_lru(struct folio *); + void folio_add_lru_vma(struct folio *, struct vm_area_struct *); + void mark_page_accessed(struct page *); +diff --git a/include/linux/vmstat.h b/include/linux/vmstat.h +--- a/include/linux/vmstat.h ++++ b/include/linux/vmstat.h +@@ -20,7 +20,6 @@ struct reclaim_stat { + unsigned nr_congested; + unsigned nr_writeback; + unsigned nr_immediate; +- unsigned nr_pageout; + unsigned nr_activate[ANON_AND_FILE]; + unsigned nr_ref_keep; + unsigned nr_unmap_fail; +@@ -194,6 +193,19 @@ unsigned long global_node_page_state_pages(enum node_stat_item item) + return x; + } + ++/* ++ * Non-clamping variant of global_node_page_state() intended for callers that ++ * snapshot a monotonically-incremented counter and subtract two samples. ++ * Returns the raw wrapping value so that unsigned modular subtraction stays ++ * correct across a signed-long overflow (a real hazard on 32-bit) that the ++ * clamp in global_node_page_state() would otherwise turn into a huge spurious ++ * delta. Do NOT use for non-monotonic page-count reads. ++ */ ++static inline unsigned long global_node_page_state_monotonic(enum node_stat_item item) ++{ ++ return (unsigned long)atomic_long_read(&vm_node_stat[item]); ++} ++ + static inline unsigned long global_node_page_state(enum node_stat_item item) + { + VM_WARN_ON_ONCE(vmstat_item_in_bytes(item)); +@@ -259,11 +271,14 @@ extern unsigned long node_page_state(struct pglist_data *pgdat, + enum node_stat_item item); + extern unsigned long node_page_state_pages(struct pglist_data *pgdat, + enum node_stat_item item); ++extern unsigned long node_page_state_monotonic(struct pglist_data *pgdat, ++ enum node_stat_item item); + extern void fold_vm_numa_events(void); + #else + #define sum_zone_node_page_state(node, item) global_zone_page_state(item) + #define node_page_state(node, item) global_node_page_state(item) + #define node_page_state_pages(node, item) global_node_page_state_pages(item) ++#define node_page_state_monotonic(node, item) global_node_page_state_monotonic(item) + static inline void fold_vm_numa_events(void) + { + } +diff --git a/lib/maple_tree.c b/lib/maple_tree.c +--- a/lib/maple_tree.c ++++ b/lib/maple_tree.c +@@ -1501,14 +1501,26 @@ static inline void mas_parent_gap(struct ma_state *mas, unsigned char offset, + goto ascend; + } + ++static __always_inline void mas_update_gap_known(struct ma_state *mas, ++ unsigned long gap) ++{ ++ unsigned char pslot; ++ unsigned long p_gap; ++ ++ pslot = mte_parent_slot(mas->node); ++ p_gap = ma_gaps(mte_parent(mas->node), ++ mas_parent_type(mas, mas->node))[pslot]; ++ ++ if (p_gap != gap) ++ mas_parent_gap(mas, pslot, gap); ++} ++ + /* + * mas_update_gap() - Update a nodes gaps and propagate up if necessary. + * @mas: the maple state. + */ + static inline void mas_update_gap(struct ma_state *mas) + { +- unsigned char pslot; +- unsigned long p_gap; + unsigned long max_gap; + + if (!mt_is_alloc(mas->tree)) +@@ -1518,13 +1530,7 @@ static inline void mas_update_gap(struct ma_state *mas) + return; + + max_gap = mas_max_gap(mas); +- +- pslot = mte_parent_slot(mas->node); +- p_gap = ma_gaps(mte_parent(mas->node), +- mas_parent_type(mas, mas->node))[pslot]; +- +- if (p_gap != max_gap) +- mas_parent_gap(mas, pslot, max_gap); ++ mas_update_gap_known(mas, max_gap); + } + + /* +@@ -2081,8 +2087,8 @@ static inline void mas_wmb_replace(struct ma_state *mas, struct maple_copy *cp) + mas->node = mt_slot_locked(mas->tree, cp->slot, 0); + /* Insert the new data in the tree */ + mas_topiary_replace(mas, old_enode, cp->height); +- if (!mte_is_leaf(mas->node)) +- mas_update_gap(mas); ++ if (mt_is_alloc(mas->tree) && !mte_is_root(mas->node)) ++ mas_update_gap_known(mas, cp->gap[0]); + + mtree_range_walk(mas); + } +@@ -3125,7 +3131,7 @@ static void mas_wr_spanning_store(struct ma_wr_state *wr_mas) + static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + { + unsigned char dst_offset, offset_end; +- unsigned char copy_size, node_pivots; ++ unsigned char copy_size, node_pivots, node_slots; + struct maple_node reuse, *newnode; + unsigned long *dst_pivots; + void __rcu **dst_slots; +@@ -3138,6 +3144,7 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + in_rcu = mt_in_rcu(mas->tree); + offset_end = wr_mas->offset_end; + node_pivots = mt_pivots[wr_mas->type]; ++ node_slots = mt_slots[wr_mas->type]; + /* Assume last adds an entry */ + new_end = mas->end + 1 - offset_end + mas->offset; + if (mas->last == wr_mas->end_piv) { +@@ -3149,7 +3156,6 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + if (in_rcu) { + newnode = mas_pop_node(mas); + } else { +- memset(&reuse, 0, sizeof(struct maple_node)); + newnode = &reuse; + } + +@@ -3193,7 +3199,21 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + dst_pivots[new_end] = mas->max; + + done: +- mas_leaf_set_meta(newnode, maple_leaf_64, new_end); ++ if (!in_rcu && new_end + 2 < node_slots) { ++ unsigned char clear_from = new_end + 1; ++ ++ /* ++ * Note that the last slot is never cleared, since the metadata ++ * will be stored there or it has a value. ++ */ ++ memset(dst_slots + clear_from, 0, ++ sizeof(void __rcu *) * (node_slots - clear_from)); ++ if (clear_from < node_pivots) ++ memset(dst_pivots + clear_from, 0, ++ sizeof(unsigned long) * (node_pivots - clear_from)); ++ } ++ ++ mas_leaf_set_meta(newnode, wr_mas->type, new_end); + if (in_rcu) { + struct maple_enode *old_enode = mas->node; + +@@ -3218,7 +3238,7 @@ static inline void mas_wr_slot_store(struct ma_wr_state *wr_mas) + void __rcu **slots = wr_mas->slots; + bool gap = false; + +- gap |= !mt_slot_locked(mas->tree, slots, offset); ++ gap |= !wr_mas->content; + gap |= !mt_slot_locked(mas->tree, slots, offset + 1); + + if (wr_mas->offset_end - offset == 1) { +@@ -3661,6 +3681,9 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) + { + struct ma_state *mas = wr_mas->mas; + unsigned char new_end; ++ bool appending; ++ bool one_slot; ++ bool in_rcu; + + if (unlikely(mas_is_none(mas) || mas_is_ptr(mas))) + return wr_store_root; +@@ -3680,21 +3703,30 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) + return wr_new_root; + + new_end = mas_wr_new_end(wr_mas); ++ in_rcu = mt_in_rcu(mas->tree); ++ appending = mas->offset == mas->end; ++ one_slot = wr_mas->offset_end - mas->offset == 1; ++ + /* Potential spanning rebalance collapsing a node */ + if (new_end < mt_min_slots[wr_mas->type]) { + if (!mte_is_root(mas->node)) + return wr_rebalance; ++ if (!in_rcu) { ++ if (appending) ++ return wr_append; ++ else if (mas->end == new_end && one_slot) ++ return wr_slot_store; ++ } + return wr_node_store; + } + + if (new_end >= mt_slots[wr_mas->type]) + return wr_split_store; + +- if (!mt_in_rcu(mas->tree) && (mas->offset == mas->end)) ++ if (!in_rcu && appending) + return wr_append; + +- if ((new_end == mas->end) && (!mt_in_rcu(mas->tree) || +- (wr_mas->offset_end - mas->offset == 1))) ++ if (new_end == mas->end && (!in_rcu || one_slot)) + return wr_slot_store; + + return wr_node_store; +diff --git a/mm/khugepaged.c b/mm/khugepaged.c +--- a/mm/khugepaged.c ++++ b/mm/khugepaged.c +@@ -1442,10 +1442,10 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s + + result = SCAN_SUCCEED; + out_up_write: +- if (anon_vma_locked) +- anon_vma_unlock_write(vma->anon_vma); + if (pte) + pte_unmap(pte); ++ if (anon_vma_locked) ++ anon_vma_unlock_write(vma->anon_vma); + mmap_write_unlock(mm); + out_nolock: + if (folio) +diff --git a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -3061,10 +3061,9 @@ int __ksm_enter(struct mm_struct *mm) + + slot = &mm_slot->slot; + ++ spin_lock(&ksm_mmlist_lock); + /* Check ksm_run too? Would need tighter locking */ + needs_wakeup = list_empty(&ksm_mm_head.slot.mm_node); +- +- spin_lock(&ksm_mmlist_lock); + mm_slot_insert(mm_slots_hash, mm, slot); + /* + * When KSM_RUN_MERGE (or KSM_RUN_STOP), +diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c +--- a/mm/memcontrol-v1.c ++++ b/mm/memcontrol-v1.c +@@ -1998,8 +1998,8 @@ void memcg1_stat_format(struct mem_cgroup *memcg, struct seq_buf *s) + for_each_online_pgdat(pgdat) { + mz = memcg->nodeinfo[pgdat->node_id]; + +- anon_cost += mz->lruvec.anon_cost; +- file_cost += mz->lruvec.file_cost; ++ anon_cost += mz->lruvec.cost[WORKINGSET_ANON].count; ++ file_cost += mz->lruvec.cost[WORKINGSET_FILE].count; + } + seq_buf_printf(s, "anon_cost %lu\n", anon_cost); + seq_buf_printf(s, "file_cost %lu\n", file_cost); +diff --git a/mm/memcontrol.c b/mm/memcontrol.c +--- a/mm/memcontrol.c ++++ b/mm/memcontrol.c +@@ -393,6 +393,7 @@ static const unsigned int memcg_node_stat_items[] = { + NR_SHMEM_THPS, + NR_FILE_THPS, + NR_ANON_THPS, ++ NR_VMSCAN_WRITE, + NR_VMALLOC, + NR_KERNEL_STACK_KB, + NR_PAGETABLE, +@@ -419,6 +420,8 @@ static const unsigned int memcg_node_stat_items[] = { + PGSCAN_PROACTIVE, + PGSCAN_ANON, + PGSCAN_FILE, ++ PGROTATE_ANON, ++ PGROTATE_FILE, + PGREFILL, + #ifdef CONFIG_HUGETLB_PAGE + NR_HUGETLB, +@@ -502,6 +505,42 @@ unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx) + return x; + } + ++/** ++ * lruvec_page_state_monotonic - non-clamping lruvec stat read for delta sampling ++ * @lruvec: the LRU vector to read from ++ * @idx: the node_stat_item to read ++ * ++ * Returns the raw state[idx] value cast to unsigned long, skipping the ++ * clamp-negative-to-zero step in lruvec_page_state(). Intended for callers ++ * that snapshot a monotonically-incremented counter and subtract two ++ * samples: unsigned modular arithmetic then yields the correct delta across ++ * a signed-long wraparound (a real hazard on 32-bit) that the clamp would ++ * otherwise turn into a huge spurious delta. ++ * ++ * Do NOT use for non-monotonic page-count reads where a transient negative ++ * reading from per-CPU delta skew must present as zero. ++ * ++ * XXX: This helper (and its node/global peers) exists because some ++ * monotonically-incremented event counters are stored in ++ * enum node_stat_item. ++ */ ++unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx) ++{ ++ struct mem_cgroup_per_node *pn; ++ int i; ++ ++ if (mem_cgroup_disabled()) ++ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); ++ ++ i = memcg_stats_index(idx); ++ if (WARN_ONCE(BAD_STAT_IDX(i), "%s: missing stat item %d\n", __func__, idx)) ++ return 0; ++ ++ pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec); ++ return (unsigned long)READ_ONCE(pn->lruvec_stats->state[i]); ++} ++ + unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx) + { +@@ -2095,7 +2134,12 @@ static bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages) + + stock_pages = READ_ONCE(stock->nr_pages[i]); + if (stock_pages >= nr_pages) { +- WRITE_ONCE(stock->nr_pages[i], stock_pages - nr_pages); ++ stock_pages -= nr_pages; ++ WRITE_ONCE(stock->nr_pages[i], stock_pages); ++ if (!stock_pages) { ++ css_put(&memcg->css); ++ WRITE_ONCE(stock->cached[i], NULL); ++ } + ret = true; + } + break; +diff --git a/mm/migrate_device.c b/mm/migrate_device.c +--- a/mm/migrate_device.c ++++ b/mm/migrate_device.c +@@ -872,7 +872,7 @@ static int migrate_vma_insert_huge_pmd_page(struct migrate_vma *migrate, + + if (flush) { + pte_free(vma->vm_mm, pgtable); +- flush_cache_page(vma, addr, addr + HPAGE_PMD_SIZE); ++ flush_cache_range(vma, addr, addr + HPAGE_PMD_SIZE); + pmdp_invalidate(vma, addr, pmdp); + } else { + pgtable_trans_huge_deposit(vma->vm_mm, pmdp, pgtable); +diff --git a/mm/mmzone.c b/mm/mmzone.c +--- a/mm/mmzone.c ++++ b/mm/mmzone.c +@@ -78,6 +78,7 @@ void lruvec_init(struct lruvec *lruvec) + + memset(lruvec, 0, sizeof(struct lruvec)); + spin_lock_init(&lruvec->lru_lock); ++ spin_lock_init(&lruvec->cost_lock); + zswap_lruvec_state_init(lruvec); + + for_each_lru(lru) +diff --git a/mm/mremap.c b/mm/mremap.c +--- a/mm/mremap.c ++++ b/mm/mremap.c +@@ -264,7 +264,7 @@ static int move_ptes(struct pagetable_move_control *pmc, + + for (; old_addr < old_end; old_ptep += nr_ptes, old_addr += nr_ptes * PAGE_SIZE, + new_ptep += nr_ptes, new_addr += nr_ptes * PAGE_SIZE) { +- VM_WARN_ON_ONCE(!pte_none(*new_ptep)); ++ VM_WARN_ON_ONCE(!pte_none(ptep_get(new_ptep))); + + nr_ptes = 1; + max_nr_ptes = (old_end - old_addr) >> PAGE_SHIFT; +diff --git a/mm/shmem.c b/mm/shmem.c +--- a/mm/shmem.c ++++ b/mm/shmem.c +@@ -3612,6 +3612,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, + struct shmem_inode_info *info = SHMEM_I(inode); + struct shmem_falloc shmem_falloc; + pgoff_t start, index, end, undo_fallocend; ++ loff_t aligned_end; + int error; + + if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) +@@ -3668,8 +3669,15 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, + goto out; + } + ++ /* Check for wraparound */ ++ if (check_add_overflow(offset + len, (loff_t)PAGE_SIZE - 1, ++ &aligned_end)) { ++ error = -EFBIG; ++ goto out; ++ } ++ + start = offset >> PAGE_SHIFT; +- end = (offset + len + PAGE_SIZE - 1) >> PAGE_SHIFT; ++ end = aligned_end >> PAGE_SHIFT; + /* Try to avoid a swapstorm if len is impossible to satisfy */ + if (sbinfo->max_blocks && end - start > sbinfo->max_blocks) { + error = -ENOSPC; +diff --git a/mm/swap.c b/mm/swap.c +--- a/mm/swap.c ++++ b/mm/swap.c +@@ -272,73 +272,6 @@ void folio_rotate_reclaimable(struct folio *folio) + folio_batch_add_and_move(folio, lru_move_tail); + } + +-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, +- unsigned int nr_io, unsigned int nr_rotated) +- __releases(lruvec->lru_lock) +- __releases(rcu) +-{ +- unsigned long cost; +- +- /* +- * Reflect the relative cost of incurring IO and spending CPU +- * time on rotations. This doesn't attempt to make a precise +- * comparison, it just says: if reloads are about comparable +- * between the LRU lists, or rotations are overwhelmingly +- * different between them, adjust scan balance for CPU work. +- */ +- cost = nr_io * SWAP_CLUSTER_MAX + nr_rotated; +- if (!cost) { +- spin_unlock_irq(&lruvec->lru_lock); +- rcu_read_unlock(); +- return; +- } +- +- for (;;) { +- unsigned long lrusize; +- +- /* Record cost event */ +- if (file) +- lruvec->file_cost += cost; +- else +- lruvec->anon_cost += cost; +- +- /* +- * Decay previous events +- * +- * Because workloads change over time (and to avoid +- * overflow) we keep these statistics as a floating +- * average, which ends up weighing recent refaults +- * more than old ones. +- */ +- lrusize = lruvec_page_state(lruvec, NR_INACTIVE_ANON) + +- lruvec_page_state(lruvec, NR_ACTIVE_ANON) + +- lruvec_page_state(lruvec, NR_INACTIVE_FILE) + +- lruvec_page_state(lruvec, NR_ACTIVE_FILE); +- +- if (lruvec->file_cost + lruvec->anon_cost > lrusize / 4) { +- lruvec->file_cost /= 2; +- lruvec->anon_cost /= 2; +- } +- +- spin_unlock_irq(&lruvec->lru_lock); +- lruvec = parent_lruvec(lruvec); +- if (!lruvec) { +- rcu_read_unlock(); +- break; +- } +- spin_lock_irq(&lruvec->lru_lock); +- } +-} +- +-void lru_note_cost_refault(struct folio *folio) +-{ +- struct lruvec *lruvec; +- +- lruvec = folio_lruvec_lock_irq(folio); +- lru_note_cost_unlock_irq(lruvec, folio_is_file_lru(folio), +- folio_nr_pages(folio), 0); +-} +- + static void lru_activate(struct lruvec *lruvec, struct folio *folio) + { + long nr_pages = folio_nr_pages(folio); +@@ -1173,8 +1106,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int + + child_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid)); + parent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid)); +- parent_lruvec->anon_cost += child_lruvec->anon_cost; +- parent_lruvec->file_cost += child_lruvec->file_cost; + + for_each_lru(lru) + lruvec_reparent_lru(child_lruvec, parent_lruvec, lru, nid); +diff --git a/mm/vma.c b/mm/vma.c +--- a/mm/vma.c ++++ b/mm/vma.c +@@ -1866,10 +1866,10 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + bool *need_rmap_locks) + { + struct vm_area_struct *vma = *vmap; +- unsigned long vma_start = vma->vm_start; ++ unsigned long old_vma_start = vma->vm_start; + struct mm_struct *mm = vma->vm_mm; + struct vm_area_struct *new_vma; +- bool faulted_in_anon_vma = true; ++ bool can_self_merge = false; + VMA_ITERATOR(vmi, mm, addr); + VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len); + +@@ -1879,7 +1879,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + */ + if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) { + pgoff = addr >> PAGE_SHIFT; +- faulted_in_anon_vma = false; ++ can_self_merge = true; + } + + /* +@@ -1899,24 +1899,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + new_vma = vma_merge_copied_range(&vmg); + + if (new_vma) { +- /* +- * Source vma may have been merged into new_vma +- */ +- if (unlikely(vma_start >= new_vma->vm_start && +- vma_start < new_vma->vm_end)) { ++ /* Self-merged and VMA replaced. */ ++ if (unlikely(new_vma->vm_start < old_vma_start && ++ new_vma->vm_end > old_vma_start)) { + /* +- * The only way we can get a vma_merge with +- * self during an mremap is if the vma hasn't +- * been faulted in yet and we were allowed to +- * reset the dst vma->vm_pgoff to the +- * destination address of the mremap to allow +- * the merge to happen. mremap must change the +- * vm_pgoff linearity between src and dst vmas +- * (in turn preventing a vma_merge) to be +- * safe. It is only safe to keep the vm_pgoff +- * linear if there are no pages mapped yet. ++ * The only way a VMA can both self-merge and be ++ * replaced is if the remap places the new VMA ++ * immediately prior to its old self ('next') and ++ * immediately after another VMA ('prev') causing the ++ * next to be removed and prev to be expanded to cover ++ * the entire range. ++ * ++ * This should only be possible if the page offset was ++ * updated, i.e. the VMA is unfaulted. + */ +- VM_BUG_ON_VMA(faulted_in_anon_vma, new_vma); ++ VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma); + *vmap = vma = new_vma; + } + *need_rmap_locks = (new_vma->vm_pgoff <= vma->vm_pgoff); +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -669,7 +669,7 @@ static pageout_t pageout(struct folio *folio, struct address_space *mapping, + folio_clear_reclaim(folio); + + trace_mm_vmscan_write_folio(folio); +- node_stat_add_folio(folio, NR_VMSCAN_WRITE); ++ lruvec_stat_mod_folio(folio, NR_VMSCAN_WRITE, folio_nr_pages(folio)); + return PAGE_SUCCESS; + } + +@@ -1420,8 +1420,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, + sc->nr_scanned -= (nr_pages - 1); + nr_pages = 1; + } +- stat->nr_pageout += nr_pages; +- + if (folio_test_writeback(folio)) + goto keep; + if (folio_test_dirty(folio)) +@@ -2045,10 +2043,10 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan, + item = PGSTEAL_KSWAPD + reclaimer_offset(sc); + mod_lruvec_state(lruvec, item, nr_reclaimed); + mod_lruvec_state(lruvec, PGSTEAL_ANON + file, nr_reclaimed); ++ if (nr_scanned > nr_reclaimed) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + file, ++ nr_scanned - nr_reclaimed); + +- lruvec_lock_irq(lruvec); +- lru_note_cost_unlock_irq(lruvec, file, stat.nr_pageout, +- nr_scanned - nr_reclaimed); + handle_reclaim_writeback(nr_taken, pgdat, sc, &stat); + trace_mm_vmscan_lru_shrink_inactive(pgdat->node_id, + nr_scanned, nr_reclaimed, &stat, sc->priority, file); +@@ -2154,9 +2152,9 @@ static void shrink_active_list(unsigned long nr_to_scan, + count_vm_events(PGDEACTIVATE, nr_deactivate); + count_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE, nr_deactivate); + mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken); ++ if (nr_rotated) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated); + +- lruvec_lock_irq(lruvec); +- lru_note_cost_unlock_irq(lruvec, file, 0, nr_rotated); + trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate, + nr_deactivate, nr_rotated, sc->priority, file); + } +@@ -2289,8 +2287,10 @@ enum scan_balance { + + static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) + { +- unsigned long file; ++ struct lru_cost *anon_cost, *file_cost; + struct lruvec *target_lruvec; ++ unsigned long lrusize; ++ unsigned long file; + + if (lru_gen_enabled() && !lru_gen_switching()) + return; +@@ -2306,11 +2306,69 @@ static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) + + /* + * Determine the scan balance between anon and file LRUs. ++ * ++ * The cost model is based on rotations, refaults and ++ * reclaim-driven writes (anon only) on each side. ++ * ++ * These event counters are monotonic, so each reclaim cycle ++ * the delta since the last scan is extracted and incorporated ++ * into a decaying average. This ensures currency, as workloads ++ * change over time, and avoids overflow in the calculations. ++ * ++ * Use lruvec_page_state_monotonic() so unsigned subtraction ++ * yields the correct delta across a signed-long wraparound of ++ * the underlying counter (a real hazard on 32-bit that the ++ * clamp in lruvec_page_state() would otherwise turn into a huge ++ * spurious delta). + */ +- spin_lock_irq(&target_lruvec->lru_lock); +- sc->anon_cost = target_lruvec->anon_cost; +- sc->file_cost = target_lruvec->file_cost; +- spin_unlock_irq(&target_lruvec->lru_lock); ++ spin_lock(&target_lruvec->cost_lock); ++ ++ for (int f = 0; f <= 1; f++) { ++ struct lru_cost *cost = &target_lruvec->cost[f]; ++ unsigned long rotated, io, nr_rotated, nr_io; ++ ++ rotated = lruvec_page_state_monotonic(target_lruvec, ++ PGROTATE_ANON + f); ++ io = lruvec_page_state_monotonic(target_lruvec, ++ WORKINGSET_RESTORE_BASE + f); ++ if (f == WORKINGSET_ANON) ++ io += lruvec_page_state_monotonic(target_lruvec, ++ NR_VMSCAN_WRITE); ++ ++ nr_rotated = rotated - cost->last_rotated; ++ nr_io = io - cost->last_io; ++ ++ /* ++ * Reflect the relative cost of incurring IO and spending ++ * CPU time on rotations. This doesn't attempt to make a ++ * precise comparison, it just says: if reloads are about ++ * comparable between the LRU lists, or rotations are ++ * overwhelmingly different between them, adjust scan ++ * balance for CPU work. ++ */ ++ cost->count += nr_io * SWAP_CLUSTER_MAX + nr_rotated; ++ ++ cost->last_rotated = rotated; ++ cost->last_io = io; ++ } ++ ++ anon_cost = &target_lruvec->cost[WORKINGSET_ANON]; ++ file_cost = &target_lruvec->cost[WORKINGSET_FILE]; ++ ++ lrusize = lruvec_page_state(target_lruvec, NR_INACTIVE_ANON) + ++ lruvec_page_state(target_lruvec, NR_ACTIVE_ANON) + ++ lruvec_page_state(target_lruvec, NR_INACTIVE_FILE) + ++ lruvec_page_state(target_lruvec, NR_ACTIVE_FILE); ++ ++ while (anon_cost->count + file_cost->count > lrusize / 4) { ++ anon_cost->count /= 2; ++ file_cost->count /= 2; ++ } ++ ++ sc->anon_cost = anon_cost->count; ++ sc->file_cost = file_cost->count; ++ ++ spin_unlock(&target_lruvec->cost_lock); + + /* + * Target desirable inactive:active list ratios for the anon +@@ -4197,7 +4255,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + unsigned long end; + struct lru_gen_mm_walk *walk; + struct folio *last = NULL; +- int young = 1; ++ int young = nr; + pte_t *pte = pvmw->pte; + unsigned long addr = pvmw->address; + struct vm_area_struct *vma = pvmw->vma; +@@ -4819,7 +4877,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + struct reclaim_stat stat; + struct lru_gen_mm_walk *walk; + int scanned, reclaimed; +- int isolated = 0, type, type_scanned; ++ int isolated = 0, nr_isolated = 0, type, type_scanned; ++ unsigned long total_reclaimed = 0; + bool skip_retry = false; + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct pglist_data *pgdat = lruvec_pgdat(lruvec); +@@ -4831,6 +4890,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + + scanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness, + &list, &isolated, &type, &type_scanned); ++ nr_isolated = isolated; + + /* Scanning may have emptied the oldest gen, flush it */ + if (scanned) +@@ -4843,6 +4903,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + retry: + reclaimed = shrink_folio_list(&list, pgdat, sc, &stat, false, memcg); + sc->nr_reclaimed += reclaimed; ++ total_reclaimed += reclaimed; + /* Retry pass is only meant for clean folios without new isolation */ + if (isolated) + handle_reclaim_writeback(isolated, pgdat, sc, &stat); +@@ -4892,6 +4953,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + goto retry; + } + ++ if (nr_isolated > total_reclaimed) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + type, ++ nr_isolated - total_reclaimed); ++ + return scanned; + } + +diff --git a/mm/vmstat.c b/mm/vmstat.c +--- a/mm/vmstat.c ++++ b/mm/vmstat.c +@@ -1024,6 +1024,17 @@ unsigned long node_page_state(struct pglist_data *pgdat, + + return node_page_state_pages(pgdat, item); + } ++ ++/* ++ * Non-clamping variant of node_page_state() intended for callers that ++ * snapshot a monotonically-incremented counter and subtract two samples. ++ * See global_node_page_state_monotonic() for the rationale. ++ */ ++unsigned long node_page_state_monotonic(struct pglist_data *pgdat, ++ enum node_stat_item item) ++{ ++ return (unsigned long)atomic_long_read(&pgdat->vm_stat[item]); ++} + #endif + + /* +@@ -1289,6 +1300,8 @@ const char * const vmstat_text[] = { + [I(PGSCAN_PROACTIVE)] = "pgscan_proactive", + [I(PGSCAN_ANON)] = "pgscan_anon", + [I(PGSCAN_FILE)] = "pgscan_file", ++ [I(PGROTATE_ANON)] = "pgrotate_anon", ++ [I(PGROTATE_FILE)] = "pgrotate_file", + [I(PGREFILL)] = "pgrefill", + #ifdef CONFIG_HUGETLB_PAGE + [I(NR_HUGETLB)] = "nr_hugetlb", +diff --git a/mm/workingset.c b/mm/workingset.c +--- a/mm/workingset.c ++++ b/mm/workingset.c +@@ -584,11 +584,6 @@ void workingset_refault(struct folio *folio, void *shadow) + /* Folio was active prior to eviction */ + if (workingset) { + folio_set_workingset(folio); +- /* +- * XXX: Move to folio_add_lru() when it supports new vs +- * putback +- */ +- lru_note_cost_refault(folio); + mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr); + } + out: +diff --git a/mm/zswap.c b/mm/zswap.c +--- a/mm/zswap.c ++++ b/mm/zswap.c +@@ -1217,7 +1217,7 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker, + * Without memcg, use the zswap pool-wide metrics. + */ + if (!mem_cgroup_disabled()) { +- mem_cgroup_flush_stats(memcg); ++ mem_cgroup_flush_stats_ratelimited(memcg); + nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT; + nr_stored = memcg_page_state(memcg, MEMCG_ZSWAPPED); + } else { +@@ -1275,6 +1275,14 @@ static struct shrinker *zswap_alloc_shrinker(void) + return shrinker; + } + ++/* ++ * Scan up to SWAP_CLUSTER_MAX pages on each per-node zswap LRU of @memcg ++ * and write back the reclaimable ones. ++ * ++ * Return: 0 if at least one entry was written back, -EAGAIN if entries ++ * were scanned but none could be written back, or -ENOENT if @memcg has ++ * writeback disabled, is a zombie cgroup, or has empty zswap LRUs. ++ */ + static int shrink_memcg(struct mem_cgroup *memcg) + { + int nid, shrunk = 0, scanned = 0; +@@ -1290,13 +1298,14 @@ static int shrink_memcg(struct mem_cgroup *memcg) + return -ENOENT; + + for_each_node_state(nid, N_NORMAL_MEMORY) { +- unsigned long nr_to_walk = 1; ++ unsigned long nr_to_walk = SWAP_CLUSTER_MAX; + + shrunk += list_lru_walk_one(&zswap_list_lru, nid, memcg, + &shrink_memcg_cb, NULL, &nr_to_walk); +- scanned += 1 - nr_to_walk; ++ scanned += SWAP_CLUSTER_MAX - nr_to_walk; + } + ++ /* Nothing was scanned: every LRU under @memcg was empty. */ + if (!scanned) + return -ENOENT; + diff --git a/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..8a293964d233c369cbd857b1a56c2a296cc4dddf GIT binary patch literal 594 zcmV-Y0l?&YbA3g>oAG&pvhkLw#9<|OocSAqK?CBpju0)IaW0w1jKX@G5D&ABa`*BG|X89S1`n!bl-%;UUY?rGrBM5 z`BbescKau>n^59jp~aOR7xJSQ0?ZpTtkmvynC+mAfL5e+hMJk8gNR(iz0y?U4(R_9!#Gl|2O>$u2rmoqA*_H1 g_g|fPMnj5@WfNA^0n(TtZw!md)GB#+@SRj@8OkpY`2YX_ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch b/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch new file mode 100644 index 0000000..e8e9809 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch @@ -0,0 +1,35 @@ +diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c +--- a/mm/pgtable-generic.c ++++ b/mm/pgtable-generic.c +@@ -438,12 +438,30 @@ static void kernel_pgtable_work_func(struct work_struct *work) + __pagetable_free(pt); + } + ++static void schedule_kernel_pgtable_free(void) ++{ ++ schedule_work(&kernel_pgtable_work.work); ++} ++ + void pagetable_free_kernel(struct ptdesc *pt) + { + spin_lock(&kernel_pgtable_work.lock); + list_add(&pt->pt_list, &kernel_pgtable_work.list); + spin_unlock(&kernel_pgtable_work.lock); + +- schedule_work(&kernel_pgtable_work.work); ++ /* ++ * The workqueue may not exist yet while the system is booting. ++ * kernel_pgtable_drain_early() schedules the work once it does. ++ */ ++ if (system_state != SYSTEM_BOOTING) ++ schedule_kernel_pgtable_free(); + } ++ ++static int __init kernel_pgtable_drain_early(void) ++{ ++ /* Free the kernel page tables queued while booting. */ ++ schedule_kernel_pgtable_free(); ++ return 0; ++} ++core_initcall(kernel_pgtable_drain_early); + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig b/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..cf29026824bbe5013456b044c71040714a735f1a GIT binary patch literal 594 zcmV-Y0_ECDK5oJkT z&kFtMzdQl+`1f3+*3=emsN%v<=yK4U+8lC8mL?=UuP@fRq6`#odP?;npf%vSF4MN* z_Z1`gbdBO006YlEwnQiuOZ-=oln?x?%qFTrpjlIO3E<=(6JKxB(LhRmly=hUYe_Sa z)$%lLurl|f&e$@8>;`@beGzhnso$-@W#9Il|tir2Dh1qK51Ab|8xD5-6<>@A3uu0aR_8o?e{5X z3m};p5=n6u0jQ`ZEJng9ARlzL!7~4rL+9)2Rp7=xQOYnXTH!1DuV%VB5`|V!dtxO4 z5EmEi4>KQhO5I;k(D}s1EU9F=np|$5w^kP^d9GKU^K=Vo2^s3m?idoDv1}5I%6wQ1 zWR?{Sf;HR`to7=kan%zdZjSBDbEBTuK;ymUFxWmBKoSY1bR(ngtJ&3h8~occ_>Ly5 zML-~a8OB|Y+Rh18cxRf3O4_xMx71f<>uRw35Z>u0;v6YO~bRHy7G_%QV* z4DHwQpI3SVp>k@1_9mQy$gY|L_i#hZGj50_j8KVBEqsq!Rzn2~LOUh;^YUb~kAJ1+E z6OCxq0P^Q6CJf%u7I<#{0KR&fpOw79nCbB++Q6ev0mP}7@hv^dqfKYJjf>>|ZAq&- zcn)U*-5tf%7Ny@e5X3*9w2OwhE;`Ys`-I80|6=DqaAZpDNoJYFn83C6shTeh5 z|70W+Wq4NL#Nu1FAq}x-Cg?Y?T)C=Yk7je$NB6l5&sq^MQGn79^sc{RTGPk+qN?K7 z3|?A8Hl~B^o--z@Zv*InQ2CeNluPWc=Pk5mS{t^QkS8_>@1Fre)~;D{fYHu9TCE$I zs?pr0Wf&8lS}YXyBNLgXCuho-;Mi;RXbxyi&qa?Ck~pI3XHewL4Fm^!bV(?SfQ(Am gzaSyMo|bzfLa<_@#Caf(kx>oF<$3-d9Z~||aa5HWAOHXW literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch b/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch new file mode 100644 index 0000000..9f0b872 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch @@ -0,0 +1,413 @@ +diff --git a/lib/zstd/common/compiler.h b/lib/zstd/common/compiler.h +--- a/lib/zstd/common/compiler.h ++++ b/lib/zstd/common/compiler.h +@@ -14,6 +14,7 @@ + + #include + ++#include "zstd_deps.h" + #include "portability_macros.h" + + /*-******************************************************* +@@ -96,6 +97,17 @@ + */ + #define BMI2_TARGET_ATTRIBUTE TARGET_ATTRIBUTE("lzcnt,bmi,bmi2") + ++#if !DYNAMIC_BMI2 ++# define ZSTD_USE_BMI2(bmi2) 0 ++# define ZSTD_SET_BMI2(state, value) do { } while (0) ++#elif defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++# define ZSTD_USE_BMI2(bmi2) cpu_feature_enabled(X86_FEATURE_BMI2) ++# define ZSTD_SET_BMI2(state, value) do { } while (0) ++#else ++# define ZSTD_USE_BMI2(bmi2) (bmi2) ++# define ZSTD_SET_BMI2(state, value) do { (state) = (value); } while (0) ++#endif ++ + /* prefetch + * can be disabled, by declaring NO_PREFETCH build macro */ + #if ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) +diff --git a/lib/zstd/common/entropy_common.c b/lib/zstd/common/entropy_common.c +--- a/lib/zstd/common/entropy_common.c ++++ b/lib/zstd/common/entropy_common.c +@@ -16,6 +16,10 @@ + /* ************************************* + * Dependencies + ***************************************/ ++#include "zstd_deps.h" ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "mem.h" + #include "error_private.h" /* ERR_*, ERROR */ + #define FSE_STATIC_LINKING_ONLY /* FSE_MIN_TABLELOG */ +@@ -210,11 +214,9 @@ size_t FSE_readNCount_bmi2( + short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr, + const void* headerBuffer, size_t hbSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return FSE_readNCount_body_bmi2(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } +-#endif + (void)bmi2; + return FSE_readNCount_body_default(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } +@@ -335,11 +337,9 @@ size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, + void* workSpace, size_t wkspSize, + int flags) + { +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + return HUF_readStats_body_bmi2(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +-#endif + (void)flags; + return HUF_readStats_body_default(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +diff --git a/lib/zstd/common/fse_decompress.c b/lib/zstd/common/fse_decompress.c +--- a/lib/zstd/common/fse_decompress.c ++++ b/lib/zstd/common/fse_decompress.c +@@ -24,6 +24,9 @@ + #include "fse.h" + #include "error_private.h" + #include "zstd_deps.h" /* ZSTD_memcpy */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "bits.h" /* ZSTD_highbit32 */ + + +@@ -306,11 +309,9 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, siz + + size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return FSE_decompress_wksp_body_bmi2(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } +-#endif + (void)bmi2; + return FSE_decompress_wksp_body_default(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } +diff --git a/lib/zstd/common/zstd_deps.h b/lib/zstd/common/zstd_deps.h +--- a/lib/zstd/common/zstd_deps.h ++++ b/lib/zstd/common/zstd_deps.h +@@ -26,6 +26,11 @@ + #ifndef ZSTD_DEPS_COMMON + #define ZSTD_DEPS_COMMON + ++#if defined(__KERNEL__) && defined(CONFIG_X86) && \ ++ !defined(__DISABLE_EXPORTS) ++#define ZSTD_USE_KERNEL_CPU_FEATURES ++#endif ++ + #include + #include + +diff --git a/lib/zstd/compress/huf_compress.c b/lib/zstd/compress/huf_compress.c +--- a/lib/zstd/compress/huf_compress.c ++++ b/lib/zstd/compress/huf_compress.c +@@ -22,6 +22,9 @@ + * Includes + ****************************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" + #include "../common/bitstream.h" + #include "hist.h" +@@ -1138,9 +1141,10 @@ HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, + const void* src, size_t srcSize, + const HUF_CElt* CTable, const int flags) + { +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + return HUF_compress1X_usingCTable_internal_bmi2(dst, dstSize, src, srcSize, CTable); + } ++ (void)flags; + return HUF_compress1X_usingCTable_internal_default(dst, dstSize, src, srcSize, CTable); + } + +diff --git a/lib/zstd/compress/zstd_compress.c b/lib/zstd/compress/zstd_compress.c +--- a/lib/zstd/compress/zstd_compress.c ++++ b/lib/zstd/compress/zstd_compress.c +@@ -102,7 +102,7 @@ static void ZSTD_initCCtx(ZSTD_CCtx* cctx, ZSTD_customMem memManager) + assert(cctx != NULL); + ZSTD_memset(cctx, 0, sizeof(*cctx)); + cctx->customMem = memManager; +- cctx->bmi2 = ZSTD_cpuSupportsBmi2(); ++ ZSTD_SET_BMI2(cctx->bmi2, ZSTD_cpuSupportsBmi2()); + { size_t const err = ZSTD_CCtx_reset(cctx, ZSTD_reset_parameters); + assert(!ZSTD_isError(err)); + (void)err; +@@ -142,7 +142,7 @@ ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize) + cctx->blockState.nextCBlock = (ZSTD_compressedBlockState_t*)ZSTD_cwksp_reserve_object(&cctx->workspace, sizeof(ZSTD_compressedBlockState_t)); + cctx->tmpWorkspace = ZSTD_cwksp_reserve_object(&cctx->workspace, TMP_WORKSPACE_SIZE); + cctx->tmpWkspSize = TMP_WORKSPACE_SIZE; +- cctx->bmi2 = ZSTD_cpuid_bmi2(ZSTD_cpuid()); ++ ZSTD_SET_BMI2(cctx->bmi2, ZSTD_cpuid_bmi2(ZSTD_cpuid())); + return cctx; + } + +@@ -4042,7 +4042,7 @@ ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, + op + ZSTD_blockHeaderSize, dstCapacity - ZSTD_blockHeaderSize, + srcSize, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */, +- zc->bmi2); ++ ZSTD_CCtx_get_bmi2(zc)); + FORWARD_IF_ERROR(cSeqsSize, "ZSTD_entropyCompressSeqStore failed!"); + + if (!zc->isFirstBlock && +@@ -4332,7 +4332,7 @@ ZSTD_compressBlock_internal(ZSTD_CCtx* zc, + dst, dstCapacity, + srcSize, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */, +- zc->bmi2); ++ ZSTD_CCtx_get_bmi2(zc)); + + if (frame && + /* We don't want to emit our first block as a RLE even if it qualifies because +@@ -6796,7 +6796,7 @@ ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, + op + ZSTD_blockHeaderSize /* Leave space for block header */, dstCapacity - ZSTD_blockHeaderSize, + blockSize, + cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */, +- cctx->bmi2); ++ ZSTD_CCtx_get_bmi2(cctx)); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); + DEBUGLOG(5, "Compressed sequences size: %zu", compressedSeqsSize); + +@@ -7321,7 +7321,7 @@ ZSTD_compressSequencesAndLiterals_internal(ZSTD_CCtx* cctx, + &cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy, + &cctx->appliedParams, + cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */, +- cctx->bmi2); ++ ZSTD_CCtx_get_bmi2(cctx)); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); + /* note: the spec forbids for any compressed block to be larger than maximum block size */ + if (compressedSeqsSize > cctx->blockSizeMax) compressedSeqsSize = 0; +diff --git a/lib/zstd/compress/zstd_compress_internal.h b/lib/zstd/compress/zstd_compress_internal.h +--- a/lib/zstd/compress/zstd_compress_internal.h ++++ b/lib/zstd/compress/zstd_compress_internal.h +@@ -537,6 +537,15 @@ struct ZSTD_CCtx_s { + size_t extSeqBufCapacity; + }; + ++MEM_STATIC int ZSTD_CCtx_get_bmi2(const struct ZSTD_CCtx_s *cctx) { ++#if DYNAMIC_BMI2 && !defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++ return cctx->bmi2; ++#else ++ (void)cctx; ++ return 0; ++#endif ++} ++ + typedef enum { ZSTD_dtlm_fast, ZSTD_dtlm_full } ZSTD_dictTableLoadMethod_e; + typedef enum { ZSTD_tfp_forCCtx, ZSTD_tfp_forCDict } ZSTD_tableFillPurpose_e; + +diff --git a/lib/zstd/compress/zstd_compress_sequences.c b/lib/zstd/compress/zstd_compress_sequences.c +--- a/lib/zstd/compress/zstd_compress_sequences.c ++++ b/lib/zstd/compress/zstd_compress_sequences.c +@@ -12,6 +12,10 @@ + /*-************************************* + * Dependencies + ***************************************/ ++#include "../common/zstd_deps.h" ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "zstd_compress_sequences.h" + + /* +@@ -429,15 +433,13 @@ size_t ZSTD_encodeSequences( + SeqDef const* sequences, size_t nbSeq, int longOffsets, int bmi2) + { + DEBUGLOG(5, "ZSTD_encodeSequences: dstCapacity = %u", (unsigned)dstCapacity); +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return ZSTD_encodeSequences_bmi2(dst, dstCapacity, + CTable_MatchLength, mlCodeTable, + CTable_OffsetBits, ofCodeTable, + CTable_LitLength, llCodeTable, + sequences, nbSeq, longOffsets); + } +-#endif + (void)bmi2; + return ZSTD_encodeSequences_default(dst, dstCapacity, + CTable_MatchLength, mlCodeTable, +diff --git a/lib/zstd/compress/zstd_compress_superblock.c b/lib/zstd/compress/zstd_compress_superblock.c +--- a/lib/zstd/compress/zstd_compress_superblock.c ++++ b/lib/zstd/compress/zstd_compress_superblock.c +@@ -684,6 +684,6 @@ size_t ZSTD_compressSuperBlock(ZSTD_CCtx* zc, + &zc->appliedParams, + dst, dstCapacity, + src, srcSize, +- zc->bmi2, lastBlock, ++ ZSTD_CCtx_get_bmi2(zc), lastBlock, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */); + } +diff --git a/lib/zstd/decompress/huf_decompress.c b/lib/zstd/decompress/huf_decompress.c +--- a/lib/zstd/decompress/huf_decompress.c ++++ b/lib/zstd/decompress/huf_decompress.c +@@ -17,6 +17,9 @@ + * Dependencies + ****************************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" + #include "../common/bitstream.h" /* BIT_* */ + #include "../common/fse.h" /* to compress headers */ +@@ -113,9 +116,10 @@ typedef size_t (*HUF_DecompressUsingDTableFn)(void *dst, size_t dstSize, + static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ + size_t cSrcSize, HUF_DTable const* DTable, int flags) \ + { \ +- if (flags & HUF_flags_bmi2) { \ ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { \ + return fn##_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); \ + } \ ++ (void)flags; \ + return fn##_default(dst, dstSize, cSrc, cSrcSize, DTable); \ + } + +@@ -899,18 +903,16 @@ static size_t HUF_decompress4X1_usingDTable_internal(void* dst, size_t dstSize, + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X1_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X1_usingDTable_internal_fast_c_loop; + +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + fallbackFn = HUF_decompress4X1_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop; + } + # endif +- } else { ++ } else if (DYNAMIC_BMI2) { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { +@@ -1723,18 +1725,16 @@ static size_t HUF_decompress4X2_usingDTable_internal(void* dst, size_t dstSize, + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X2_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X2_usingDTable_internal_fast_c_loop; + +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + fallbackFn = HUF_decompress4X2_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop; + } + # endif +- } else { ++ } else if (DYNAMIC_BMI2) { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { +diff --git a/lib/zstd/decompress/zstd_decompress.c b/lib/zstd/decompress/zstd_decompress.c +--- a/lib/zstd/decompress/zstd_decompress.c ++++ b/lib/zstd/decompress/zstd_decompress.c +@@ -259,9 +259,7 @@ static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) + dctx->noForwardProgress = 0; + dctx->oversizedDuration = 0; + dctx->isFrameDecompression = 1; +-#if DYNAMIC_BMI2 +- dctx->bmi2 = ZSTD_cpuSupportsBmi2(); +-#endif ++ ZSTD_SET_BMI2(dctx->bmi2, ZSTD_cpuSupportsBmi2()); + dctx->ddictSet = NULL; + ZSTD_DCtx_resetParameters(dctx); + #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +diff --git a/lib/zstd/decompress/zstd_decompress_block.c b/lib/zstd/decompress/zstd_decompress_block.c +--- a/lib/zstd/decompress/zstd_decompress_block.c ++++ b/lib/zstd/decompress/zstd_decompress_block.c +@@ -16,6 +16,9 @@ + * Dependencies + *********************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" /* prefetch */ + #include "../common/cpu.h" /* bmi2 */ + #include "../common/mem.h" /* low level memory routines */ +@@ -631,13 +634,11 @@ void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, + const U32* baseValue, const U8* nbAdditionalBits, + unsigned tableLog, void* wksp, size_t wkspSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + ZSTD_buildFSETable_body_bmi2(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); + return; + } +-#endif + (void)bmi2; + ZSTD_buildFSETable_body_default(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); +@@ -1955,11 +1956,9 @@ ZSTD_decompressSequences(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequences"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequences_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + static size_t +@@ -1968,11 +1967,9 @@ ZSTD_decompressSequencesSplitLitBuffer(ZSTD_DCtx* dctx, void* dst, size_t maxDst + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequencesSplitLitBuffer"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequencesSplitLitBuffer_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequencesSplitLitBuffer_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ +@@ -1991,11 +1988,9 @@ ZSTD_decompressSequencesLong(ZSTD_DCtx* dctx, + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequencesLong"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequencesLong_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequencesLong_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ +diff --git a/lib/zstd/decompress/zstd_decompress_internal.h b/lib/zstd/decompress/zstd_decompress_internal.h +--- a/lib/zstd/decompress/zstd_decompress_internal.h ++++ b/lib/zstd/decompress/zstd_decompress_internal.h +@@ -204,7 +204,7 @@ struct ZSTD_DCtx_s + }; /* typedef'd to ZSTD_DCtx within "zstd.h" */ + + MEM_STATIC int ZSTD_DCtx_get_bmi2(const struct ZSTD_DCtx_s *dctx) { +-#if DYNAMIC_BMI2 ++#if DYNAMIC_BMI2 && !defined(ZSTD_USE_KERNEL_CPU_FEATURES) + return dctx->bmi2; + #else + (void)dctx; diff --git a/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig b/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..e39aa6cb83bc310b2e47c6e541b6e4b37f9bdf77 GIT binary patch literal 594 zcmV-Y0$`gUfYX8Gy#U`sY4_m;c?020 z-5{mq07!O7k3pIz0lOgCee+Um*;YHr{Am~b22x1G^6wBTno4*-v-rInar(1OwgcE` zq&WNIguAkpMy@xM=wCA+sql9Cd|$%;ru44l#KPP19R%ULW5IzvP#}_TH;-=M?;!EX z&~FeT2s)CO*KiOXBCnb&B^7qREWT+m=SFKp(6uk$BKdt0Cr`L31;p16zSn>$rYR*b z?mN9u^ZjMQ30mA`cnCT_<{&5chbGQSB+L6gaID+21qcH*Cw=U-!VDR!ztB8;KI5t6 z;`sfyB0NCreie-5n8{ohX-xG?ci;f?B1ze?68*6r+xF_-dF$&)SyoH-qk|CoP&)6S zONf_p;O_r2Gu zOrj~s)RoAYC~@i%6$SAMh@lNBNXgUGB2QMa0_Kb_<+Lxm+4LdMcctx = zstd_init_cstream(&ctx->params, 0, ctx->wksp, ctx->wksp_size); +- if (!ctx->cctx) { +- ret = -EINVAL; +- goto out; +- } +- + do { + dcur = acomp_walk_next_dst(&walk); + if (!dcur) { +@@ -142,6 +137,19 @@ static int zstd_compress(struct acomp_req *req) + goto out; + } + ++ if (!stream_initialized) { ++ ctx->cctx = zstd_init_cstream(&ctx->params, 0, ++ ctx->wksp, ctx->wksp_size); ++ if (!ctx->cctx) { ++ /* Release in the reverse of the map order. */ ++ acomp_walk_done_src(&walk, 0); ++ acomp_walk_done_dst(&walk, 0); ++ ret = -EINVAL; ++ goto out; ++ } ++ stream_initialized = true; ++ } ++ + if (scur) { + inbuf.pos = 0; + inbuf.src = walk.src.virt.addr; diff --git a/pkgbuilds/linux-omarchy-muqss/0292-crypto-zstd-defer-cstream-init.patch.sig b/pkgbuilds/linux-omarchy-muqss/0292-crypto-zstd-defer-cstream-init.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..eb7a182e7899eef75384c0405f75dd66cc42c752 GIT binary patch literal 594 zcmV-Y0Qz-X)et~kZl9)q zJZ!lX7Af)!To9bFwj(k;v@t)3?Y|Lcd{h+K=FyOK{q@j>uqgn$K{%ViJ^os4&pgc& zpPS4mA27{ZcCj=)+-D_DYBnfB9tHJGC@Qhsbh@=OjQvF=6D}d zxPp-w7Q9~BT6bIxRG?K7T&jD0>0>JU!-biZmZ%PR1M;`}IWbeabBByMgo(t4Iod9? zNrxdctx = zstd_init_dstream(ZSTD_MAX_SIZE, ctx->wksp, ctx->wksp_size); +- if (!ctx->dctx) { +- ret = -EINVAL; +- goto out; +- } +- + do { + scur = acomp_walk_next_src(&walk); + if (scur) { +@@ -263,6 +258,19 @@ static int zstd_decompress(struct acomp_req *req) + goto out; + } + ++ if (!stream_initialized) { ++ ctx->dctx = zstd_init_dstream(ZSTD_MAX_SIZE, ctx->wksp, ++ ctx->wksp_size); ++ if (!ctx->dctx) { ++ /* Release in the reverse of the map order. */ ++ acomp_walk_done_dst(&walk, 0); ++ acomp_walk_done_src(&walk, 0); ++ ret = -EINVAL; ++ goto out; ++ } ++ stream_initialized = true; ++ } ++ + outbuf.pos = 0; + outbuf.dst = (u8 *)walk.dst.virt.addr; + outbuf.size = dcur; diff --git a/pkgbuilds/linux-omarchy-muqss/0293-crypto-zstd-defer-dstream-init.patch.sig b/pkgbuilds/linux-omarchy-muqss/0293-crypto-zstd-defer-dstream-init.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..697fca091d0866ee729c414d5d75aa12b9043afb GIT binary patch literal 594 zcmV-Y017Bv_lg`Fs11|kgl zdQ|9Q>kF=76Z>tOT>l#q2@3{@B!lpz@}SJwo+RoRkDOXvJ>XiR_vQAMpz)!Zk4sW0 zR~Bys)Qy8|pe+#f2Zz!3&V*;2;1}(3A-J>_SZ~Q_=s+X10jj zVvT0O^-646xqrq(J6vPYMs@VxKwAjW>(D8b&u>=D`$#8 zMKbyzw?+IQKl0salvO0aX0RMMd1~$fS-90P!DoH?t*dS$YP401WI!{HkQ!PY=-d4f z=Xq$S`(S}o>J|m3&Qie-Uf?J8IwN;PK&|#v!Wl#M2Nms|gOe|$Ls?OdVHD7lf#DuV33&}TSSCGMm)e$GDZKk$_{_ypQXy~;o3a!Q_Uqy5}%;nhe@~$ z+?{n%nPYSDNYm>wb%>4OQz|sAgzw-dVW)RIO5ZHw%wC)s3w?DSNK}A{cT}qEWKep;tgNPFR)nz gUXlqO4@rV1uo*8TMVQ1KY)g%D(f=>;v2`>b#pk^jeE ++#include + #include + #include + #include +@@ -22,10 +23,28 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include + ++static int af_alg_restrict = 1; ++ ++static const struct ctl_table af_alg_table[] = { ++ { ++ .procname = "af_alg_restrict", ++ .data = &af_alg_restrict, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_TWO, ++ }, ++}; ++ ++static struct ctl_table_header *af_alg_header; ++ + struct alg_type_list { + const struct af_alg_type *type; + struct list_head list; +@@ -110,6 +129,43 @@ + } + EXPORT_SYMBOL_GPL(af_alg_unregister_type); + ++static bool af_alg_capable(void) ++{ ++ return ns_capable_noaudit(&init_user_ns, CAP_NET_ADMIN) || ++ capable(CAP_SYS_ADMIN); ++} ++ ++int af_alg_check_restriction(const char *name, ++ const struct af_alg_allowlist_entry allowlist[]) ++{ ++ int level = READ_ONCE(af_alg_restrict); ++ ++ if (level == 0) ++ return 0; ++ if (level == 1) { ++ for (const struct af_alg_allowlist_entry *ent = allowlist; ++ ent->name; ent++) { ++ if (strcmp(name, ent->name) == 0) { ++ if ((ent->flags & AF_ALG_UNPRIVILEGED) || ++ af_alg_capable()) ++ return 0; ++ /* List contains at most one entry per name. */ ++ break; ++ } ++ } ++ } ++ /* ++ * Use -ENOENT (the error code for "algorithm not found") instead of ++ * -EACCES or -EPERM, for the highest chance of correctly triggering ++ * fallback code paths in userspace programs. ++ * ++ * Don't log a warning, since it would be noisy. iwd tries to bind a ++ * bunch of algorithms that it never uses. ++ */ ++ return -ENOENT; ++} ++EXPORT_SYMBOL_GPL(af_alg_check_restriction); ++ + static void alg_do_release(const struct af_alg_type *type, void *private) + { + if (!type) +@@ -506,6 +562,9 @@ + struct sock *sk; + int err; + ++ if (READ_ONCE(af_alg_restrict) == 2) ++ return -EAFNOSUPPORT; ++ + if (sock->type != SOCK_SEQPACKET) + return -ESOCKTNOSUPPORT; + if (protocol != 0) +@@ -1222,27 +1281,32 @@ + + static int __init af_alg_init(void) + { +- int err = proto_register(&alg_proto, 0); ++ int err; ++ ++ af_alg_header = register_sysctl("crypto", af_alg_table); + ++ err = proto_register(&alg_proto, 0); + if (err) +- goto out; ++ goto out_unregister_sysctl; + + err = sock_register(&alg_family); +- if (err != 0) ++ if (err) + goto out_unregister_proto; + +-out: +- return err; ++ return 0; + + out_unregister_proto: + proto_unregister(&alg_proto); +- goto out; ++out_unregister_sysctl: ++ unregister_sysctl_table(af_alg_header); ++ return err; + } + + static void __exit af_alg_exit(void) + { + sock_unregister(PF_ALG); + proto_unregister(&alg_proto); ++ unregister_sysctl_table(af_alg_header); + } + + module_init(af_alg_init); +--- a/crypto/algif_aead.c ++++ b/crypto/algif_aead.c +@@ -34,6 +34,11 @@ + #include + #include + ++static const struct af_alg_allowlist_entry aead_allowlist[] = { ++ { "ccm(aes)" }, /* bluez */ ++ {}, ++}; ++ + static inline bool aead_sufficient_data(struct sock *sk) + { + struct alg_sock *ask = alg_sk(sk); +@@ -344,6 +349,12 @@ + + static void *aead_bind(const char *name) + { ++ int err; ++ ++ err = af_alg_check_restriction(name, aead_allowlist); ++ if (err) ++ return ERR_PTR(err); ++ + return crypto_alloc_aead(name, 0, AF_ALG_CRYPTOAPI_MASK); + } + +--- a/crypto/algif_hash.c ++++ b/crypto/algif_hash.c +@@ -16,6 +16,24 @@ + #include + #include + ++static const struct af_alg_allowlist_entry hash_allowlist[] = { ++ { "cmac(aes)" }, /* iwd, bluez */ ++ { "hmac(md5)" }, /* iwd */ ++ { "hmac(sha1)" }, /* iwd */ ++ { "hmac(sha224)" }, /* iwd */ ++ { "hmac(sha256)" }, /* iwd */ ++ { "hmac(sha384)" }, /* iwd */ ++ { "hmac(sha512)" }, /* iwd, sha512hmac */ ++ { "md4" }, /* iwd */ ++ { "md5" }, /* iwd */ ++ { "sha1", AF_ALG_UNPRIVILEGED }, /* iwd, iproute2 < 7.0 */ ++ { "sha224" }, /* iwd */ ++ { "sha256" }, /* iwd */ ++ { "sha384" }, /* iwd */ ++ { "sha512" }, /* iwd */ ++ {}, ++}; ++ + struct hash_ctx { + struct af_alg_sgl sgl; + +@@ -382,6 +400,12 @@ + + static void *hash_bind(const char *name) + { ++ int err; ++ ++ err = af_alg_check_restriction(name, hash_allowlist); ++ if (err) ++ return ERR_PTR(err); ++ + return crypto_alloc_ahash(name, 0, AF_ALG_CRYPTOAPI_MASK); + } + +--- a/crypto/algif_rng.c ++++ b/crypto/algif_rng.c +@@ -50,6 +50,10 @@ + MODULE_AUTHOR("Stephan Mueller "); + MODULE_DESCRIPTION("User-space interface for random number generators"); + ++static const struct af_alg_allowlist_entry rng_allowlist[] = { ++ {}, ++}; ++ + struct rng_ctx { + #define MAXSIZE 128 + unsigned int len; +@@ -201,6 +205,11 @@ + { + struct rng_parent_ctx *pctx; + struct crypto_rng *rng; ++ int err; ++ ++ err = af_alg_check_restriction(name, rng_allowlist); ++ if (err) ++ return ERR_PTR(err); + + pctx = kzalloc_obj(*pctx); + if (!pctx) +--- a/crypto/algif_skcipher.c ++++ b/crypto/algif_skcipher.c +@@ -35,6 +35,24 @@ + #include + #include + ++static const struct af_alg_allowlist_entry skcipher_allowlist[] = { ++ { "adiantum(xchacha12,aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "adiantum(xchacha20,aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "cbc(aes)" }, /* iwd */ ++ { "cbc(des)" }, /* iwd */ ++ { "cbc(des3_ede)" }, /* iwd */ ++ { "cbc(paes)" }, /* caam and others */ ++ { "ctr(aes)" }, /* iwd */ ++ { "ecb(aes)" }, /* iwd, bluez */ ++ { "ecb(des)" }, /* iwd */ ++ { "hctr2(aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(aes)", AF_ALG_UNPRIVILEGED }, /* cryptsetup benchmark */ ++ { "xts(camellia)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(serpent)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ { "xts(twofish)", AF_ALG_UNPRIVILEGED }, /* cryptsetup */ ++ {}, ++}; ++ + static int skcipher_sendmsg(struct socket *sock, struct msghdr *msg, + size_t size) + { +@@ -311,6 +329,11 @@ + static void *skcipher_bind(const char *name) + { + u32 mask = AF_ALG_CRYPTOAPI_MASK; ++ int err; ++ ++ err = af_alg_check_restriction(name, skcipher_allowlist); ++ if (err) ++ return ERR_PTR(err); + + if (strcmp(name, "cbc(paes)") == 0) + mask = 0; +--- a/include/crypto/if_alg.h ++++ b/include/crypto/if_alg.h +@@ -8,6 +8,7 @@ + #ifndef _CRYPTO_IF_ALG_H + #define _CRYPTO_IF_ALG_H + ++#include + #include + #include + #include +@@ -121,7 +122,7 @@ + * @iv: IV for cipher operation + * @state: Existing state for continuing operation + * @aead_assoclen: Length of AAD for AEAD cipher operations +- * @completion: Work queue for synchronous operation ++ * @wait: For waiting for completion of async crypto ops + * @used: TX bytes sent to kernel. This variable is used to + * ensure that user space cannot cause the kernel + * to allocate too much memory in sendmsg operation. +@@ -161,9 +162,20 @@ + unsigned int inflight; + }; + ++/* Flags for af_alg_allowlist_entry::flags: */ ++#define AF_ALG_UNPRIVILEGED BIT(0) /* Unprivileged use is allowed */ ++ ++struct af_alg_allowlist_entry { ++ const char *name; ++ u32 flags; ++}; ++ + int af_alg_register_type(const struct af_alg_type *type); + int af_alg_unregister_type(const struct af_alg_type *type); + ++int af_alg_check_restriction(const char *name, ++ const struct af_alg_allowlist_entry allowlist[]); ++ + int af_alg_release(struct socket *sock); + void af_alg_release_parent(struct sock *sk); + int af_alg_accept(struct sock *sk, struct socket *newsock, +@@ -177,10 +189,11 @@ + } + + /** +- * Size of available buffer for sending data from user space to kernel. ++ * af_alg_sndbuf - Size of available buffer for sending data from user space to kernel. + * +- * @sk socket of connection to user space +- * @return number of bytes still available ++ * @sk: socket of connection to user space ++ * ++ * Returns: number of bytes still available + */ + static inline int af_alg_sndbuf(struct sock *sk) + { +@@ -192,10 +205,11 @@ + } + + /** +- * Can the send buffer still be written to? ++ * af_alg_writable - Can the send buffer still be written to? ++ * ++ * @sk: socket of connection to user space + * +- * @sk socket of connection to user space +- * @return true => writable, false => not writable ++ * Returns: true => writable, false => not writable + */ + static inline bool af_alg_writable(struct sock *sk) + { +@@ -203,10 +217,11 @@ + } + + /** +- * Size of available buffer used by kernel for the RX user space operation. ++ * af_alg_rcvbuf - Size of available buffer used by kernel for the RX user space operation. + * +- * @sk socket of connection to user space +- * @return number of bytes still available ++ * @sk: socket of connection to user space ++ * ++ * Returns: number of bytes still available + */ + static inline int af_alg_rcvbuf(struct sock *sk) + { +@@ -218,10 +233,11 @@ + } + + /** +- * Can the RX buffer still be written to? ++ * af_alg_readable - Can the RX buffer still be read from? ++ * ++ * @sk: socket of connection to user space + * +- * @sk socket of connection to user space +- * @return true => writable, false => not writable ++ * Returns: true => readable, false => not readable + */ + static inline bool af_alg_readable(struct sock *sk) + { diff --git a/pkgbuilds/linux-omarchy-muqss/0295-af-alg-restrict.patch.sig b/pkgbuilds/linux-omarchy-muqss/0295-af-alg-restrict.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..0a983711e572786c8598e6c813870bda513655e3 GIT binary patch literal 594 zcmV-Y0aeIiRya0B|UubMknq& z_;0>GyJUnYk+^OGk@<4sDv2|bN^>bCP@@jS=t@s1Y2FxJ@L`w<-b@N}wu*~qoRa}4 zU_K;5;0ECw#;KZwa&u9PD&?lUcuQUN_OmG`>1pAewn*YBZFU41bzg%&f{Ulength = map->chunk_len; +- bg->flags = map->type; ++ bg->flags = map->on_disk_type; + bg->cached = BTRFS_CACHE_FINISHED; + bg->used = map->chunk_len; +- bg->flags = map->type; + bg->space_info = btrfs_find_space_info(fs_info, bg->flags); + ret = btrfs_add_block_group_cache(bg); + /* +@@ -3922,7 +3921,7 @@ int btrfs_update_block_group(struct btrfs_trans_handle *trans, + old_val += num_bytes; + cache->used = old_val; + cache->reserved -= num_bytes; +- cache->reclaim_mark = 0; ++ cache->reclaim_mark = false; + space_info->bytes_reserved -= num_bytes; + space_info->bytes_used += num_bytes; + space_info->disk_used += num_bytes * factor; +diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h +--- a/fs/btrfs/block-group.h ++++ b/fs/btrfs/block-group.h +@@ -263,6 +263,9 @@ struct btrfs_block_group { + + enum btrfs_block_group_size_class size_class:8; + ++ /* If set, this blockgroup is not used for allocation between two reclaim sweeps. */ ++ bool reclaim_mark; ++ + /* + * Number of extents in this block group used for swap files. + * All accesses protected by the spinlock 'lock'. +@@ -281,7 +284,6 @@ struct btrfs_block_group { + struct list_head active_bg_list; + struct work_struct zone_finish_work; + struct extent_buffer *last_eb; +- u64 reclaim_mark; + }; + + static inline u64 btrfs_block_group_end(const struct btrfs_block_group *block_group) +diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c +--- a/fs/btrfs/compression.c ++++ b/fs/btrfs/compression.c +@@ -651,9 +651,9 @@ struct heuristic_ws { + u8 *sample; + u32 sample_size; + /* Buckets store counters for each byte value */ +- struct bucket_item *bucket; ++ struct bucket_item bucket[BUCKET_SIZE]; + /* Sorting buffer */ +- struct bucket_item *bucket_b; ++ struct bucket_item bucket_b[BUCKET_SIZE]; + struct list_head list; + }; + +@@ -664,8 +664,6 @@ static void free_heuristic_ws(struct list_head *ws) + workspace = list_entry(ws, struct heuristic_ws, list); + + kvfree(workspace->sample); +- kfree(workspace->bucket); +- kfree(workspace->bucket_b); + kfree(workspace); + } + +@@ -681,14 +679,6 @@ static struct list_head *alloc_heuristic_ws(struct btrfs_fs_info *fs_info) + if (!ws->sample) + goto fail; + +- ws->bucket = kzalloc_objs(*ws->bucket, BUCKET_SIZE); +- if (!ws->bucket) +- goto fail; +- +- ws->bucket_b = kzalloc_objs(*ws->bucket_b, BUCKET_SIZE); +- if (!ws->bucket_b) +- goto fail; +- + INIT_LIST_HEAD(&ws->list); + return &ws->list; + fail: +diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c +--- a/fs/btrfs/ctree.c ++++ b/fs/btrfs/ctree.c +@@ -1460,6 +1460,7 @@ static noinline void unlock_up(struct btrfs_path *path, int level, + */ + static int + read_block_for_search(struct btrfs_root *root, struct btrfs_path *p, ++ struct btrfs_eb_prealloc *pa, + struct extent_buffer **eb_ret, int slot, + const struct btrfs_key *key) + { +@@ -1546,7 +1547,8 @@ read_block_for_search(struct btrfs_root *root, struct btrfs_path *p, + if (p->reada != READA_NONE) + reada_for_search(fs_info, p, parent_level, slot, key->objectid); + +- tmp = btrfs_find_create_tree_block(fs_info, blocknr, check.owner_root, check.level); ++ tmp = btrfs_find_create_tree_block(fs_info, pa, blocknr, ++ check.owner_root, check.level); + if (IS_ERR(tmp)) { + ret = PTR_ERR(tmp); + tmp = NULL; +@@ -2004,6 +2006,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + u8 lowest_level = 0; + int min_write_lock_level; + int prev_cmp; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + + if (!root) + return -EINVAL; +@@ -2058,6 +2061,11 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + } + + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + prev_cmp = -1; + b = btrfs_search_slot_get_root(root, p, write_lock_level); + if (IS_ERR(b)) { +@@ -2187,7 +2195,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + goto done; + } + +- ret2 = read_block_for_search(root, p, &b, slot, key); ++ ret2 = read_block_for_search(root, p, &pa, &b, slot, key); + if (ret2 == -EAGAIN && !p->nowait) { + trace_btrfs_search_slot_restart(root, level, "read_block"); + goto again; +@@ -2234,6 +2242,8 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + ret = ret2; + } + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + ALLOW_ERROR_INJECTION(btrfs_search_slot, ERRNO); +@@ -2259,6 +2269,7 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + int level; + int lowest_unlock = 1; + u8 lowest_level = 0; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + + lowest_level = p->lowest_level; + WARN_ON(p->nodes[0] != NULL); +@@ -2270,6 +2281,11 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + } + + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + b = btrfs_get_old_root(root, time_seq); + if (unlikely(!b)) { + ret = -EIO; +@@ -2316,7 +2332,7 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + goto done; + } + +- ret2 = read_block_for_search(root, p, &b, slot, key); ++ ret2 = read_block_for_search(root, p, &pa, &b, slot, key); + if (ret2 == -EAGAIN && !p->nowait) + goto again; + if (ret2) { +@@ -2339,6 +2355,8 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + if (ret < 0) + btrfs_release_path(p); + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + +@@ -4780,6 +4798,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + struct extent_buffer *next; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_key key; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + bool need_commit_sem = false; + u32 nritems; + int ret; +@@ -4798,6 +4817,11 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + + btrfs_item_key_to_cpu(path->nodes[0], &key, nritems - 1); + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + level = 1; + next = NULL; + btrfs_release_path(path); +@@ -4880,7 +4904,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + } + + next = c; +- ret = read_block_for_search(root, path, &next, slot, &key); ++ ret = read_block_for_search(root, path, &pa, &next, slot, &key); + if (ret == -EAGAIN && !path->nowait) + goto again; + +@@ -4923,7 +4947,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + if (!level) + break; + +- ret = read_block_for_search(root, path, &next, 0, &key); ++ ret = read_block_for_search(root, path, &pa, &next, 0, &key); + if (ret == -EAGAIN && !path->nowait) + goto again; + +@@ -4956,6 +4980,8 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + ret = ret2; + } + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + +diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h +--- a/fs/btrfs/ctree.h ++++ b/fs/btrfs/ctree.h +@@ -131,7 +131,6 @@ enum { + BTRFS_ROOT_ORPHAN_ITEM_INSERTED, + BTRFS_ROOT_DEFRAG_RUNNING, + BTRFS_ROOT_FORCE_COW, +- BTRFS_ROOT_MULTI_LOG_TASKS, + BTRFS_ROOT_DIRTY, + BTRFS_ROOT_DELETING, + +@@ -196,9 +195,7 @@ struct btrfs_root { + struct list_head log_ctxs[2]; + /* Used only for log trees of subvolumes, not for the log root tree */ + atomic_t log_writers; +- atomic_t log_commit[2]; +- /* Used only for log trees of subvolumes, not for the log root tree */ +- atomic_t log_batch; ++ bool log_commit[2]; + /* + * Protected by the 'log_mutex' lock but can be read without holding + * that lock to avoid unnecessary lock contention, in which case it +@@ -216,7 +213,6 @@ struct btrfs_root { + * to access this field. + */ + int last_log_commit; +- pid_t log_start_pid; + + u64 last_trans; + +diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c +--- a/fs/btrfs/defrag.c ++++ b/fs/btrfs/defrag.c +@@ -1093,7 +1093,7 @@ static int defrag_collect_targets(struct btrfs_inode *inode, + struct defrag_target_range *tmp; + + list_for_each_entry_safe(entry, tmp, target_list, list) { +- list_del_init(&entry->list); ++ list_del(&entry->list); + kfree(entry); + } + } +@@ -1169,7 +1169,7 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + struct defrag_target_range *entry; + struct defrag_target_range *tmp; + LIST_HEAD(target_list); +- struct folio **folios; ++ struct folio AUTO_KFREE(*folios); + const u32 sectorsize = inode->root->fs_info->sectorsize; + u64 cur = start; + const unsigned int nr_pages = ((start + len - 1) >> PAGE_SHIFT) - +@@ -1196,10 +1196,8 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + * range or the extent lock. + */ + ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); +- if (ret < 0) { +- kfree(folios); ++ if (ret < 0) + return ret; +- } + + /* Prepare all pages */ + for (int i = 0; cur < start + len && i < nr_pages; i++) { +@@ -1234,16 +1232,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + if (ret < 0) + goto unlock_extent; + +- list_for_each_entry(entry, &target_list, list) { ++ list_for_each_entry_safe(entry, tmp, &target_list, list) { + defrag_one_locked_target(inode, entry, folios, nr_pages, &cached_state); + if (entry->start > last_defrag_end) + btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, + entry->start - last_defrag_end, true); + last_defrag_end = entry->start + entry->len; +- } +- +- list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->list); + kfree(entry); + } + unlock_extent: +@@ -1255,7 +1249,6 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + folio_unlock(folios[i]); + folio_put(folios[i]); + } +- kfree(folios); + btrfs_delalloc_release_extents(inode, len); + if (last_defrag_end < start + len) + btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, +@@ -1326,10 +1319,8 @@ static int defrag_one_cluster(struct btrfs_inode *inode, + inode->root->fs_info->sectorsize_bits; + } + out: +- list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->list); ++ list_for_each_entry_safe(entry, tmp, &target_list, list) + kfree(entry); +- } + if (ret >= 0) + *last_scanned_ret = max(*last_scanned_ret, start + len); + return ret; +diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c +--- a/fs/btrfs/delayed-inode.c ++++ b/fs/btrfs/delayed-inode.c +@@ -1523,10 +1523,10 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, + ret = __btrfs_add_delayed_item(delayed_node, delayed_item); + if (unlikely(ret)) { + btrfs_err(trans->fs_info, +-"error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %d", ++"error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %pe", + name_len, name, index, btrfs_root_id(delayed_node->root), + delayed_node->inode_id, dir->index_cnt, +- delayed_node->index_cnt, ret); ++ delayed_node->index_cnt, ERR_PTR(ret)); + btrfs_release_delayed_item(delayed_item); + btrfs_release_dir_index_item_space(trans); + mutex_unlock(&delayed_node->mutex); +@@ -1645,8 +1645,8 @@ int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, + */ + if (ret < 0) { + btrfs_err(trans->fs_info, +-"metadata reservation failed for delayed dir item deletion, index: %llu, root: %llu, inode: %llu, error: %d", +- index, btrfs_root_id(node->root), node->inode_id, ret); ++"metadata reservation failed for delayed dir item deletion, index: %llu, root: %llu, inode: %llu, error: %pe", ++ index, btrfs_root_id(node->root), node->inode_id, ERR_PTR(ret)); + btrfs_release_delayed_item(item); + goto end; + } +@@ -1655,8 +1655,8 @@ int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, + ret = __btrfs_add_delayed_item(node, item); + if (unlikely(ret)) { + btrfs_err(trans->fs_info, +-"failed to add delayed dir index item, root: %llu, inode: %llu, index: %llu, error: %d", +- btrfs_root_id(node->root), node->inode_id, index, ret); ++"failed to add delayed dir index item, root: %llu, inode: %llu, index: %llu, error: %pe", ++ btrfs_root_id(node->root), node->inode_id, index, ERR_PTR(ret)); + btrfs_delayed_item_release_metadata(dir->root, item); + btrfs_release_delayed_item(item); + } +diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -14,7 +14,6 @@ + #include "ordered-data.h" + + struct btrfs_dio_data { +- ssize_t submitted; + loff_t old_isize; + struct extent_changeset *data_reserved; + struct btrfs_ordered_extent *ordered; +@@ -151,7 +150,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode, + if (type != BTRFS_ORDERED_NOCOW) { + em = btrfs_create_io_em(inode, start, file_extent, type); + if (IS_ERR(em)) +- goto out; ++ return em; + } + + ordered = btrfs_alloc_ordered_extent(inode, start, file_extent, +@@ -168,7 +167,6 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode, + ASSERT(!dio_data->ordered); + dio_data->ordered = ordered; + } +- out: + + return em; + } +@@ -626,78 +624,81 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length, + { + struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap); + struct btrfs_dio_data *dio_data = iter->private; +- size_t submitted = dio_data->submitted; + const bool write = !!(flags & IOMAP_WRITE); + int ret = 0; + +- if (!write && (iomap->type == IOMAP_HOLE)) { +- /* If reading from a hole, unlock and return */ +- btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +- pos + length - 1, NULL); ++ if (!write) { ++ /* ++ * Hole read, nothing is submitted, thus we have to unlock ++ * the whole range. ++ */ ++ if (iomap->type == IOMAP_HOLE) { ++ btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, ++ pos + length - 1, NULL); ++ return 0; ++ } ++ /* ++ * Short read, needs to unlock the remaining range, and ++ * return -ENOTBLK so we can later fault in the pages and retry. ++ */ ++ if (written < length) { ++ btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos + written, ++ pos + length - 1, NULL); ++ return -ENOTBLK; ++ } ++ /* The full range is submitted, endio will do the unlock. */ + return 0; + } + +- if (submitted < length) { +- pos += submitted; +- length -= submitted; +- if (write) { +- /* +- * Got a short write and have updated the isize, need to +- * revert the isize change. +- * +- * Normally we need to update isize with extent lock hold, +- * but we're safe due to the following factors: +- * +- * - Only a single writer can be enlarging isize +- * Enlarging isize will take the exclusive inode lock. +- * +- * - Buffered readers need to wait for the OE we're holding +- * Buffered readers will lock extent and wait for OE +- * of the folio range, and since page cache is invalidated +- * the OE wait can not be skipped. +- * +- * So here we are safe to revert the isize before +- * finishing the OE, and no reader of the remaining range +- * can see the enlarged size. +- * +- * TODO: Extend the DIO_LOCKED lifespan for direct writes, +- * and only enlarge isize after a successful write. +- */ +- if (dio_data->updated_isize) { +- u64 new_isize; ++ if (written < length) { ++ /* ++ * Got a short write and have updated the i_size, need to revert ++ * the i_size change. ++ * ++ * Normally we need to update i_size with extent lock held, but ++ * we're safe due to the following factors: ++ * ++ * - Only a single writer can be enlarging i_size ++ * Enlarging i_size will take the exclusive inode lock. ++ * ++ * - Buffered readers need to wait for the OE we're holding ++ * Buffered readers will lock extent and wait for OE ++ * of the folio range, and since page cache is invalidated ++ * the OE wait cannot be skipped. ++ * ++ * So here we are safe to revert the isize before finishing the ++ * OE, and no reader of the remaining range can see the enlarged ++ * size. ++ * ++ * TODO: Extend the DIO_LOCKED lifespan for direct writes, ++ * and only enlarge isize after a successful write. ++ */ ++ if (dio_data->updated_isize) { ++ u64 new_isize; + +- if (submitted == 0) +- new_isize = dio_data->old_isize; +- else +- new_isize = max(dio_data->old_isize, pos); +- i_size_write(inode, new_isize); +- dio_data->updated_isize = false; +- } +- /* +- * We have a short write, if there is any range +- * that is submitted properly, that part will have +- * its own OE split from the original one. +- * +- * So for the OE at dio_data->ordered, it's the part +- * that is not submitted, and should be marked +- * as fully truncated. +- */ +- btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); +- btrfs_finish_ordered_extent(dio_data->ordered, +- pos, length, true); +- } else { +- btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +- pos + length - 1, NULL); ++ if (written == 0) ++ new_isize = dio_data->old_isize; ++ else ++ new_isize = max(dio_data->old_isize, pos + written); ++ i_size_write(inode, new_isize); ++ dio_data->updated_isize = false; + } ++ /* ++ * We have a short write, if there is any range that is submitted ++ * properly, that part will have its own OE split from the ++ * original one. ++ * ++ * So for the OE at dio_data->ordered, it's the part that is not ++ * submitted, and should be marked as fully truncated. ++ */ ++ btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); ++ btrfs_finish_ordered_extent(dio_data->ordered, ++ pos + written, length - written, true); + ret = -ENOTBLK; + } +- if (write) { +- btrfs_put_ordered_extent(dio_data->ordered); +- dio_data->ordered = NULL; +- } +- +- if (write) +- extent_changeset_free(dio_data->data_reserved); ++ btrfs_put_ordered_extent(dio_data->ordered); ++ dio_data->ordered = NULL; ++ extent_changeset_free(dio_data->data_reserved); + return ret; + } + +@@ -779,8 +780,6 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio, + dip->file_offset = file_offset; + dip->bytes = bio->bi_iter.bi_size; + +- dio_data->submitted += bio->bi_iter.bi_size; +- + /* + * Check if we are doing a partial write. If we are, we need to split + * the ordered extent to match the submitted bio. Hang on to the +@@ -824,13 +823,41 @@ static ssize_t btrfs_dio_read(struct kiocb *iocb, struct iov_iter *iter, + IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED, &data, done_before); + } + ++static bool need_stable_write(struct btrfs_inode *inode) ++{ ++ const u64 data_profile = btrfs_data_alloc_profile(inode->root->fs_info) & ++ BTRFS_BLOCK_GROUP_PROFILE_MASK; ++ ++ /* Data checksum requires stable buffer. */ ++ if (!(inode->flags & BTRFS_INODE_NODATASUM)) ++ return true; ++ /* ++ * Any profile with mirror/parity will require stable buffer. ++ * Otherwise the mirror may differ from each other. ++ * ++ * Thus only SINGLE and RAID0 doesn't require stable buffer. ++ */ ++ if (data_profile != 0 && data_profile != BTRFS_BLOCK_GROUP_RAID0) ++ return true; ++ return false; ++} ++ + static struct iomap_dio *btrfs_dio_write(struct kiocb *iocb, struct iov_iter *iter, + size_t done_before) + { + struct btrfs_dio_data data = { 0 }; ++ unsigned int dio_flags = IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED; ++ ++ if (need_stable_write(BTRFS_I(file_inode(iocb->ki_filp)))) { ++ /* For now no support for BOUNCE and NOWAIT direct write. */ ++ if (iocb->ki_flags & IOCB_NOWAIT) ++ return ERR_PTR(-EAGAIN); ++ ++ dio_flags |= IOMAP_DIO_BOUNCE; ++ } + + return __iomap_dio_rw(iocb, iter, &btrfs_dio_iomap_ops, &btrfs_dio_ops, +- IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED, &data, done_before); ++ dio_flags, &data, done_before); + } + + static ssize_t check_direct_IO(struct btrfs_fs_info *fs_info, +@@ -859,8 +886,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + ssize_t ret; + unsigned int ilock_flags = 0; + struct iomap_dio *dio; +- const u64 data_profile = btrfs_data_alloc_profile(fs_info) & +- BTRFS_BLOCK_GROUP_PROFILE_MASK; + + if (iocb->ki_flags & IOCB_NOWAIT) + ilock_flags |= BTRFS_ILOCK_TRY; +@@ -874,16 +899,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + if (iocb->ki_pos + iov_iter_count(from) <= i_size_read(inode) && IS_NOSEC(inode)) + ilock_flags |= BTRFS_ILOCK_SHARED; + +- /* +- * If our data profile has duplication (either extra mirrors or RAID56), +- * we can not trust the direct IO buffer, the content may change during +- * writeback and cause different contents written to different mirrors. +- * +- * Thus only RAID0 and SINGLE can go true zero-copy direct IO. +- */ +- if (data_profile != BTRFS_BLOCK_GROUP_RAID0 && data_profile != 0) +- goto buffered; +- + relock: + ret = btrfs_inode_lock(BTRFS_I(inode), ilock_flags); + if (ret < 0) +@@ -924,22 +939,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); + goto buffered; + } +- /* +- * We can't control the folios being passed in, applications can write +- * to them while a direct IO write is in progress. This means the +- * content might change after we calculated the data checksum. +- * Therefore we can end up storing a checksum that doesn't match the +- * persisted data. +- * +- * To be extra safe and avoid false data checksum mismatch, if the +- * inode requires data checksum, just fallback to buffered IO. +- * For buffered IO we have full control of page cache and can ensure +- * no one is modifying the content during writeback. +- */ +- if (!(BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) { +- btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); +- goto buffered; +- } + + /* + * The iov_iter can be mapped to the same file range we are writing to. +diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c +--- a/fs/btrfs/disk-io.c ++++ b/fs/btrfs/disk-io.c +@@ -591,12 +591,13 @@ static const struct address_space_operations btree_aops = { + + struct extent_buffer *btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 bytenr, u64 owner_root, + int level) + { + if (btrfs_is_testing(fs_info)) + return alloc_test_extent_buffer(fs_info, bytenr); +- return alloc_extent_buffer(fs_info, bytenr, owner_root, level); ++ return alloc_extent_buffer(fs_info, pa, bytenr, owner_root, level); + } + + /* +@@ -609,12 +610,13 @@ struct extent_buffer *btrfs_find_create_tree_block( + struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + struct btrfs_tree_parent_check *check) + { ++ struct btrfs_eb_prealloc pa = { 0 }; + struct extent_buffer *buf = NULL; + int ret; + + ASSERT(check); + +- buf = btrfs_find_create_tree_block(fs_info, bytenr, check->owner_root, ++ buf = btrfs_find_create_tree_block(fs_info, &pa, bytenr, check->owner_root, + check->level); + if (IS_ERR(buf)) + return buf; +@@ -667,10 +669,7 @@ static struct btrfs_root *btrfs_alloc_root(struct btrfs_fs_info *fs_info, + init_waitqueue_head(&root->log_commit_wait[1]); + INIT_LIST_HEAD(&root->log_ctxs[0]); + INIT_LIST_HEAD(&root->log_ctxs[1]); +- atomic_set(&root->log_commit[0], 0); +- atomic_set(&root->log_commit[1], 0); + atomic_set(&root->log_writers, 0); +- atomic_set(&root->log_batch, 0); + refcount_set(&root->refs, 1); + atomic_set(&root->snapshot_force_cow, 0); + atomic_set(&root->nr_swapfiles, 0); +@@ -2053,7 +2052,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, + if (IS_ERR(log_tree_root->node)) { + ret = PTR_ERR(log_tree_root->node); + log_tree_root->node = NULL; +- btrfs_err(fs_info, "failed to read log tree with error: %d", ret); ++ btrfs_err(fs_info, "failed to read log tree with error: %pe", ERR_PTR(ret)); + btrfs_put_root(log_tree_root); + return ret; + } +@@ -2063,7 +2062,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, + btrfs_put_root(log_tree_root); + if (unlikely(ret)) { + ASSERT(BTRFS_FS_ERROR(fs_info) != 0); +- btrfs_err(fs_info, "failed to recover log trees with error: %d", ret); ++ btrfs_err(fs_info, "failed to recover log trees with error: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -2304,8 +2303,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) + + return 0; + out: +- btrfs_warn(fs_info, "failed to read root (objectid=%llu): %d", +- location.objectid, ret); ++ btrfs_warn(fs_info, "failed to read root (objectid=%llu): %pe", ++ location.objectid, ERR_PTR(ret)); + return ret; + } + +@@ -2400,8 +2399,8 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, + int btrfs_validate_super(const struct btrfs_fs_info *fs_info, + const struct btrfs_super_block *sb, int mirror_num) + { +- u64 nodesize = btrfs_super_nodesize(sb); +- u64 sectorsize = btrfs_super_sectorsize(sb); ++ const u32 nodesize = btrfs_super_nodesize(sb); ++ const u32 sectorsize = btrfs_super_sectorsize(sb); + int ret = 0; + const bool ignore_flags = btrfs_test_opt(fs_info, IGNORESUPERFLAGS); + +@@ -2443,24 +2442,24 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info, + */ + if (unlikely(!is_power_of_2(sectorsize) || sectorsize < BTRFS_MIN_BLOCKSIZE || + sectorsize > BTRFS_MAX_METADATA_BLOCKSIZE)) { +- btrfs_err(fs_info, "invalid sectorsize %llu", sectorsize); ++ btrfs_err(fs_info, "invalid sectorsize %u", sectorsize); + ret = -EINVAL; + } + + if (unlikely(!btrfs_supported_blocksize(sectorsize))) { + btrfs_err(fs_info, +- "sectorsize %llu not yet supported for page size %lu", ++ "sectorsize %u not yet supported for page size %lu", + sectorsize, PAGE_SIZE); + ret = -EINVAL; + } + + if (unlikely(!is_power_of_2(nodesize) || nodesize < sectorsize || + nodesize > BTRFS_MAX_METADATA_BLOCKSIZE)) { +- btrfs_err(fs_info, "invalid nodesize %llu", nodesize); ++ btrfs_err(fs_info, "invalid nodesize %u", nodesize); + ret = -EINVAL; + } + if (unlikely(nodesize != le32_to_cpu(sb->__unused_leafsize))) { +- btrfs_err(fs_info, "invalid leafsize %u, should be %llu", ++ btrfs_err(fs_info, "invalid leafsize %u, should be %u", + le32_to_cpu(sb->__unused_leafsize), nodesize); + ret = -EINVAL; + } +@@ -2910,7 +2909,6 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) + fs_info->nodesize = 4096; + fs_info->sectorsize = 4096; + fs_info->sectorsize_bits = ilog2(4096); +- fs_info->stripesize = 4096; + + /* Default compress algorithm when user does -o compress */ + fs_info->compress_type = BTRFS_COMPRESS_ZLIB; +@@ -2984,8 +2982,8 @@ static int btrfs_uuid_rescan_kthread(void *data) + ret = btrfs_uuid_tree_iterate(fs_info); + if (ret < 0) { + if (ret != -EINTR) +- btrfs_warn(fs_info, "iterating uuid_tree failed %d", +- ret); ++ btrfs_warn(fs_info, "iterating uuid_tree failed %pe", ++ ERR_PTR(ret)); + up(&fs_info->uuid_tree_rescan_sem); + return ret; + } +@@ -3088,7 +3086,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_rebuild_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to rebuild free space tree: %d", ret); ++ "failed to rebuild free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3099,7 +3097,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_delete_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to disable free space tree: %d", ret); ++ "failed to disable free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3110,7 +3108,8 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + */ + ret = btrfs_delete_orphan_free_space_entries(fs_info); + if (ret < 0) { +- btrfs_err(fs_info, "failed to delete orphan free space tree entries: %d", ret); ++ btrfs_err(fs_info, "failed to delete orphan free space tree entries: %pe", ++ ERR_PTR(ret)); + return ret; + } + /* +@@ -3144,7 +3143,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_recover_relocation(fs_info); + mutex_unlock(&fs_info->cleaner_mutex); + if (ret < 0) { +- btrfs_warn(fs_info, "failed to recover relocation: %d", ret); ++ btrfs_warn(fs_info, "failed to recover relocation: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -3154,7 +3153,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_create_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to create free space tree: %d", ret); ++ "failed to create free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3182,7 +3181,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_create_uuid_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to create the UUID tree %d", ret); ++ "failed to create the UUID tree %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3372,7 +3371,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + { + u32 sectorsize; + u32 nodesize; +- u32 stripesize; + u64 generation; + u16 csum_type; + struct btrfs_super_block *disk_super; +@@ -3481,7 +3479,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + /* Set up fs_info before parsing mount options */ + nodesize = btrfs_super_nodesize(disk_super); + sectorsize = btrfs_super_sectorsize(disk_super); +- stripesize = sectorsize; + fs_info->dirty_metadata_batch = nodesize * (1 + ilog2(nr_cpu_ids)); + fs_info->delalloc_batch = sectorsize * 512 * (1 + ilog2(nr_cpu_ids)); + +@@ -3500,7 +3497,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + else + fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); + fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; +- fs_info->stripesize = stripesize; + fs_info->fs_devices->fs_info = fs_info; + + if (fs_info->sectorsize > PAGE_SIZE) +@@ -3566,7 +3562,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_read_sys_array(fs_info); + mutex_unlock(&fs_info->chunk_mutex); + if (ret) { +- btrfs_err(fs_info, "failed to read the system array: %d", ret); ++ btrfs_err(fs_info, "failed to read the system array: %pe", ERR_PTR(ret)); + goto fail_sb_buffer; + } + +@@ -3585,7 +3581,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + + ret = btrfs_read_chunk_tree(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to read chunk tree: %d", ret); ++ btrfs_err(fs_info, "failed to read chunk tree: %pe", ERR_PTR(ret)); + goto fail_tree_roots; + } + +@@ -3615,7 +3611,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_get_dev_zone_info_all_devices(fs_info); + if (ret) { + btrfs_err(fs_info, +- "zoned: failed to read device zone info: %d", ret); ++ "zoned: failed to read device zone info: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + +@@ -3638,72 +3634,73 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_verify_dev_extents(fs_info); + if (ret) { + btrfs_err(fs_info, +- "failed to verify dev extents against chunks: %d", +- ret); ++ "failed to verify dev extents against chunks: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + ret = btrfs_recover_balance(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to recover balance: %d", ret); ++ btrfs_err(fs_info, "failed to recover balance: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_init_dev_stats(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init dev_stats: %d", ret); ++ btrfs_err(fs_info, "failed to init dev_stats: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_init_dev_replace(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init dev_replace: %d", ret); ++ btrfs_err(fs_info, "failed to init dev_replace: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_check_zoned_mode(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to initialize zoned mode: %d", +- ret); ++ btrfs_err(fs_info, "failed to initialize zoned mode: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_sysfs_add_fsid(fs_devices); + if (ret) { +- btrfs_err(fs_info, "failed to init sysfs fsid interface: %d", +- ret); ++ btrfs_err(fs_info, "failed to init sysfs fsid interface: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_sysfs_add_mounted(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init sysfs interface: %d", ret); ++ btrfs_err(fs_info, "failed to init sysfs interface: %pe", ERR_PTR(ret)); + goto fail_fsdev_sysfs; + } + + ret = btrfs_init_space_info(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to initialize space info: %d", ret); ++ btrfs_err(fs_info, "failed to initialize space info: %pe", ERR_PTR(ret)); + goto fail_sysfs; + } + + ret = btrfs_read_block_groups(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to read block groups: %d", ret); ++ btrfs_err(fs_info, "failed to read block groups: %pe", ERR_PTR(ret)); + goto fail_sysfs; + } + + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + ret = btrfs_populate_fully_remapped_bgs_list(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to populate fully_remapped_bgs list: %d", ret); ++ btrfs_err(fs_info, "failed to populate fully_remapped_bgs list: %pe", ++ ERR_PTR(ret)); + goto fail_sysfs; + } + } + + ret = btrfs_init_writeback_bio_size(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to get optimum writeback size: %d", +- ret); ++ btrfs_err(fs_info, "failed to get optimum writeback size: %pe", ++ ERR_PTR(ret)); + goto fail_sysfs; + } + +@@ -3759,7 +3756,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + fs_info->fs_root = btrfs_get_fs_root(fs_info, BTRFS_FS_TREE_OBJECTID, true); + if (IS_ERR(fs_info->fs_root)) { + ret = PTR_ERR(fs_info->fs_root); +- btrfs_err(fs_info, "failed to read fs tree: %d", ret); ++ btrfs_err(fs_info, "failed to read fs tree: %pe", ERR_PTR(ret)); + fs_info->fs_root = NULL; + goto fail_qgroup; + } +@@ -3780,7 +3777,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + btrfs_info(fs_info, "checking UUID tree"); + ret = btrfs_check_uuid_tree(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to check the UUID tree: %d", ret); ++ btrfs_err(fs_info, "failed to check the UUID tree: %pe", ERR_PTR(ret)); + close_ctree(fs_info); + return ret; + } +@@ -3896,8 +3893,8 @@ static int write_dev_supers(struct btrfs_device *device, + continue; + } else if (ret < 0) { + btrfs_err(device->fs_info, +- "couldn't get super block location for mirror %d error %d", +- i, ret); ++ "couldn't get super block location for mirror %d error %pe", ++ i, ERR_PTR(ret)); + atomic_inc(&device->sb_write_errors); + continue; + } +@@ -3915,8 +3912,8 @@ static int write_dev_supers(struct btrfs_device *device, + GFP_NOFS); + if (IS_ERR(folio)) { + btrfs_err(device->fs_info, +- "couldn't get super block page for bytenr %llu error %ld", +- bytenr, PTR_ERR(folio)); ++ "couldn't get super block page for bytenr %llu error %pe", ++ bytenr, folio); + atomic_inc(&device->sb_write_errors); + continue; + } +@@ -4398,6 +4395,21 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + */ + flush_workqueue(fs_info->fixup_workers); + ++ /* ++ * After we entered close_ctree() autodefrag could be running and before ++ * we parked the cleaner kthread, it dirtied folios of some inode. ++ * We don't want to leave any delalloc here, it may be flushed any time ++ * after this point and result in ordered extents that create delayed ++ * iputs after flushed the ordered extent queues further below, run ++ * delayed iputs and set BTRFS_FS_STATE_NO_DELAYED_IPUT. If we are ++ * mounted with flushoncommit, then btrfs_commit_super() called below ++ * will flush delalloc and wait for ordered extents but we end up ++ * getting delayed iputs than are never run. So flush delalloc and wait ++ * for ordered extents. ++ */ ++ btrfs_start_delalloc_roots(fs_info, LONG_MAX, false); ++ btrfs_wait_ordered_roots(fs_info, U64_MAX, NULL); ++ + /* + * Handle the error fs first, as it will flush and wait for all ordered + * extents. This will generate delayed iputs, thus we want to handle +@@ -4528,7 +4540,7 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + if (!btrfs_is_shutdown(fs_info)) { + ret = btrfs_commit_super(fs_info); + if (ret) +- btrfs_err(fs_info, "commit super block returned %d", ret); ++ btrfs_err(fs_info, "commit super block returned %pe", ERR_PTR(ret)); + } + } + +@@ -4567,6 +4579,13 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + free_root_pointers(fs_info, true); + btrfs_free_fs_roots(fs_info); + ++ /* ++ * Drop metadata left stranded ahead of a zone write pointer while the ++ * endio workqueues are still up, so the final iput() of the btree inode ++ * below does not hang submitting a write that can no longer complete. ++ */ ++ btrfs_zoned_release_dirty_metadata(fs_info); ++ + /* + * We must make sure there is not any read request to + * submit after we stop all workers. +@@ -5015,6 +5034,7 @@ static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info) + btrfs_assert_delayed_root_empty(fs_info); + btrfs_destroy_all_delalloc_inodes(fs_info); + btrfs_drop_all_logs(fs_info); ++ btrfs_zoned_release_dirty_metadata(fs_info); + btrfs_free_all_qgroup_pertrans(fs_info); + mutex_unlock(&fs_info->transaction_kthread_mutex); + +diff --git a/fs/btrfs/disk-io.h b/fs/btrfs/disk-io.h +--- a/fs/btrfs/disk-io.h ++++ b/fs/btrfs/disk-io.h +@@ -15,6 +15,7 @@ + struct block_device; + struct super_block; + struct extent_buffer; ++struct btrfs_eb_prealloc; + struct btrfs_device; + struct btrfs_fs_devices; + struct btrfs_fs_info; +@@ -48,6 +49,7 @@ struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + struct btrfs_tree_parent_check *check); + struct extent_buffer *btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 bytenr, u64 owner_root, + int level); + int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info); +diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c +--- a/fs/btrfs/extent-io-tree.c ++++ b/fs/btrfs/extent-io-tree.c +@@ -334,6 +334,21 @@ static inline struct extent_state *tree_search(struct extent_io_tree *tree, u64 + return tree_search_for_insert(tree, offset, NULL, NULL); + } + ++static void validate_extent_state(const struct extent_io_tree *tree, ++ const struct extent_state *state) ++{ ++ u32 blocksize; ++ ++ if (tree->owner != IO_TREE_INODE_IO) ++ return; ++ ++ blocksize = btrfs_extent_io_tree_to_fs_info(tree)->sectorsize; ++ ASSERT(IS_ALIGNED(state->start, blocksize) && ++ IS_ALIGNED(state->end + 1, blocksize), ++ "unaligned extent state, blocksize=%u start=%llu end=%llu state=0x%x", ++ blocksize, state->start, state->end, state->state); ++} ++ + #define extent_io_tree_panic(tree, state, opname, err) \ + btrfs_panic(btrfs_extent_io_tree_to_fs_info((tree)), (err), \ + "extent io tree error on %s state start %llu end %llu", \ +@@ -429,6 +444,8 @@ static struct extent_state *insert_state(struct extent_io_tree *tree, + const u64 end = state->end + 1; + const bool try_merge = !(bits & (EXTENT_LOCK_BITS | EXTENT_BOUNDARY)); + ++ validate_extent_state(tree, state); ++ + set_state_bits(tree, state, bits, changeset); + + node = &tree->state.rb_node; +@@ -481,6 +498,8 @@ static void insert_state_fast(struct extent_io_tree *tree, + struct rb_node *parent, unsigned bits, + struct extent_changeset *changeset) + { ++ validate_extent_state(tree, state); ++ + set_state_bits(tree, state, bits, changeset); + rb_link_node(&state->rb_node, parent, node); + rb_insert_color(&state->rb_node, &tree->state); +@@ -533,6 +552,8 @@ static int split_state(struct extent_io_tree *tree, struct extent_state *orig, + } + } + ++ validate_extent_state(tree, orig); ++ validate_extent_state(tree, prealloc); + rb_link_node(&prealloc->rb_node, parent, node); + rb_insert_color(&prealloc->rb_node, &tree->state); + +diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c +--- a/fs/btrfs/extent-tree.c ++++ b/fs/btrfs/extent-tree.c +@@ -4757,7 +4757,7 @@ static noinline int find_free_extent(struct btrfs_root *root, + + /* Checks */ + ffe_ctl->search_start = round_up(ffe_ctl->found_offset, +- fs_info->stripesize); ++ fs_info->sectorsize); + + /* move on to the next group */ + if (ffe_ctl->search_start + ffe_ctl->num_bytes > +@@ -5260,10 +5260,11 @@ btrfs_init_new_buffer(struct btrfs_trans_handle *trans, struct btrfs_root *root, + enum btrfs_lock_nesting nest) + { + struct btrfs_fs_info *fs_info = root->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + struct extent_buffer *buf; + u64 lockdep_owner = owner; + +- buf = btrfs_find_create_tree_block(fs_info, bytenr, owner, level); ++ buf = btrfs_find_create_tree_block(fs_info, &pa, bytenr, owner, level); + if (IS_ERR(buf)) + return buf; + +@@ -5880,8 +5881,8 @@ static int maybe_drop_reference(struct btrfs_trans_handle *trans, struct btrfs_r + ret = btrfs_qgroup_trace_subtree(trans, next, generation, level - 1); + if (ret) { + btrfs_err_rl(root->fs_info, +-"error %d accounting shared subtree, quota is out of sync, rescan required", +- ret); ++"error %pe accounting shared subtree, quota is out of sync, rescan required", ++ ERR_PTR(ret)); + } + } + +@@ -5917,6 +5918,7 @@ static noinline int do_walk_down(struct btrfs_trans_handle *trans, + struct walk_control *wc) + { + struct btrfs_fs_info *fs_info = root->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + u64 bytenr; + u64 generation; + u64 owner_root = 0; +@@ -5939,7 +5941,7 @@ static noinline int do_walk_down(struct btrfs_trans_handle *trans, + + bytenr = btrfs_node_blockptr(path->nodes[level], path->slots[level]); + +- next = btrfs_find_create_tree_block(fs_info, bytenr, btrfs_root_id(root), ++ next = btrfs_find_create_tree_block(fs_info, &pa, bytenr, btrfs_root_id(root), + level - 1); + if (IS_ERR(next)) + return PTR_ERR(next); +@@ -6096,8 +6098,8 @@ static noinline int walk_up_proc(struct btrfs_trans_handle *trans, + ret = btrfs_qgroup_trace_leaf_items(trans, eb); + if (ret) { + btrfs_err_rl(fs_info, +- "error %d accounting leaf items, quota is out of sync, rescan required", +- ret); ++ "error %pe accounting leaf items, quota is out of sync, rescan required", ++ ERR_PTR(ret)); + } + } + } +@@ -6498,8 +6500,8 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc + ret = btrfs_qgroup_cleanup_dropped_subvolume(fs_info, rootid); + if (ret < 0) + btrfs_warn_rl(fs_info, +- "failed to cleanup qgroup 0/%llu: %d", +- rootid, ret); ++ "failed to cleanup qgroup 0/%llu: %pe", ++ rootid, ERR_PTR(ret)); + ret = 0; + } + /* +@@ -6914,8 +6916,8 @@ int btrfs_trim_fs(struct btrfs_fs_info *fs_info, struct fstrim_range *range) + + if (bg_failed) + btrfs_warn(fs_info, +- "failed to trim %llu block group(s), first error %d", +- bg_failed, bg_ret); ++ "failed to trim %llu block group(s), first error %pe", ++ bg_failed, ERR_PTR(bg_ret)); + + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; +@@ -6925,8 +6927,8 @@ int btrfs_trim_fs(struct btrfs_fs_info *fs_info, struct fstrim_range *range) + + if (dev_failed) + btrfs_warn(fs_info, +- "failed to trim %llu device(s), first error %d", +- dev_failed, dev_ret); ++ "failed to trim %llu device(s), first error %pe", ++ dev_failed, ERR_PTR(dev_ret)); + range->len = trimmed; + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; +diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1392,6 +1392,22 @@ static void lock_extents_for_read(struct btrfs_inode *inode, u64 start, u64 end, + } + } + ++static void assert_folio_range(const struct btrfs_inode *inode, ++ u64 start, u64 end) ++{ ++ const u32 blocksize = inode->root->fs_info->sectorsize; ++ ++ /* ++ * For btrfs page cache, a folio always contains at least one block, ++ * so the range should always be block size aligned. ++ */ ++ ASSERT(IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize), ++ "blocksize=%u root=%lld ino=%llu start=%llu end=%llu mapping min order=%u", ++ blocksize, btrfs_root_id(inode->root), btrfs_ino(inode), ++ start, end, ++ mapping_min_folio_order(inode->vfs_inode.i_mapping)); ++} ++ + int btrfs_read_folio(struct file *file, struct folio *folio) + { + struct inode *vfs_inode = folio->mapping->host; +@@ -1407,6 +1423,7 @@ int btrfs_read_folio(struct file *file, struct folio *folio) + struct fsverity_info *vi = NULL; + int ret; + ++ assert_folio_range(inode, start, end); + lock_extents_for_read(inode, start, end, &cached_state); + if (folio_pos(folio) < i_size_read(vfs_inode)) + vi = fsverity_get_info(vfs_inode); +@@ -1698,13 +1715,13 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, + last_finished_delalloc_end = found_start + found_len; + if (unlikely(ret < 0)) + btrfs_err_rl(fs_info, +-"failed to run delalloc range, root=%lld ino=%llu folio=%llu submit_bitmap=%*pbl start=%llu len=%u: %d", ++"failed to run delalloc range, root=%lld ino=%llu folio=%llu submit_bitmap=%*pbl start=%llu len=%u: %pe", + btrfs_root_id(inode->root), + btrfs_ino(inode), + folio_pos(folio), + blocks_per_folio, + bio_ctrl->submit_bitmap, +- found_start, found_len, ret); ++ found_start, found_len, ERR_PTR(ret)); + } else { + /* + * We've hit an error during previous delalloc range, +@@ -1914,6 +1931,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, + ASSERT(start >= folio_start, "start=%llu folio_start=%llu", start, folio_start); + ASSERT(end <= folio_end, "start=%llu len=%u folio_start=%llu folio_size=%zu", + start, len, folio_start, folio_size(folio)); ++ assert_folio_range(inode, folio_start, folio_end - 1); + + /* + * We are about to checksum and write out the data, so it must not be +@@ -2081,10 +2099,10 @@ static int extent_writepage(struct folio *folio, struct btrfs_bio_ctrl *bio_ctrl + return 0; + if (unlikely(ret < 0)) + btrfs_err_rl(fs_info, +-"failed to submit blocks, root=%lld inode=%llu folio=%llu submit_bitmap=%*pbl: %d", ++"failed to submit blocks, root=%lld inode=%llu folio=%llu submit_bitmap=%*pbl: %pe", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio), blocks_per_folio, +- bio_ctrl->submit_bitmap, ret); ++ bio_ctrl->submit_bitmap, ERR_PTR(ret)); + + bio_ctrl->wbc->nr_to_write--; + +@@ -2976,13 +2994,25 @@ void btrfs_readahead(struct readahead_control *rac) + struct extent_map *em_cached = NULL; + struct fsverity_info *vi = NULL; + ++ assert_folio_range(inode, start, end); + lock_extents_for_read(inode, start, end, &cached_state); ++ /* We don't use cached state for a bulk unlock, just free it. */ ++ btrfs_free_extent_state(cached_state); + if (start < i_size_read(vfs_inode)) + vi = fsverity_get_info(vfs_inode); +- while ((folio = readahead_folio(rac)) != NULL) +- btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi); ++ while ((folio = readahead_folio(rac)) != NULL) { ++ /* ++ * Read start and end before btrfs_do_readpage(). It unlocks the ++ * folio, so our reference might not be valid after. ++ */ ++ const u64 folio_start = folio_pos(folio); ++ const u64 folio_end = folio_start + folio_size(folio) - 1; + +- btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state); ++ btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi); ++ /* Only unlock the range we locked, even if readahead expands. */ ++ if (folio_start >= start && folio_end <= end) ++ btrfs_unlock_extent(&inode->io_tree, folio_start, folio_end, NULL); ++ } + + if (em_cached) + btrfs_free_extent_map(em_cached); +@@ -3298,12 +3328,9 @@ void btrfs_uninhibit_all_eb_writeback(struct btrfs_trans_handle *trans) + trans->inhibited_ebs_hand = 0; + } + +-static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, +- u64 start) ++static void init_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct extent_buffer *eb, u64 start) + { +- struct extent_buffer *eb = NULL; +- +- eb = kmem_cache_zalloc(extent_buffer_cache, GFP_NOFS|__GFP_NOFAIL); + eb->start = start; + eb->len = fs_info->nodesize; + eb->fs_info = fs_info; +@@ -3316,7 +3343,15 @@ static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info + refcount_set(&eb->refs, 1); + + ASSERT(eb->len <= BTRFS_MAX_METADATA_BLOCKSIZE); ++} + ++static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ u64 start) ++{ ++ struct extent_buffer *eb; ++ ++ eb = kmem_cache_zalloc(extent_buffer_cache, GFP_NOFS | __GFP_NOFAIL); ++ init_extent_buffer(fs_info, eb, start); + return eb; + } + +@@ -3613,7 +3648,7 @@ static bool check_eb_alignment(struct btrfs_fs_info *fs_info, u64 start) + * The caller needs to free the existing folios and retry using the same order. + */ + static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, +- struct btrfs_folio_state *prealloc, ++ struct btrfs_eb_prealloc *pa, + struct extent_buffer **found_eb_ret) + { + +@@ -3635,6 +3670,7 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + if (!ret) + goto finish; + ++ /* ret == -EEXIST: a folio already lives at this index. */ + existing_folio = filemap_lock_folio(mapping, index + i); + /* The page cache only exists for a very short time, just retry. */ + if (IS_ERR(existing_folio)) +@@ -3643,7 +3679,27 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + /* For now, we should only have single-page folios for btree inode. */ + ASSERT(folio_nr_pages(existing_folio) == 1); + ++ /* ++ * TODO: Special handling for a corner case where the order of ++ * folios mismatch between the new eb and filemap. ++ * ++ * This happens when: ++ * ++ * - the new eb is using higher order folio ++ * ++ * - the filemap is still using 0-order folios for the range ++ * This can happen at the previous eb allocation, and we don't ++ * have higher order folio for the call. ++ * ++ * - the existing eb has already been freed ++ * ++ * In this case, we have to free the existing folios first, and ++ * re-allocate using the same order. ++ * Thankfully this is not going to happen yet, as we're still ++ * using 0-order folios. ++ */ + if (folio_size(existing_folio) != eb->folio_size) { ++ DEBUG_WARN("folio order mismatch between new eb and filemap"); + folio_unlock(existing_folio); + folio_put(existing_folio); + return -EAGAIN; +@@ -3674,8 +3730,10 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + eb->folio_size = folio_size(eb->folios[i]); + eb->folio_shift = folio_shift(eb->folios[i]); + /* Should not fail, as we have preallocated the memory. */ +- ret = attach_extent_buffer_folio(eb, eb->folios[i], prealloc); ++ ret = attach_extent_buffer_folio(eb, eb->folios[i], pa->bfs); + ASSERT(!ret); ++ /* The subpage state, if any, is now attached to the folio or freed. */ ++ pa->bfs = NULL; + /* + * To inform we have an extra eb under allocation, so that + * detach_extent_buffer_page() won't release the folio private when the +@@ -3690,13 +3748,104 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + return 0; + } + ++/* ++ * Allocate the extent_buffer, its folios, and btrfs_folio_state, if needed. ++ * ++ * @pa: The holder struct to do the allocation in. ++ * @nowait: Whether to do a speculative GFP_NOWAIT allocation while holding locks. ++ * ++ * Return 0 on success and a negative errno otherwise. On failure, pa->eb/bfs ++ * will be NULL. If @nowait=true, then on ENOMEM, mark @pa->needs_prealloc and ++ * return -EAGAIN to signal the caller to unlock and retry. ++ */ ++int btrfs_init_eb_prealloc(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, bool nowait) ++{ ++ gfp_t gfp = nowait ? GFP_NOWAIT : GFP_NOFS | __GFP_NOFAIL; ++ int ret; ++ ++ ASSERT(!pa->eb, "unexpected non-null eb: %p", pa->eb); ++ ASSERT(!pa->bfs, "unexpected non-null bfs: %p", pa->bfs); ++ pa->needs_prealloc = false; ++ ++ pa->eb = kmem_cache_zalloc(extent_buffer_cache, gfp); ++ if (!pa->eb) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ /* alloc_eb_folio_array() needs len; init_extent_buffer() sets it again later. */ ++ pa->eb->len = fs_info->nodesize; ++ ++ /* ++ * Preallocate folio private for subpage case, so that we won't ++ * allocate memory with i_private_lock nor page lock hold. ++ * ++ * The memory will be freed by attach_extent_buffer_page() or freed ++ * manually if we exit earlier. ++ */ ++ if (btrfs_meta_is_subpage(fs_info)) { ++ pa->bfs = btrfs_alloc_folio_state(fs_info, PAGE_SIZE, ++ BTRFS_SUBPAGE_METADATA, gfp); ++ if (IS_ERR(pa->bfs)) { ++ ret = PTR_ERR(pa->bfs); ++ pa->bfs = NULL; ++ goto free_eb; ++ } ++ } ++ ++ /* ++ * Allocate pages without attaching them. Caller is ultimately responsible ++ * for attaching the folios to the mapping with attach_eb_folio_to_filemap(). ++ */ ++ ret = alloc_eb_folio_array(pa->eb, gfp | __GFP_MOVABLE); ++ if (ret < 0) ++ goto free_bfs; ++ ++ return 0; ++ ++free_bfs: ++ btrfs_free_folio_state(pa->bfs); ++ pa->bfs = NULL; ++free_eb: ++ kmem_cache_free(extent_buffer_cache, pa->eb); ++ pa->eb = NULL; ++out: ++ if (nowait && ret == -ENOMEM) { ++ pa->needs_prealloc = true; ++ ret = -EAGAIN; ++ } ++ return ret; ++} ++ ++/* ++ * Used to cleanup a btrfs_eb_prealloc which had its contents allocated but ++ * folios not yet attached and eb/bfs consumed, and refs still 0. ++ * ++ * Safe to call on a fully used btrfs_eb_prealloc as the internal structs will ++ * be null once they are owned by the context using them. ++ */ ++void btrfs_free_eb_prealloc(struct btrfs_eb_prealloc *pa) ++{ ++ if (!pa->eb) ++ return; ++ ++ for (int i = 0; i < num_extent_pages(pa->eb); i++) { ++ if (pa->eb->folios[i]) ++ folio_put(pa->eb->folios[i]); ++ } ++ btrfs_free_folio_state(pa->bfs); ++ kmem_cache_free(extent_buffer_cache, pa->eb); ++ pa->eb = NULL; ++ pa->bfs = NULL; ++} ++ + struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 start, u64 owner_root, int level) + { + int attached = 0; + struct extent_buffer *eb; + struct extent_buffer *existing_eb = NULL; +- struct btrfs_folio_state *prealloc = NULL; + u64 lockdep_owner = owner_root; + bool page_contig = true; + bool uptodate = true; +@@ -3720,9 +3869,14 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + if (eb) + return eb; + +- eb = __alloc_extent_buffer(fs_info, start); +- if (!eb) +- return ERR_PTR(-ENOMEM); ++ if (!pa->eb) { ++ ret = btrfs_init_eb_prealloc(fs_info, pa, pa->supports_nowait); ++ if (ret) ++ return ERR_PTR(ret); ++ } ++ eb = pa->eb; ++ pa->eb = NULL; ++ init_extent_buffer(fs_info, eb, start); + + /* + * The reloc trees are just snapshots, so we need them to appear to be +@@ -3733,66 +3887,18 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + + btrfs_set_buffer_lockdep_class(lockdep_owner, eb, level); + +- /* +- * Preallocate folio private for subpage case, so that we won't +- * allocate memory with i_private_lock nor page lock hold. +- * +- * The memory will be freed by attach_extent_buffer_page() or freed +- * manually if we exit earlier. +- */ +- if (btrfs_meta_is_subpage(fs_info)) { +- prealloc = btrfs_alloc_folio_state(fs_info, PAGE_SIZE, BTRFS_SUBPAGE_METADATA); +- if (IS_ERR(prealloc)) { +- ret = PTR_ERR(prealloc); +- goto out; +- } +- } +- +-reallocate: +- /* +- * Allocate all pages first. These will be attached to btree_inode->i_mapping +- * below (added to LRU, served by btree_migrate_folio), so request +- * __GFP_MOVABLE so the page allocator places them in MOVABLE pageblocks. +- */ +- ret = alloc_eb_folio_array(eb, GFP_NOFS | __GFP_NOFAIL | __GFP_MOVABLE); +- if (ret < 0) { +- btrfs_free_folio_state(prealloc); +- goto out; +- } +- + /* Attach all pages to the filemap. */ + for (int i = 0; i < num_extent_folios(eb); i++) { + struct folio *folio; + +- ret = attach_eb_folio_to_filemap(eb, i, prealloc, &existing_eb); ++ ret = attach_eb_folio_to_filemap(eb, i, pa, &existing_eb); + if (ret > 0) { + ASSERT(existing_eb); + goto out; + } +- +- /* +- * TODO: Special handling for a corner case where the order of +- * folios mismatch between the new eb and filemap. +- * +- * This happens when: +- * +- * - the new eb is using higher order folio +- * +- * - the filemap is still using 0-order folios for the range +- * This can happen at the previous eb allocation, and we don't +- * have higher order folio for the call. +- * +- * - the existing eb has already been freed +- * +- * In this case, we have to free the existing folios first, and +- * re-allocate using the same order. +- * Thankfully this is not going to happen yet, as we're still +- * using 0-order folios. +- */ +- if (unlikely(ret == -EAGAIN)) { +- DEBUG_WARN("folio order mismatch between new eb and filemap"); +- goto reallocate; +- } ++ /* -EAGAIN: folio order mismatch, unreachable with 0-order folios. */ ++ if (ret < 0) ++ goto out; + attached++; + + /* +@@ -3869,6 +3975,10 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + out: + WARN_ON(!refcount_dec_and_test(&eb->refs)); + ++ /* Attach hands off pa->bfs; free it if we bailed first. */ ++ btrfs_free_folio_state(pa->bfs); ++ pa->bfs = NULL; ++ + /* + * Any attached folios need to be detached before we unlock them. This + * is because when we're inserting our new folios into the mapping, and +@@ -4030,6 +4140,32 @@ void free_extent_buffer_stale(struct extent_buffer *eb) + release_extent_buffer(eb); + } + ++static void clear_extent_buffer_dirty(struct extent_buffer *eb) ++{ ++ struct btrfs_fs_info *fs_info = eb->fs_info; ++ ++ if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) ++ return; ++ ++ buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY); ++ percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len, ++ fs_info->dirty_metadata_batch); ++ ++ for (int i = 0; i < num_extent_folios(eb); i++) { ++ struct folio *folio = eb->folios[i]; ++ bool last; ++ ++ if (!folio_test_dirty(folio)) ++ continue; ++ folio_lock(folio); ++ last = btrfs_meta_folio_clear_and_test_dirty(folio, eb); ++ if (last) ++ btrfs_clear_folio_dirty_tag(folio); ++ folio_unlock(folio); ++ } ++ WARN_ON(refcount_read(&eb->refs) == 0); ++} ++ + void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + struct extent_buffer *eb) + { +@@ -4054,26 +4190,42 @@ void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + return; + } + +- if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) ++ clear_extent_buffer_dirty(eb); ++} ++ ++/* ++ * On a zoned filesystem a freed tree block is kept dirty and flagged as ++ * EXTENT_BUFFER_ZONED_ZEROOUT so a later writeback zeroes it out and advances ++ * the zone write pointer. Such buffers still dirty when the filesystem is torn ++ * down can no longer be written back and are stale; if left dirty they hang the ++ * final iput() of the btree inode. Drop their dirty state, and the deferred ++ * zero-out along with it. ++ */ ++void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info) ++{ ++ struct eb_batch batch; ++ unsigned long index = 0; ++ ++ if (!btrfs_is_zoned(fs_info)) + return; + +- buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY); +- percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len, +- fs_info->dirty_metadata_batch); ++ btrfs_zoned_meta_io_lock(fs_info); ++ eb_batch_init(&batch); ++ while (buffer_tree_get_ebs_tag(fs_info, &index, ULONG_MAX, ++ PAGECACHE_TAG_DIRTY, &batch)) { ++ struct extent_buffer *eb; + +- for (int i = 0; i < num_extent_folios(eb); i++) { +- struct folio *folio = eb->folios[i]; +- bool last; +- +- if (!folio_test_dirty(folio)) +- continue; +- folio_lock(folio); +- last = btrfs_meta_folio_clear_and_test_dirty(folio, eb); +- if (last) +- btrfs_clear_folio_dirty_tag(folio); +- folio_unlock(folio); ++ while ((eb = eb_batch_next(&batch)) != NULL) { ++ btrfs_tree_lock(eb); ++ if (test_and_clear_bit(EXTENT_BUFFER_ZONED_ZEROOUT, ++ &eb->bflags)) ++ clear_extent_buffer_dirty(eb); ++ btrfs_tree_unlock(eb); ++ } ++ eb_batch_release(&batch); ++ cond_resched(); + } +- WARN_ON(refcount_read(&eb->refs) == 0); ++ btrfs_zoned_meta_io_unlock(fs_info); + } + + void set_extent_buffer_dirty(struct extent_buffer *eb) +@@ -4923,6 +5075,8 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, + .level = level, + .transid = gen + }; ++ /* Readahead is best effort so prefer to fail rather than block in reclaim. */ ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + struct extent_buffer *eb; + int ret; + +@@ -4931,7 +5085,7 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, + check.has_first_key = true; + } + +- eb = btrfs_find_create_tree_block(fs_info, bytenr, owner_root, level); ++ eb = btrfs_find_create_tree_block(fs_info, &pa, bytenr, owner_root, level); + if (IS_ERR(eb)) + return; + +diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h +--- a/fs/btrfs/extent_io.h ++++ b/fs/btrfs/extent_io.h +@@ -119,6 +119,25 @@ struct extent_buffer { + #endif + }; + ++/* ++ * Wrapper struct for managing preallocating an extent_buffer, its folios and a ++ * btrfs_folio_state if needed. ++ * ++ * Only used to mediate allocation, do not refer to the eb directly if not ++ * returned from a successful eb allocating API. ++ * ++ * The eb folios and bfs should generally not be fully attached, except briefly ++ * before they are NULLed in the struct after successful attachment. ++ */ ++struct btrfs_eb_prealloc { ++ struct extent_buffer *eb; ++ struct btrfs_folio_state *bfs; ++ /* eb alloc may use GFP_NOWAIT; caller can drop locks and retry. */ ++ bool supports_nowait; ++ /* GFP_NOWAIT eb alloc failed; preallocate again and retry. */ ++ bool needs_prealloc; ++}; ++ + struct btrfs_eb_write_context { + struct writeback_control *wbc; + struct extent_buffer *eb; +@@ -271,7 +290,11 @@ int set_folio_extent_mapped(struct folio *folio); + void clear_folio_extent_mapped(struct folio *folio); + + struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 start, u64 owner_root, int level); ++int btrfs_init_eb_prealloc(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, bool nowait); ++void btrfs_free_eb_prealloc(struct btrfs_eb_prealloc *pa); + struct extent_buffer *alloc_dummy_extent_buffer(struct btrfs_fs_info *fs_info, + u64 start); + struct extent_buffer *btrfs_clone_extent_buffer(const struct extent_buffer *src); +@@ -393,6 +416,7 @@ void extent_clear_unlock_delalloc(struct btrfs_inode *inode, u64 start, u64 end, + u32 bits_to_clear, unsigned long page_ops); + void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + struct extent_buffer *buf); ++void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info); + + static inline void btrfs_clear_folio_dirty_tag(struct folio *folio) + { +diff --git a/fs/btrfs/fiemap.c b/fs/btrfs/fiemap.c +--- a/fs/btrfs/fiemap.c ++++ b/fs/btrfs/fiemap.c +@@ -641,7 +641,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + u64 prev_extent_end; + u64 range_start; + u64 range_end; +- const u64 sectorsize = inode->root->fs_info->sectorsize; ++ const u32 sectorsize = inode->root->fs_info->sectorsize; + bool stopped = false; + int ret; + +diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c +--- a/fs/btrfs/file.c ++++ b/fs/btrfs/file.c +@@ -1573,8 +1573,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) + else + btrfs_inode_lock(inode, BTRFS_ILOCK_MMAP); + +- atomic_inc(&root->log_batch); +- + /* + * Before we acquired the inode's lock and the mmap lock, someone may + * have dirtied more pages in the target range. We need to make sure +@@ -1657,8 +1655,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) + if (ret) + goto out_release_extents; + +- atomic_inc(&root->log_batch); +- + if (skip_inode_logging(&ctx)) { + /* + * We've had everything committed since the last time we were +@@ -2663,8 +2659,8 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len) + + lockstart = round_up(offset, fs_info->sectorsize); + lockend = round_down(offset + len, fs_info->sectorsize) - 1; +- same_block = (BTRFS_BYTES_TO_BLKS(fs_info, offset)) +- == (BTRFS_BYTES_TO_BLKS(fs_info, offset + len - 1)); ++ same_block = (offset >> fs_info->sectorsize_bits) == ++ ((offset + len - 1) >> fs_info->sectorsize_bits); + /* + * Only do this if we are in the same block and we aren't doing the + * entire block. +@@ -2868,7 +2864,7 @@ enum { + static int btrfs_zero_range_check_range_boundary(struct btrfs_inode *inode, + u64 offset) + { +- const u64 sectorsize = inode->root->fs_info->sectorsize; ++ const u32 sectorsize = inode->root->fs_info->sectorsize; + struct extent_map *em; + int ret; + +@@ -2898,7 +2894,7 @@ static int btrfs_zero_range(struct inode *inode, + struct extent_changeset *data_reserved = NULL; + int ret; + u64 alloc_hint = 0; +- const u64 sectorsize = fs_info->sectorsize; ++ const u32 sectorsize = fs_info->sectorsize; + const u64 orig_start = offset; + const u64 orig_end = offset + len - 1; + u64 alloc_start = round_down(offset, sectorsize); +@@ -2947,8 +2943,8 @@ static int btrfs_zero_range(struct inode *inode, + } + btrfs_free_extent_map(em); + +- if (BTRFS_BYTES_TO_BLKS(fs_info, offset) == +- BTRFS_BYTES_TO_BLKS(fs_info, offset + len - 1)) { ++ if ((offset >> fs_info->sectorsize_bits) == ++ ((offset + len - 1) >> fs_info->sectorsize_bits)) { + em = btrfs_get_extent(BTRFS_I(inode), NULL, alloc_start, sectorsize); + if (IS_ERR(em)) { + ret = PTR_ERR(em); +diff --git a/fs/btrfs/fs.c b/fs/btrfs/fs.c +--- a/fs/btrfs/fs.c ++++ b/fs/btrfs/fs.c +@@ -127,20 +127,9 @@ void btrfs_csum_final(struct btrfs_csum_ctx *ctx, u8 *out) + } + + /* +- * We support the following block sizes for all systems: +- * +- * - 4K +- * This is the most common block size. For PAGE SIZE > 4K cases the subpage +- * mode is used. +- * +- * - PAGE_SIZE +- * The straightforward block size to support. +- * +- * And extra support for the following block sizes based on the kernel config: +- * +- * - MIN_BLOCKSIZE +- * This is either 4K (regular builds) or 2K (debug builds) +- * This allows testing subpage routines on x86_64. ++ * For regular builds, any block size <= page size is supported. ++ * For experimental builds, any block size between BTRFS_MIN_BLOCKSIZE ++ * and BTRFS_MAX_BLOCKSIZE (inclusive) is supported. + */ + bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) + { +@@ -148,7 +137,7 @@ bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) + ASSERT(is_power_of_2(blocksize) && blocksize >= BTRFS_MIN_BLOCKSIZE && + blocksize <= BTRFS_MAX_BLOCKSIZE); + +- if (blocksize == PAGE_SIZE || blocksize == SZ_4K || blocksize == BTRFS_MIN_BLOCKSIZE) ++ if (blocksize <= PAGE_SIZE) + return true; + #ifdef CONFIG_BTRFS_EXPERIMENTAL + /* +diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h +--- a/fs/btrfs/fs.h ++++ b/fs/btrfs/fs.h +@@ -289,7 +289,8 @@ enum { + BTRFS_MOUNT_IGNOREBADROOTS | \ + BTRFS_MOUNT_IGNOREDATACSUMS | \ + BTRFS_MOUNT_IGNOREMETACSUMS | \ +- BTRFS_MOUNT_IGNORESUPERFLAGS) ++ BTRFS_MOUNT_IGNORESUPERFLAGS | \ ++ BTRFS_MOUNT_USEBACKUPROOT) + + /* + * Compat flags that we support. If any incompat flags are set other than the +@@ -890,7 +891,6 @@ struct btrfs_fs_info { + u32 sectorsize_bits; + u32 block_min_order; + u32 block_max_order; +- u32 stripesize; + u32 writeback_bio_size; + u32 csum_size; + u32 csums_per_leaf; +@@ -1060,8 +1060,6 @@ static inline u64 btrfs_calc_metadata_size(const struct btrfs_fs_info *fs_info, + #define BTRFS_MAX_EXTENT_ITEM_SIZE(r) ((BTRFS_LEAF_DATA_SIZE(r->fs_info) >> 4) - \ + sizeof(struct btrfs_item)) + +-#define BTRFS_BYTES_TO_BLKS(fs_info, bytes) ((bytes) >> (fs_info)->sectorsize_bits) +- + static inline bool btrfs_is_zoned(const struct btrfs_fs_info *fs_info) + { + return IS_ENABLED(CONFIG_BLK_DEV_ZONED) && fs_info->zone_size > 0; +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -250,8 +250,8 @@ static void print_data_reloc_error(const struct btrfs_inode *inode, u64 file_off + + ret = extent_from_logical(fs_info, logical, &path, &found_key, &flags); + if (ret < 0) { +- btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %d", +- logical, ret); ++ btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %pe", ++ logical, ERR_PTR(ret)); + return; + } + eb = path.nodes[0]; +@@ -869,7 +869,7 @@ static void compress_file_range(struct btrfs_work *work) + struct btrfs_inode *inode = async_chunk->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct compressed_bio *cb = NULL; +- u64 blocksize = fs_info->sectorsize; ++ const u32 blocksize = fs_info->sectorsize; + u64 start = async_chunk->start; + u64 end = async_chunk->end; + u64 actual_end; +@@ -1019,9 +1019,10 @@ static void submit_uncompressed_range(struct btrfs_inode *inode, + btrfs_folio_end_lock(inode->root->fs_info, locked_folio, + start, async_extent->ram_size); + btrfs_err_rl(inode->root->fs_info, +- "%s failed, root=%llu inode=%llu start=%llu len=%llu: %d", ++ "%s failed, root=%llu inode=%llu start=%llu len=%llu: %pe", + __func__, btrfs_root_id(inode->root), +- btrfs_ino(inode), start, async_extent->ram_size, ret); ++ btrfs_ino(inode), start, async_extent->ram_size, ++ ERR_PTR(ret)); + } + } + +@@ -1508,10 +1509,10 @@ static noinline int cow_file_range(struct btrfs_inode *inode, + end - start - cur_alloc_size + 1, NULL); + } + btrfs_err(fs_info, +-"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu cur_alloc_size=%u: %d", ++"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu cur_alloc_size=%u: %pe", + __func__, btrfs_root_id(inode->root), + btrfs_ino(inode), orig_start, end + 1 - orig_start, +- start, cur_alloc_size, ret); ++ start, cur_alloc_size, ERR_PTR(ret)); + return ret; + } + +@@ -1962,9 +1963,9 @@ static int nocow_one_range(struct btrfs_inode *inode, struct folio *locked_folio + PAGE_UNLOCK | PAGE_START_WRITEBACK | + PAGE_END_WRITEBACK); + btrfs_err(inode->root->fs_info, +- "%s failed, root=%lld inode=%llu start=%llu len=%llu: %d", ++ "%s failed, root=%lld inode=%llu start=%llu len=%llu: %pe", + __func__, btrfs_root_id(inode->root), btrfs_ino(inode), +- file_pos, len, ret); ++ file_pos, len, ERR_PTR(ret)); + return ret; + } + +@@ -2285,10 +2286,10 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode, + } + btrfs_free_path(path); + btrfs_err(fs_info, +-"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu oe_cleanup=%llu oe_cleanup_len=%llu untouched_start=%llu untouched_len=%llu: %d", ++"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu oe_cleanup=%llu oe_cleanup_len=%llu untouched_start=%llu untouched_len=%llu: %pe", + __func__, btrfs_root_id(inode->root), btrfs_ino(inode), + start, end + 1 - start, cur_offset, oe_cleanup_start, oe_cleanup_len, +- untouched_start, untouched_len, ret); ++ untouched_start, untouched_len, ERR_PTR(ret)); + return ret; + } + +@@ -3065,7 +3066,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, + u64 qgroup_reserved) + { + struct btrfs_root *root = inode->root; +- const u64 sectorsize = root->fs_info->sectorsize; ++ const u32 sectorsize = root->fs_info->sectorsize; + BTRFS_PATH_AUTO_FREE(path); + struct extent_buffer *leaf; + struct btrfs_key ins; +@@ -3926,7 +3927,7 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) + + out: + if (ret) +- btrfs_err(fs_info, "could not do orphan cleanup %d", ret); ++ btrfs_err(fs_info, "could not do orphan cleanup %pe", ERR_PTR(ret)); + return ret; + } + +@@ -4229,8 +4230,8 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path + ret = btrfs_load_inode_props(inode, path); + if (ret) + btrfs_err(fs_info, +- "error loading props for ino %llu (root %llu): %d", +- btrfs_ino(inode), btrfs_root_id(root), ret); ++ "error loading props for ino %llu (root %llu): %pe", ++ btrfs_ino(inode), btrfs_root_id(root), ERR_PTR(ret)); + } + + /* +@@ -6844,8 +6845,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, + } + if (ret) { + btrfs_err(fs_info, +- "error inheriting props for ino %llu (root %llu): %d", +- btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ret); ++ "error inheriting props for ino %llu (root %llu): %pe", ++ btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ERR_PTR(ret)); + } + + /* +@@ -10224,6 +10225,8 @@ static void btrfs_free_swapfile_pins(struct inode *inode) + struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; + struct btrfs_swapfile_pin *sp; + struct rb_node *node, *next; ++ u64 bg_bytes_released = 0; ++ u32 bg_nr_released = 0; + + spin_lock(&fs_info->swapfile_pins_lock); + node = rb_first(&fs_info->swapfile_pins); +@@ -10233,15 +10236,24 @@ static void btrfs_free_swapfile_pins(struct inode *inode) + if (sp->inode == inode) { + rb_erase(&sp->node, &fs_info->swapfile_pins); + if (sp->is_block_group) { +- btrfs_dec_block_group_swap_extents(sp->ptr, ++ struct btrfs_block_group *bg = sp->ptr; ++ ++ bg_bytes_released += bg->length; ++ bg_nr_released++; ++ btrfs_dec_block_group_swap_extents(bg, + sp->bg_extent_count); +- btrfs_put_block_group(sp->ptr); ++ btrfs_put_block_group(bg); + } + kfree(sp); + } + node = next; + } + spin_unlock(&fs_info->swapfile_pins_lock); ++ btrfs_info(fs_info, ++"swapfile deactivated on root %llu ino %llu, released %llu bytes from %u block group(s)", ++ btrfs_root_id(BTRFS_I(inode)->root), ++ btrfs_ino(BTRFS_I(inode)), bg_bytes_released, ++ bg_nr_released); + } + + struct btrfs_swap_info { +@@ -10319,8 +10331,10 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + struct btrfs_backref_share_check_ctx *backref_ctx = NULL; + struct btrfs_path *path = NULL; + int ret = 0; ++ u32 pinned_bg_nr = 0; + u64 isize; + u64 prev_extent_end = 0; ++ u64 pinned_bg_size = 0; + + /* + * Acquire the inode's mmap lock to prevent races with memory mapped +@@ -10570,6 +10584,9 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + ret = 0; + else + goto out; ++ } else { ++ pinned_bg_size += bg->length; ++ pinned_bg_nr++; + } + + if (bsi.block_len && +@@ -10617,6 +10634,14 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + if (ret) + return ret; + ++ btrfs_info(fs_info, ++"swapfile activated on root %llu ino %llu, pinned down %llu bytes from %u block group(s)", ++ btrfs_root_id(BTRFS_I(inode)->root), ++ btrfs_ino(BTRFS_I(inode)), ++ pinned_bg_size, pinned_bg_nr); ++ btrfs_warn(fs_info, ++"block groups with swapfile extents will not be scrubbed or balanced"); ++ + if (device) + sis->bdev = device->bdev; + *span = bsi.highest_ppage - bsi.lowest_ppage + 1; +diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -400,9 +400,9 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + + /* + * 1 for inode item +- * 2 for properties ++ * 1 for property + */ +- trans = btrfs_start_transaction(root, 3); ++ trans = btrfs_start_transaction(root, 2); + if (IS_ERR(trans)) + return PTR_ERR(trans); + +@@ -1143,13 +1143,13 @@ static noinline int btrfs_ioctl_resize(struct file *file, + } + + static noinline int __btrfs_ioctl_snap_create(struct file *file, +- struct mnt_idmap *idmap, + const char *name, unsigned long fd, bool subvol, + bool readonly, + struct btrfs_qgroup_inherit *inherit) + { + int ret; + struct qstr qname = QSTR(name); ++ struct mnt_idmap *idmap = file_mnt_idmap(file); + + if (!S_ISDIR(file_inode(file)->i_mode)) + return -ENOTDIR; +@@ -1227,8 +1227,7 @@ static noinline int btrfs_ioctl_snap_create(struct file *file, + if (ret < 0) + return ret; + +- return __btrfs_ioctl_snap_create(file, file_mnt_idmap(file), +- vol_args->name, vol_args->fd, subvol, ++ return __btrfs_ioctl_snap_create(file, vol_args->name, vol_args->fd, subvol, + false, NULL); + } + +@@ -1271,8 +1270,7 @@ static noinline int btrfs_ioctl_snap_create_v2(struct file *file, + return ret; + } + +- return __btrfs_ioctl_snap_create(file, file_mnt_idmap(file), +- vol_args->name, vol_args->fd, subvol, ++ return __btrfs_ioctl_snap_create(file, vol_args->name, vol_args->fd, subvol, + readonly, inherit); + } + +@@ -1664,13 +1662,11 @@ static noinline int btrfs_ioctl_tree_search_v2(struct btrfs_root *root, + } + + /* +- * Search INODE_REFs to identify path name of 'dirid' directory +- * in a 'tree_id' tree. and sets path name to 'name'. ++ * Search for an INODE_REF in a 'root' tree which identifies the path name of ++ * 'dirid'. When found, it sets 'name' with the path name. + */ +-static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, +- u64 tree_id, u64 dirid, char *name) ++static noinline int btrfs_search_path_in_tree(struct btrfs_root *root, u64 dirid, char *name) + { +- struct btrfs_root *root; + struct btrfs_key key; + char *ptr; + int ret = -1; +@@ -1692,13 +1688,6 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + + ptr = &name[BTRFS_INO_LOOKUP_PATH_MAX - 1]; + +- root = btrfs_get_fs_root(info, tree_id, true); +- if (IS_ERR(root)) { +- ret = PTR_ERR(root); +- root = NULL; +- goto out; +- } +- + key.objectid = dirid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; +@@ -1706,11 +1695,9 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + while (1) { + ret = btrfs_search_backwards(root, &key, path); + if (ret < 0) +- goto out; +- else if (ret > 0) { +- ret = -ENOENT; +- goto out; +- } ++ return ret; ++ else if (ret > 0) ++ return -ENOENT; + + l = path->nodes[0]; + slot = path->slots[0]; +@@ -1719,10 +1706,8 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + len = btrfs_inode_ref_name_len(l, iref); + ptr -= len + 1; + total_len += len + 1; +- if (ptr < name) { +- ret = -ENAMETOOLONG; +- goto out; +- } ++ if (ptr < name) ++ return -ENAMETOOLONG; + + *(ptr + len) = '/'; + read_extent_buffer(l, ptr, (unsigned long)(iref + 1), len); +@@ -1737,10 +1722,8 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + } + memmove(name, ptr, total_len); + name[total_len] = '\0'; +- ret = 0; +-out: +- btrfs_put_root(root); +- return ret; ++ ++ return 0; + } + + static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, +@@ -1884,6 +1867,7 @@ static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, + static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + void __user *argp) + { ++ bool new_root = false; + struct btrfs_ioctl_ino_lookup_args AUTO_KFREE(args); + int ret = 0; + +@@ -1897,6 +1881,8 @@ static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + */ + if (args->treeid == 0) + args->treeid = btrfs_root_id(root); ++ else ++ new_root = true; + + if (args->objectid == BTRFS_FIRST_FREE_OBJECTID) { + args->name[0] = 0; +@@ -1908,9 +1894,14 @@ static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + goto out; + } + +- ret = btrfs_search_path_in_tree(root->fs_info, +- args->treeid, args->objectid, +- args->name); ++ if (new_root) { ++ root = btrfs_get_fs_root(root->fs_info, args->treeid, true); ++ if (IS_ERR(root)) ++ return PTR_ERR(root); ++ } ++ ret = btrfs_search_path_in_tree(root, args->objectid, args->name); ++ if (new_root) ++ btrfs_put_root(root); + + out: + if (ret == 0 && copy_to_user(argp, args, sizeof(*args))) +@@ -2848,8 +2839,8 @@ static long btrfs_ioctl_default_subvol(struct file *file, void __user *argp) + else + ret = -ENOENT; + btrfs_err(fs_info, +- "could not find default diritem for dir %llu: %d", +- dir_id, ret); ++ "could not find default diritem for dir %llu: %pe", ++ dir_id, ERR_PTR(ret)); + goto out_free; + } + +@@ -3620,7 +3611,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); + struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); +- struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_root *quota_root; + struct btrfs_ioctl_qgroup_assign_args AUTO_KFREE(sa); + struct btrfs_qgroup_list AUTO_KFREE(prealloc); + struct btrfs_trans_handle *trans; +@@ -3651,10 +3642,20 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + } + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* 2 BTRFS_QGROUP_RELATION_KEY items. */ +- trans = btrfs_start_transaction(root, 2); ++ trans = btrfs_start_transaction(quota_root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3678,6 +3679,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + "qgroup status update failed after %s relation, marked as inconsistent", + sa->assign ? "adding" : "deleting"); + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +@@ -3689,7 +3691,8 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); +- struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_root *quota_root; + struct btrfs_ioctl_qgroup_create_args AUTO_KFREE(sa); + struct btrfs_trans_handle *trans; + int ret; +@@ -3698,7 +3701,7 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + +- if (!btrfs_qgroup_enabled(root->fs_info)) ++ if (!btrfs_qgroup_enabled(fs_info)) + return -ENOTCONN; + + ret = mnt_want_write_file(file); +@@ -3721,13 +3724,23 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + goto drop_write; + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* + * 1 BTRFS_QGROUP_INFO_KEY item. + * 1 BTRFS_QGROUP_LIMIT_KEY item. + */ +- trans = btrfs_start_transaction(root, 2); ++ trans = btrfs_start_transaction(quota_root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3738,6 +3751,7 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + } + + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +@@ -3750,6 +3764,8 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); + struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_root *quota_root; ++ struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_ioctl_qgroup_limit_args AUTO_KFREE(sa); + struct btrfs_trans_handle *trans; + int ret; +@@ -3759,7 +3775,7 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + +- if (!btrfs_qgroup_enabled(root->fs_info)) ++ if (!btrfs_qgroup_enabled(fs_info)) + return -ENOTCONN; + + ret = mnt_want_write_file(file); +@@ -3772,10 +3788,20 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + goto drop_write; + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* 1 BTRFS_QGROUP_LIMIT_KEY item. */ +- trans = btrfs_start_transaction(root, 1); ++ trans = btrfs_start_transaction(quota_root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3788,6 +3814,7 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + ret = btrfs_limit_qgroup(trans, qgroupid, &sa->lim); + + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c +--- a/fs/btrfs/messages.c ++++ b/fs/btrfs/messages.c +@@ -279,7 +279,6 @@ void __btrfs_panic(const struct btrfs_fs_info *fs_info, const char *function, + unsigned int line, int error, const char *fmt, ...) + { + char *s_id = ""; +- const char *errstr; + struct va_format vaf = { .fmt = fmt }; + va_list args; + +@@ -289,13 +288,12 @@ void __btrfs_panic(const struct btrfs_fs_info *fs_info, const char *function, + va_start(args, fmt); + vaf.va = &args; + +- errstr = btrfs_decode_error(error); + if (fs_info && (btrfs_test_opt(fs_info, PANIC_ON_FATAL_ERROR))) +- panic(KERN_CRIT "BTRFS panic (device %s) in %s:%d: %pV (errno=%d %s)\n", +- s_id, function, line, &vaf, error, errstr); ++ panic(KERN_CRIT "BTRFS panic (device %s) in %s:%d: %pV (errno=%d %pe)\n", ++ s_id, function, line, &vaf, error, ERR_PTR(error)); + +- btrfs_crit(fs_info, "panic in %s:%d: %pV (errno=%d %s)", +- function, line, &vaf, error, errstr); ++ btrfs_crit(fs_info, "panic in %s:%d: %pV (errno=%d %pe)", ++ function, line, &vaf, error, ERR_PTR(error)); + va_end(args); + /* Caller calls BUG() */ + } +diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c +--- a/fs/btrfs/qgroup.c ++++ b/fs/btrfs/qgroup.c +@@ -3915,8 +3915,8 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + ret = PTR_ERR(trans); + trans = NULL; + btrfs_err(fs_info, +- "fail to start transaction for status update: %d", +- ret); ++ "fail to start transaction for status update: %pe", ++ ERR_PTR(ret)); + } + } else { + trans = NULL; +@@ -3931,7 +3931,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + + if (ret2 < 0) { + ret = ret2; +- btrfs_err(fs_info, "fail to update qgroup status: %d", ret); ++ btrfs_err(fs_info, "fail to update qgroup status: %pe", ERR_PTR(ret)); + } + } + fs_info->qgroup_rescan_running = false; +@@ -3952,7 +3952,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + btrfs_info(fs_info, "qgroup scan completed%s", + ret > 0 ? " (inconsistency flag cleared)" : ""); + } else { +- btrfs_err(fs_info, "qgroup scan failed with %d", ret); ++ btrfs_err(fs_info, "qgroup scan failed with %pe", ERR_PTR(ret)); + } + } + +diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c +--- a/fs/btrfs/raid56.c ++++ b/fs/btrfs/raid56.c +@@ -2997,13 +2997,11 @@ void raid56_parity_submit_scrub_rbio(struct btrfs_raid_bio *rbio) + * This is due to the fact rbio has its own page management for its cache. + */ + void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, +- struct folio **data_folios, u64 data_logical) ++ void *vaddr, u64 data_logical) + { + struct btrfs_fs_info *fs_info = rbio->bioc->fs_info; + const u64 offset_in_full_stripe = data_logical - + rbio->bioc->full_stripe_logical; +- unsigned int findex = 0; +- unsigned int foffset = 0; + int ret; + + /* +@@ -3026,18 +3024,10 @@ void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, + cur_off < offset_in_full_stripe + BTRFS_STRIPE_LEN; + cur_off += PAGE_SIZE) { + const unsigned int pindex = cur_off >> PAGE_SHIFT; +- void *kaddr; + +- kaddr = kmap_local_page(rbio->stripe_pages[pindex]); +- memcpy_from_folio(kaddr, data_folios[findex], foffset, PAGE_SIZE); +- kunmap_local(kaddr); +- +- foffset += PAGE_SIZE; +- ASSERT(foffset <= folio_size(data_folios[findex])); +- if (foffset == folio_size(data_folios[findex])) { +- findex++; +- foffset = 0; +- } ++ ASSERT(cur_off - offset_in_full_stripe + PAGE_SIZE <= BTRFS_STRIPE_LEN); ++ memcpy_to_page(rbio->stripe_pages[pindex], 0, ++ vaddr + cur_off - offset_in_full_stripe, PAGE_SIZE); + } + bitmap_set(rbio->stripe_uptodate_bitmap, + offset_in_full_stripe >> fs_info->sectorsize_bits, +diff --git a/fs/btrfs/raid56.h b/fs/btrfs/raid56.h +--- a/fs/btrfs/raid56.h ++++ b/fs/btrfs/raid56.h +@@ -283,7 +283,7 @@ struct btrfs_raid_bio *raid56_parity_alloc_scrub_rbio(struct bio *bio, + void raid56_parity_submit_scrub_rbio(struct btrfs_raid_bio *rbio); + + void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, +- struct folio **data_folios, u64 data_logical); ++ void *vaddr, u64 data_logical); + + int btrfs_alloc_stripe_hash_table(struct btrfs_fs_info *info); + void btrfs_free_stripe_hash_table(struct btrfs_fs_info *info); +diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c +--- a/fs/btrfs/reflink.c ++++ b/fs/btrfs/reflink.c +@@ -20,30 +20,31 @@ + #define BTRFS_MAX_DEDUPE_LEN SZ_16M + + static int clone_finish_inode_update(struct btrfs_trans_handle *trans, +- struct inode *inode, ++ struct btrfs_inode *inode, + u64 endoff, + const u64 destoff, + const u64 olen, + bool no_time_update) + { ++ struct inode *vfs_inode = &inode->vfs_inode; + int ret; + +- inode_inc_iversion(inode); +- if (!no_time_update) { +- inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +- } ++ inode_inc_iversion(vfs_inode); ++ if (!no_time_update) ++ inode_set_mtime_to_ts(vfs_inode, inode_set_ctime_current(vfs_inode)); ++ + /* + * We round up to the block size at eof when determining which + * extents to clone above, but shouldn't round up the file size. + */ + if (endoff > destoff + olen) + endoff = destoff + olen; +- if (endoff > inode->i_size) { +- i_size_write(inode, endoff); +- btrfs_inode_safe_disk_i_size_write(BTRFS_I(inode), 0); ++ if (endoff > vfs_inode->i_size) { ++ i_size_write(vfs_inode, endoff); ++ btrfs_inode_safe_disk_i_size_write(inode, 0); + } + +- ret = btrfs_update_inode(trans, BTRFS_I(inode)); ++ ret = btrfs_update_inode(trans, inode); + if (unlikely(ret)) { + btrfs_abort_transaction(trans, ret); + btrfs_end_transaction(trans); +@@ -392,11 +393,11 @@ static int clone_copy_inline_extent(struct btrfs_inode *inode, + * @destoff: Offset within @inode to start clone + * @no_time_update: Whether to update mtime/ctime on the target inode + */ +-static int btrfs_clone(struct inode *src, struct inode *inode, ++static int btrfs_clone(struct btrfs_inode *src, struct btrfs_inode *inode, + const u64 off, const u64 olen, const u64 olen_aligned, + const u64 destoff, bool no_time_update) + { +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_fs_info *fs_info = inode->root->fs_info; + BTRFS_PATH_AUTO_FREE(path); + struct extent_buffer *leaf; + struct btrfs_trans_handle *trans; +@@ -420,7 +421,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + + path->reada = READA_FORWARD; + /* Clone data */ +- key.objectid = btrfs_ino(BTRFS_I(src)); ++ key.objectid = btrfs_ino(src); + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = off; + +@@ -436,8 +437,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + u64 drop_start; + + /* Note the key will change type as we walk through the tree */ +- ret = btrfs_search_slot(NULL, BTRFS_I(src)->root, &key, path, +- 0, 0); ++ ret = btrfs_search_slot(NULL, src->root, &key, path, 0, 0); + if (ret < 0) + goto out; + /* +@@ -455,7 +455,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + nritems = btrfs_header_nritems(path->nodes[0]); + process_slot: + if (path->slots[0] >= nritems) { +- ret = btrfs_next_leaf(BTRFS_I(src)->root, path); ++ ret = btrfs_next_leaf(src->root, path); + if (ret < 0) + goto out; + if (ret > 0) +@@ -466,8 +466,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + slot = path->slots[0]; + + btrfs_item_key_to_cpu(leaf, &key, slot); +- if (key.type > BTRFS_EXTENT_DATA_KEY || +- key.objectid != btrfs_ino(BTRFS_I(src))) ++ if (key.type > BTRFS_EXTENT_DATA_KEY || key.objectid != btrfs_ino(src)) + break; + + ASSERT(key.type == BTRFS_EXTENT_DATA_KEY, "key.type=%u", key.type); +@@ -514,7 +513,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + btrfs_release_path(path); + + memcpy(&new_key, &key, sizeof(new_key)); +- new_key.objectid = btrfs_ino(BTRFS_I(inode)); ++ new_key.objectid = btrfs_ino(inode); + if (off <= key.offset) + new_key.offset = key.offset + destoff - off; + else +@@ -558,7 +557,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + clone_info.extent_buf = buf; + clone_info.is_new_extent = false; + clone_info.update_times = !no_time_update; +- ret = btrfs_replace_file_extents(BTRFS_I(inode), path, ++ ret = btrfs_replace_file_extents(inode, path, + drop_start, new_key.offset + datal - 1, + &clone_info, &trans); + if (ret) +@@ -582,7 +581,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + goto out; + } + +- ret = clone_copy_inline_extent(BTRFS_I(inode), path, &new_key, ++ ret = clone_copy_inline_extent(inode, path, &new_key, + drop_start, datal, size, + comp, buf, &trans); + if (ret) +@@ -605,9 +604,9 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + * the checksums problem on fsync. + */ + if (extent_gen == trans->transid && disko > 0) +- BTRFS_I(src)->last_reflink_trans = trans->transid; ++ src->last_reflink_trans = trans->transid; + +- BTRFS_I(inode)->last_reflink_trans = trans->transid; ++ inode->last_reflink_trans = trans->transid; + + last_dest_end = ALIGN(new_key.offset + datal, + fs_info->sectorsize); +@@ -653,10 +652,10 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + * set by previous calls to btrfs_replace_file_extents() that + * replaced file extent items. + */ +- if (last_dest_end >= i_size_read(inode)) +- btrfs_set_inode_full_sync(BTRFS_I(inode)); ++ if (last_dest_end >= i_size_read(&inode->vfs_inode)) ++ btrfs_set_inode_full_sync(inode); + +- ret = btrfs_replace_file_extents(BTRFS_I(inode), path, ++ ret = btrfs_replace_file_extents(inode, path, + last_dest_end, destoff + len - 1, NULL, &trans); + if (ret) + goto out; +@@ -666,7 +665,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + } + + out: +- clear_bit(BTRFS_INODE_NO_DELALLOC_FLUSH, &BTRFS_I(inode)->runtime_flags); ++ clear_bit(BTRFS_INODE_NO_DELALLOC_FLUSH, &inode->runtime_flags); + + return ret; + } +@@ -688,10 +687,10 @@ static void btrfs_double_mmap_unlock(struct btrfs_inode *inode1, struct btrfs_in + static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + struct btrfs_inode *dst, u64 dst_loff) + { +- const u64 end = dst_loff + len - 1; + struct extent_state *cached_state = NULL; + struct btrfs_fs_info *fs_info = src->root->fs_info; +- const u64 bs = fs_info->sectorsize; ++ const u32 bs = fs_info->sectorsize; ++ const u64 end = round_up(dst_loff + len, bs) - 1; + int ret; + + /* +@@ -701,8 +700,7 @@ static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + * mode. + */ + btrfs_lock_extent(&dst->io_tree, dst_loff, end, &cached_state); +- ret = btrfs_clone(&src->vfs_inode, &dst->vfs_inode, loff, len, +- ALIGN(len, bs), dst_loff, true); ++ ret = btrfs_clone(src, dst, loff, len, ALIGN(len, bs), dst_loff, true); + btrfs_unlock_extent(&dst->io_tree, dst_loff, end, &cached_state); + + btrfs_btree_balance_dirty(fs_info); +@@ -710,12 +708,12 @@ static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + return ret; + } + +-static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, +- struct inode *dst, u64 dst_loff) ++static int btrfs_extent_same(struct btrfs_inode *src, u64 loff, u64 olen, ++ struct btrfs_inode *dst, u64 dst_loff) + { + int ret = 0; + u64 i, tail_len, chunk_count; +- struct btrfs_root *root_dst = BTRFS_I(dst)->root; ++ struct btrfs_root *root_dst = dst->root; + + spin_lock(&root_dst->root_item_lock); + if (root_dst->send_in_progress) { +@@ -733,8 +731,8 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, + chunk_count = div_u64(olen, BTRFS_MAX_DEDUPE_LEN); + + for (i = 0; i < chunk_count; i++) { +- ret = btrfs_extent_same_range(BTRFS_I(src), loff, BTRFS_MAX_DEDUPE_LEN, +- BTRFS_I(dst), dst_loff); ++ ret = btrfs_extent_same_range(src, loff, BTRFS_MAX_DEDUPE_LEN, ++ dst, dst_loff); + if (ret) + goto out; + +@@ -743,8 +741,7 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, + } + + if (tail_len > 0) +- ret = btrfs_extent_same_range(BTRFS_I(src), loff, tail_len, +- BTRFS_I(dst), dst_loff); ++ ret = btrfs_extent_same_range(src, loff, tail_len, dst, dst_loff); + out: + spin_lock(&root_dst->root_item_lock); + root_dst->dedupe_in_progress--; +@@ -757,12 +754,14 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + u64 off, u64 olen, u64 destoff) + { + struct extent_state *cached_state = NULL; +- struct inode *inode = file_inode(file); +- struct inode *src = file_inode(file_src); +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_inode *inode = BTRFS_I(file_inode(file)); ++ struct btrfs_inode *src = BTRFS_I(file_inode(file_src)); ++ struct btrfs_fs_info *fs_info = inode->root->fs_info; ++ const u64 src_isize = src->vfs_inode.i_size; ++ const u64 inode_isize = inode->vfs_inode.i_size; + int ret; + u64 len = olen; +- u64 bs = fs_info->sectorsize; ++ const u32 bs = fs_info->sectorsize; + u64 end; + + /* +@@ -771,13 +770,13 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * if the file size is not blocksize aligned. So we don't need to check + * for that case here. + */ +- if (off + len == src->i_size) +- len = ALIGN(src->i_size, bs) - off; ++ if (off + len == src_isize) ++ len = ALIGN(src_isize, bs) - off; + +- if (destoff > inode->i_size) { +- const u64 wb_start = ALIGN_DOWN(inode->i_size, bs); ++ if (destoff > inode_isize) { ++ const u64 wb_start = ALIGN_DOWN(inode_isize, bs); + +- ret = btrfs_cont_expand(BTRFS_I(inode), inode->i_size, destoff); ++ ret = btrfs_cont_expand(inode, inode_isize, destoff); + if (ret) + return ret; + /* +@@ -789,8 +788,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * we found the previous extent covering eof and before we + * attempted to increment its reference count). + */ +- ret = btrfs_wait_ordered_range(BTRFS_I(inode), wb_start, +- destoff - wb_start); ++ ret = btrfs_wait_ordered_range(inode, wb_start, destoff - wb_start); + if (ret) + return ret; + } +@@ -801,10 +799,10 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * because we have already locked the inode's i_mmap_lock in exclusive + * mode. + */ +- end = destoff + len - 1; +- btrfs_lock_extent(&BTRFS_I(inode)->io_tree, destoff, end, &cached_state); ++ end = round_up(destoff + len, bs) - 1; ++ btrfs_lock_extent(&inode->io_tree, destoff, end, &cached_state); + ret = btrfs_clone(src, inode, off, olen, len, destoff, false); +- btrfs_unlock_extent(&BTRFS_I(inode)->io_tree, destoff, end, &cached_state); ++ btrfs_unlock_extent(&inode->io_tree, destoff, end, &cached_state); + if (ret < 0) + return ret; + +@@ -818,7 +816,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * could come from some range other than the copied inline extent's + * destination range and we have no way to know that. + */ +- ret = btrfs_wait_ordered_range(BTRFS_I(inode), destoff, len); ++ ret = btrfs_wait_ordered_range(inode, destoff, len); + if (ret < 0) + return ret; + +@@ -826,7 +824,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * Invalidate page cache so that future reads will see the cloned data + * immediately and not the previous data. + */ +- ret = filemap_invalidate_inode(inode, false, destoff, end); ++ ret = filemap_invalidate_inode(&inode->vfs_inode, false, destoff, end); + if (ret < 0) + return ret; + +@@ -841,7 +839,7 @@ static int btrfs_remap_file_range_prep(struct file *file_in, loff_t pos_in, + { + struct btrfs_inode *inode_in = BTRFS_I(file_inode(file_in)); + struct btrfs_inode *inode_out = BTRFS_I(file_inode(file_out)); +- u64 bs = inode_out->root->fs_info->sectorsize; ++ const u32 bs = inode_out->root->fs_info->sectorsize; + u64 wb_len; + int ret; + +@@ -934,7 +932,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, + bool same_inode = dst_inode == src_inode; + int ret; + +- if (btrfs_is_shutdown(inode_to_fs_info(file_inode(src_file)))) ++ if (btrfs_is_shutdown(src_inode->root->fs_info)) + return -EIO; + + if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY)) +@@ -953,8 +951,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, + goto out_unlock; + + if (remap_flags & REMAP_FILE_DEDUP) +- ret = btrfs_extent_same(&src_inode->vfs_inode, off, len, +- &dst_inode->vfs_inode, destoff); ++ ret = btrfs_extent_same(src_inode, off, len, dst_inode, destoff); + else + ret = btrfs_clone_files(dst_file, src_file, off, len, destoff); + +diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c +--- a/fs/btrfs/relocation.c ++++ b/fs/btrfs/relocation.c +@@ -339,14 +339,15 @@ static struct btrfs_backref_node *walk_down_backref( + + static bool reloc_root_is_dead(const struct btrfs_root *root) + { +- /* +- * Pair with set_bit/clear_bit in clean_dirty_subvols and +- * btrfs_update_reloc_root. We need to see the updated bit before +- * trying to access reloc_root +- */ +- smp_rmb(); + if (test_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state)) + return true; ++ /* ++ * Pairs with set_bit/clear_bit in clear_reloc_root() and ++ * btrfs_update_reloc_root(). We need to see the updated bit before ++ * trying to access root->reloc_root in our callers. ++ */ ++ smp_rmb(); ++ + return false; + } + +@@ -4151,10 +4152,10 @@ static int copy_remapped_data(struct btrfs_fs_info *fs_info, u64 old_addr, + u64 new_addr, u64 length) + { + int ret; +- u64 copy_len = min_t(u64, length, SZ_1M); ++ const u64 copy_len = min_t(u64, length, SZ_1M); + struct page **pages; + struct reloc_io_private priv; +- unsigned int nr_pages = DIV_ROUND_UP(length, PAGE_SIZE); ++ const unsigned int nr_pages = DIV_ROUND_UP(copy_len, PAGE_SIZE); + + pages = kzalloc_objs(struct page *, nr_pages, GFP_NOFS); + if (!pages) +diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c +--- a/fs/btrfs/root-tree.c ++++ b/fs/btrfs/root-tree.c +@@ -265,15 +265,15 @@ int btrfs_find_orphan_roots(struct btrfs_fs_info *fs_info) + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + btrfs_err(fs_info, +- "failed to join transaction to delete orphan item: %d", +- ret); ++ "failed to join transaction to delete orphan item: %pe", ++ ERR_PTR(ret)); + return ret; + } + ret = btrfs_del_orphan_item(trans, tree_root, root_objectid); + btrfs_end_transaction(trans); + if (ret) { + btrfs_err(fs_info, +- "failed to delete root orphan item: %d", ret); ++ "failed to delete root orphan item: %pe", ERR_PTR(ret)); + return ret; + } + continue; +diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c +--- a/fs/btrfs/scrub.c ++++ b/fs/btrfs/scrub.c +@@ -57,12 +57,6 @@ struct scrub_ctx; + + #define SCRUB_TOTAL_STRIPES (SCRUB_GROUPS_PER_SCTX * SCRUB_STRIPES_PER_GROUP) + +-/* +- * The following value times PAGE_SIZE needs to be large enough to match the +- * largest node/leaf/sector size that shall be supported. +- */ +-#define SCRUB_MAX_SECTORS_PER_BLOCK (BTRFS_MAX_METADATA_BLOCKSIZE / SZ_4K) +- + /* Represent one sector and its needed info to verify the content. */ + struct scrub_sector_verification { + union { +@@ -129,19 +123,17 @@ enum { + scrub_bitmap_nr_last, + }; + +-#define SCRUB_STRIPE_MAX_FOLIOS (BTRFS_STRIPE_LEN / PAGE_SIZE) +- + /* + * Represent one contiguous range with a length of BTRFS_STRIPE_LEN. + */ + struct scrub_stripe { + struct scrub_ctx *sctx; + struct btrfs_block_group *bg; +- +- struct folio *folios[SCRUB_STRIPE_MAX_FOLIOS]; + struct scrub_sector_verification *sectors; +- + struct btrfs_device *dev; ++ ++ void *buffer; ++ + u64 logical; + u64 physical; + +@@ -227,6 +219,9 @@ struct scrub_ctx { + refcount_t refs; + }; + ++static_assert(BTRFS_STRIPE_LEN >= PAGE_SIZE); ++static_assert(IS_ALIGNED(BTRFS_STRIPE_LEN, PAGE_SIZE)); ++ + #define scrub_calc_start_bit(stripe, name, block_nr) \ + ({ \ + unsigned int __start_bit; \ +@@ -338,13 +333,10 @@ static void release_scrub_stripe(struct scrub_stripe *stripe) + if (!stripe) + return; + +- for (int i = 0; i < SCRUB_STRIPE_MAX_FOLIOS; i++) { +- if (stripe->folios[i]) +- folio_put(stripe->folios[i]); +- stripe->folios[i] = NULL; +- } ++ kvfree(stripe->buffer); + kfree(stripe->sectors); + kfree(stripe->csums); ++ stripe->buffer = NULL; + stripe->sectors = NULL; + stripe->csums = NULL; + stripe->sctx = NULL; +@@ -354,9 +346,6 @@ static void release_scrub_stripe(struct scrub_stripe *stripe) + static int init_scrub_stripe(struct btrfs_fs_info *fs_info, + struct scrub_stripe *stripe) + { +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- int ret; +- + memset(stripe, 0, sizeof(*stripe)); + + stripe->nr_sectors = BTRFS_STRIPE_LEN >> fs_info->sectorsize_bits; +@@ -367,11 +356,8 @@ static int init_scrub_stripe(struct btrfs_fs_info *fs_info, + atomic_set(&stripe->pending_io, 0); + spin_lock_init(&stripe->write_error_lock); + +- ASSERT(BTRFS_STRIPE_LEN >> min_folio_shift <= SCRUB_STRIPE_MAX_FOLIOS); +- ret = btrfs_alloc_folio_array(BTRFS_STRIPE_LEN >> min_folio_shift, +- fs_info->block_min_order, stripe->folios, +- GFP_NOFS); +- if (ret < 0) ++ stripe->buffer = kvmalloc(BTRFS_STRIPE_LEN, GFP_NOFS); ++ if (!stripe->buffer) + goto error; + + stripe->sectors = kzalloc_objs(struct scrub_sector_verification, +@@ -682,32 +668,18 @@ static int fill_writer_pointer_gap(struct scrub_ctx *sctx, u64 physical) + return ret; + } + +-static void *scrub_stripe_get_kaddr(struct scrub_stripe *stripe, int sector_nr) ++/* ++ * Unlike the existing csum which is based on paddr, this version is fully on ++ * vaddr, so no extra per-page iteration needed. ++ */ ++static void scrub_calc_vaddr_csum(struct btrfs_fs_info *fs_info, ++ void *vaddr, unsigned int len, u8 *dest) + { +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- u32 offset = (sector_nr << fs_info->sectorsize_bits); +- const struct folio *folio = stripe->folios[offset >> min_folio_shift]; ++ struct btrfs_csum_ctx csum; + +- /* stripe->folios[] is allocated by us and no highmem is allowed. */ +- ASSERT(folio); +- ASSERT(!folio_test_highmem(folio)); +- return folio_address(folio) + offset_in_folio(folio, offset); +-} +- +-static phys_addr_t scrub_stripe_get_paddr(struct scrub_stripe *stripe, int sector_nr) +-{ +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- u32 offset = (sector_nr << fs_info->sectorsize_bits); +- const struct folio *folio = stripe->folios[offset >> min_folio_shift]; +- +- /* stripe->folios[] is allocated by us and no highmem is allowed. */ +- ASSERT(folio); +- ASSERT(!folio_test_highmem(folio)); +- /* And the range must be contained inside the folio. */ +- ASSERT(offset_in_folio(folio, offset) + fs_info->sectorsize <= folio_size(folio)); +- return page_to_phys(folio_page(folio, 0)) + offset_in_folio(folio, offset); ++ btrfs_csum_init(&csum, fs_info->csum_type); ++ btrfs_csum_update(&csum, vaddr, len); ++ btrfs_csum_final(&csum, dest); + } + + static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr) +@@ -715,19 +687,10 @@ static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits; + const u64 logical = stripe->logical + (sector_nr << fs_info->sectorsize_bits); +- void *first_kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); +- struct btrfs_header *header = first_kaddr; +- struct btrfs_csum_ctx csum; +- u8 on_disk_csum[BTRFS_CSUM_SIZE]; ++ void *first_vaddr = stripe->buffer + (sector_nr << fs_info->sectorsize_bits); ++ struct btrfs_header *header = first_vaddr; + u8 calculated_csum[BTRFS_CSUM_SIZE]; + +- /* +- * Here we don't have a good way to attach the pages (and subpages) +- * to a dummy extent buffer, thus we have to directly grab the members +- * from pages. +- */ +- memcpy(on_disk_csum, header->csum, fs_info->csum_size); +- + if (logical != btrfs_stack_header_bytenr(header)) { + scrub_bitmap_set_meta_error(stripe, sector_nr, sectors_per_tree); + scrub_bitmap_set_error(stripe, sector_nr, sectors_per_tree); +@@ -759,23 +722,15 @@ static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr + } + + /* Now check tree block csum. */ +- btrfs_csum_init(&csum, fs_info->csum_type); +- btrfs_csum_update(&csum, first_kaddr + BTRFS_CSUM_SIZE, +- fs_info->sectorsize - BTRFS_CSUM_SIZE); +- +- for (int i = sector_nr + 1; i < sector_nr + sectors_per_tree; i++) { +- btrfs_csum_update(&csum, scrub_stripe_get_kaddr(stripe, i), +- fs_info->sectorsize); +- } +- +- btrfs_csum_final(&csum, calculated_csum); +- if (memcmp(calculated_csum, on_disk_csum, fs_info->csum_size) != 0) { ++ scrub_calc_vaddr_csum(fs_info, first_vaddr + BTRFS_CSUM_SIZE, ++ fs_info->nodesize - BTRFS_CSUM_SIZE, calculated_csum); ++ if (memcmp(calculated_csum, header->csum, fs_info->csum_size) != 0) { + scrub_bitmap_set_meta_error(stripe, sector_nr, sectors_per_tree); + scrub_bitmap_set_error(stripe, sector_nr, sectors_per_tree); + btrfs_warn_rl(fs_info, + "scrub: tree block %llu mirror %u has bad csum, has " BTRFS_CSUM_FMT " want " BTRFS_CSUM_FMT, + logical, stripe->mirror_num, +- BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, on_disk_csum), ++ BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, header->csum), + BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, calculated_csum)); + return; + } +@@ -801,9 +756,7 @@ static void scrub_verify_one_sector(struct scrub_stripe *stripe, int sector_nr) + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + struct scrub_sector_verification *sector = &stripe->sectors[sector_nr]; + const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits; +- phys_addr_t paddr = scrub_stripe_get_paddr(stripe, sector_nr); + u8 csum_buf[BTRFS_CSUM_SIZE]; +- int ret; + + ASSERT(sector_nr >= 0 && sector_nr < stripe->nr_sectors); + +@@ -846,8 +799,10 @@ static void scrub_verify_one_sector(struct scrub_stripe *stripe, int sector_nr) + return; + } + +- ret = btrfs_check_block_csum(fs_info, paddr, csum_buf, sector->csum); +- if (ret < 0) { ++ scrub_calc_vaddr_csum(fs_info, ++ stripe->buffer + (sector_nr << fs_info->sectorsize_bits), ++ fs_info->sectorsize, csum_buf); ++ if (memcmp(csum_buf, sector->csum, fs_info->csum_size)) { + scrub_bitmap_set_bit_csum_error(stripe, sector_nr); + scrub_bitmap_set_bit_error(stripe, sector_nr); + } else { +@@ -870,16 +825,51 @@ static void scrub_verify_one_stripe(struct scrub_stripe *stripe, unsigned long b + } + } + +-static int calc_sector_number(struct scrub_stripe *stripe, struct bio_vec *first_bvec) ++static unsigned int calc_sector_number(const struct btrfs_bio *bbio) + { +- int i; ++ const struct scrub_stripe *stripe = bbio->private; ++ const struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + +- for (i = 0; i < stripe->nr_sectors; i++) { +- if (scrub_stripe_get_kaddr(stripe, i) == bvec_virt(first_bvec)) +- break; ++ /* Scrub bbios all have their @file_offset set to the logical bytenr. */ ++ ASSERT(bbio->file_offset >= stripe->logical && ++ bbio->file_offset < stripe->logical + (stripe->nr_sectors << ++ fs_info->sectorsize_bits), ++ "scrub bio logical=%llu stripe logical=%llu stripe len=%u", ++ bbio->file_offset, stripe->logical, ++ stripe->nr_sectors << fs_info->sectorsize_bits); ++ return (bbio->file_offset - stripe->logical) >> fs_info->sectorsize_bits; ++} ++ ++/* ++ * Common handling of read endio. ++ * ++ * The bbio will be released, so no more access to @bbio after this function. ++ */ ++static void scrub_read_endio_common(struct btrfs_bio *bbio) ++{ ++ struct scrub_stripe *stripe = bbio->private; ++ struct btrfs_fs_info *fs_info = stripe->bg->fs_info; ++ unsigned int sector_nr = calc_sector_number(bbio); ++ const u32 bio_size = bio_get_size(&bbio->bio); ++ const u32 sectors = bio_size >> fs_info->sectorsize_bits; ++ ++ ++ /* ++ * For vmallocated space, readers need to call invalidate_kernel_vmap_range() ++ * to manage the coherency between kernel mapping and devie space mapping. ++ */ ++ if (is_vmalloc_addr(stripe->buffer)) ++ invalidate_kernel_vmap_range( ++ stripe->buffer + (sector_nr << fs_info->sectorsize_bits), ++ bio_size); ++ ++ if (bbio->bio.bi_status) { ++ scrub_bitmap_set_io_error(stripe, sector_nr, sectors); ++ scrub_bitmap_set_error(stripe, sector_nr, sectors); ++ } else { ++ scrub_bitmap_clear_io_error(stripe, sector_nr, sectors); + } +- ASSERT(i < stripe->nr_sectors); +- return i; ++ bio_put(&bbio->bio); + } + + /* +@@ -891,22 +881,9 @@ static int calc_sector_number(struct scrub_stripe *stripe, struct bio_vec *first + static void scrub_repair_read_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); +- const u32 bio_size = bio_get_size(&bbio->bio); + +- ASSERT(sector_nr < stripe->nr_sectors); ++ scrub_read_endio_common(bbio); + +- if (bbio->bio.bi_status) { +- scrub_bitmap_set_io_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- scrub_bitmap_set_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- } else { +- scrub_bitmap_clear_io_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- } +- bio_put(&bbio->bio); + if (atomic_dec_and_test(&stripe->pending_io)) + wake_up(&stripe->io_wait); + } +@@ -921,30 +898,35 @@ static void scrub_bio_add_sector(struct btrfs_bio *bbio, struct scrub_stripe *st + int sector_nr) + { + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; +- void *kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); ++ const u32 offset = sector_nr << fs_info->sectorsize_bits; + int ret; + +- ret = bio_add_page(&bbio->bio, virt_to_page(kaddr), fs_info->sectorsize, +- offset_in_page(kaddr)); +- /* +- * Caller should ensure the bbio has enough size. +- * And we cannot use __bio_add_page(), which doesn't do any merge. +- * +- * Meanwhile for scrub_submit_initial_read() we fully rely on the merge +- * to create the minimal amount of bio vectors, for fs block size < page +- * size cases. +- */ ++ ASSERT(offset + fs_info->sectorsize <= BTRFS_STRIPE_LEN); ++ ++ if (is_vmalloc_addr(stripe->buffer)) { ++ ret = bio_add_vmalloc(&bbio->bio, stripe->buffer + offset, fs_info->sectorsize); ++ ASSERT(ret == true); ++ return; ++ } ++ ret = bio_add_page(&bbio->bio, virt_to_page(stripe->buffer + offset), ++ fs_info->sectorsize, offset_in_page(stripe->buffer + offset)); + ASSERT(ret == fs_info->sectorsize); + } + + static struct btrfs_bio *alloc_scrub_bbio(struct btrfs_fs_info *fs_info, +- unsigned int nr_vecs, blk_opf_t opf, ++ blk_opf_t opf, + u64 logical, + btrfs_bio_end_io_t end_io, void *private) + { + struct btrfs_bio *bbio; + +- bbio = btrfs_bio_alloc(nr_vecs, opf, BTRFS_I(fs_info->btree_inode), ++ /* ++ * Stripe->buffer is allocated by kvmalloc(), which can be pages at ++ * different physical addresses, we have to ensure the bbio is large ++ * enough to contain the full stripe. ++ */ ++ bbio = btrfs_bio_alloc(BTRFS_STRIPE_LEN >> PAGE_SHIFT, opf, ++ BTRFS_I(fs_info->btree_inode), + logical, end_io, private); + bbio->is_scrub = true; + bbio->bio.bi_iter.bi_sector = logical >> SECTOR_SHIFT; +@@ -976,7 +958,7 @@ static void scrub_stripe_submit_repair_read(struct scrub_stripe *stripe, + } + + if (!bbio) +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + stripe->logical + (i << fs_info->sectorsize_bits), + scrub_repair_read_endio, stripe); + +@@ -1249,20 +1231,9 @@ static void scrub_stripe_read_repair_worker(struct work_struct *work) + static void scrub_read_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); +- int num_sectors; +- const u32 bio_size = bio_get_size(&bbio->bio); + +- ASSERT(sector_nr < stripe->nr_sectors); +- num_sectors = bio_size >> stripe->bg->fs_info->sectorsize_bits; ++ scrub_read_endio_common(bbio); + +- if (bbio->bio.bi_status) { +- scrub_bitmap_set_io_error(stripe, sector_nr, num_sectors); +- scrub_bitmap_set_error(stripe, sector_nr, num_sectors); +- } else { +- scrub_bitmap_clear_io_error(stripe, sector_nr, num_sectors); +- } +- bio_put(&bbio->bio); + if (atomic_dec_and_test(&stripe->pending_io)) { + wake_up(&stripe->io_wait); + INIT_WORK(&stripe->work, scrub_stripe_read_repair_worker); +@@ -1274,7 +1245,7 @@ static void scrub_write_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); ++ unsigned int sector_nr = calc_sector_number(bbio); + const u32 bio_size = bio_get_size(&bbio->bio); + + if (bbio->bio.bi_status) { +@@ -1353,7 +1324,7 @@ static void scrub_write_sectors(struct scrub_ctx *sctx, struct scrub_stripe *str + bbio = NULL; + } + if (!bbio) +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_WRITE, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_WRITE, + stripe->logical + (sector_nr << fs_info->sectorsize_bits), + scrub_write_endio, stripe); + scrub_bio_add_sector(bbio, stripe, sector_nr); +@@ -1848,7 +1819,7 @@ static void scrub_submit_extent_sector_read(struct scrub_stripe *stripe) + continue; + } + +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + logical, scrub_read_endio, stripe); + } + +@@ -1873,7 +1844,6 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, + { + struct btrfs_fs_info *fs_info = sctx->fs_info; + struct btrfs_bio *bbio; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; + unsigned int nr_sectors = stripe_length(stripe) >> fs_info->sectorsize_bits; + int mirror = stripe->mirror_num; + +@@ -1886,7 +1856,7 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, + return; + } + +- bbio = alloc_scrub_bbio(fs_info, BTRFS_STRIPE_LEN >> min_folio_shift, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + stripe->logical, scrub_read_endio, stripe); + /* Read the whole range inside the chunk boundary. */ + for (unsigned int cur = 0; cur < nr_sectors; cur++) +@@ -2142,7 +2112,7 @@ static int scrub_raid56_cached_parity(struct scrub_ctx *sctx, + for (int i = 0; i < data_stripes; i++) { + struct scrub_stripe *stripe = &sctx->raid56_data_stripes[i]; + +- raid56_parity_cache_data_folios(rbio, stripe->folios, ++ raid56_parity_cache_data_folios(rbio, stripe->buffer, + full_stripe_start + (i << BTRFS_STRIPE_LEN_SHIFT)); + } + raid56_parity_submit_scrub_rbio(rbio); +@@ -3095,14 +3065,6 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start, + /* At mount time we have ensured nodesize is in the range of [4K, 64K]. */ + ASSERT(fs_info->nodesize <= BTRFS_STRIPE_LEN); + +- /* +- * SCRUB_MAX_SECTORS_PER_BLOCK is calculated using the largest possible +- * value (max nodesize / min sectorsize), thus nodesize should always +- * be fine. +- */ +- ASSERT(fs_info->nodesize <= +- SCRUB_MAX_SECTORS_PER_BLOCK << fs_info->sectorsize_bits); +- + /* Allocate outside of device_list_mutex */ + sctx = scrub_setup_ctx(fs_info, is_dev_replace); + if (IS_ERR(sctx)) +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -130,10 +130,10 @@ static_assert(offsetof(struct backref_cache_entry, entry) == 0); + #define SEND_MAX_DIR_CREATED_CACHE_SIZE 64 + + /* +- * Max number of entries in the cache that stores directories that were already +- * created. The cache uses raw struct btrfs_lru_cache_entry entries, so it uses +- * at most 4096 bytes - sizeof(struct btrfs_lru_cache_entry) is 48 bytes, but +- * the kmalloc-64 slab is used, so we get 4096 bytes (64 bytes * 64). ++ * Maximum number of entries in the cache that stores utimes values for directories. ++ * The cache uses raw struct btrfs_lru_cache_entry entries, so it uses at most ++ * 4096 bytes - sizeof(struct btrfs_lru_cache_entry) is 48 bytes, but the ++ * kmalloc-64 slab is used, so we get 4096 bytes (64 bytes * 64). + */ + #define SEND_MAX_DIR_UTIMES_CACHE_SIZE 64 + +@@ -625,9 +625,8 @@ static void fs_path_unreverse(struct fs_path *p) + static inline bool is_current_inode_path(const struct send_ctx *sctx, + const struct fs_path *path) + { +- const struct fs_path *cur = &sctx->cur_inode_path; +- +- return (strncmp(path->start, cur->start, fs_path_len(cur)) == 0); ++ /* Paths are always nul terminated. */ ++ return (strcmp(path->start, sctx->cur_inode_path.start) == 0); + } + + static struct btrfs_path *alloc_path_for_send(void) +@@ -6033,7 +6032,7 @@ static int send_write_or_clone(struct send_ctx *sctx, + int ret = 0; + u64 offset = key->offset; + u64 end; +- u64 bs = sctx->send_root->fs_info->sectorsize; ++ const u32 bs = sctx->send_root->fs_info->sectorsize; + struct btrfs_file_extent_item *ei; + u64 disk_byte; + u64 data_offset; +@@ -8251,7 +8250,7 @@ long btrfs_ioctl_send(struct btrfs_root *send_root, const struct btrfs_ioctl_sen + } + + if (sort_clone_roots) { +- for (i = 0; i < sctx->clone_roots_cnt; i++) { ++ for (i = 0; sctx && i < sctx->clone_roots_cnt; i++) { + btrfs_root_dec_send_in_progress( + sctx->clone_roots[i].root); + btrfs_put_root(sctx->clone_roots[i].root); +diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c +--- a/fs/btrfs/space-info.c ++++ b/fs/btrfs/space-info.c +@@ -2156,7 +2156,7 @@ static bool do_reclaim_sweep(struct btrfs_space_info *space_info, int raid) + will_reclaim = true; + reclaim = true; + } +- bg->reclaim_mark++; ++ bg->reclaim_mark = true; + spin_unlock(&bg->lock); + if (reclaim) + btrfs_mark_bg_to_reclaim(bg); +diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c +--- a/fs/btrfs/subpage.c ++++ b/fs/btrfs/subpage.c +@@ -59,7 +59,7 @@ int btrfs_attach_folio_state(const struct btrfs_fs_info *fs_info, + if (type == BTRFS_SUBPAGE_DATA && !btrfs_is_subpage(fs_info, folio)) + return 0; + +- bfs = btrfs_alloc_folio_state(fs_info, folio_size(folio), type); ++ bfs = btrfs_alloc_folio_state(fs_info, folio_size(folio), type, GFP_NOFS); + if (IS_ERR(bfs)) + return PTR_ERR(bfs); + +@@ -86,7 +86,8 @@ void btrfs_detach_folio_state(const struct btrfs_fs_info *fs_info, struct folio + } + + struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs_info, +- size_t fsize, enum btrfs_folio_type type) ++ size_t fsize, enum btrfs_folio_type type, ++ gfp_t gfp) + { + struct btrfs_folio_state *ret; + unsigned int real_size; +@@ -96,7 +97,7 @@ struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs + real_size = struct_size(ret, bitmaps, + BITS_TO_LONGS(btrfs_bitmap_nr_max * + (fsize >> fs_info->sectorsize_bits))); +- ret = kzalloc(real_size, GFP_NOFS); ++ ret = kzalloc(real_size, gfp); + if (!ret) + return ERR_PTR(-ENOMEM); + +diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h +--- a/fs/btrfs/subpage.h ++++ b/fs/btrfs/subpage.h +@@ -110,7 +110,8 @@ void btrfs_detach_folio_state(const struct btrfs_fs_info *fs_info, struct folio + + /* Allocate additional data where page represents more than one sector */ + struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs_info, +- size_t fsize, enum btrfs_folio_type type); ++ size_t fsize, enum btrfs_folio_type type, ++ gfp_t gfp); + static inline void btrfs_free_folio_state(struct btrfs_folio_state *bfs) + { + kfree(bfs); +diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c +--- a/fs/btrfs/super.c ++++ b/fs/btrfs/super.c +@@ -129,7 +129,6 @@ enum { + + /* Rescue options */ + Opt_rescue, +- Opt_usebackuproot, + + /* Debugging options */ + Opt_enospc_debug, +@@ -249,8 +248,6 @@ static const struct fs_parameter_spec btrfs_fs_parameters[] = { + + /* Rescue options. */ + fsparam_enum("rescue", Opt_rescue, btrfs_parameter_rescue), +- /* Deprecated, with alias rescue=usebackuproot */ +- __fsparam(NULL, "usebackuproot", Opt_usebackuproot, fs_param_deprecated, NULL), + /* For compatibility only, alias for "rescue=nologreplay". */ + fsparam_flag("norecovery", Opt_norecovery), + +@@ -514,19 +511,20 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + btrfs_clear_opt(ctx->mount_opt, NODISCARD); + break; + case Opt_space_cache: +- if (result.negated) { +- btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); +- btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); +- btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); +- } else { +- btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); +- btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); +- } ++ if (!result.negated) ++ btrfs_warn(NULL, ++ "v1 space cache is deprecated, falling back to no space cache"); ++ btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); ++ btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); ++ btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case Opt_space_cache_version: + switch (result.uint_32) { + case Opt_space_cache_v1: +- btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); ++ btrfs_warn(NULL, ++ "v1 space cache is deprecated, falling back to no space cache"); ++ btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); ++ btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); + btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case Opt_space_cache_v2: +@@ -560,14 +558,6 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + else + btrfs_set_opt(ctx->mount_opt, AUTO_DEFRAG); + break; +- case Opt_usebackuproot: +- btrfs_warn(NULL, +- "'usebackuproot' is deprecated, use 'rescue=usebackuproot' instead"); +- btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); +- +- /* If we're loading the backup roots we can't trust the space cache. */ +- btrfs_set_opt(ctx->mount_opt, CLEAR_CACHE); +- break; + case Opt_skip_balance: + btrfs_set_opt(ctx->mount_opt, SKIP_BALANCE); + break; +@@ -620,6 +610,7 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + btrfs_set_opt(ctx->mount_opt, IGNORESUPERFLAGS); + btrfs_set_opt(ctx->mount_opt, IGNOREBADROOTS); + btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY); ++ btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + break; + default: + btrfs_info(NULL, "unrecognized rescue option '%s'", +@@ -668,7 +659,6 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + */ + static void btrfs_clear_oneshot_options(struct btrfs_fs_info *fs_info) + { +- btrfs_clear_opt(fs_info->mount_opt, USEBACKUPROOT); + btrfs_clear_opt(fs_info->mount_opt, CLEAR_CACHE); + btrfs_clear_opt(fs_info->mount_opt, NOSPACECACHE); + } +@@ -692,7 +682,8 @@ bool btrfs_check_options(const struct btrfs_fs_info *info, + bool ret = true; + + if (!(flags & SB_RDONLY) && +- (check_ro_option(info, *mount_opt, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || ++ (check_ro_option(info, *mount_opt, BTRFS_MOUNT_USEBACKUPROOT, "usebackuproot") || ++ check_ro_option(info, *mount_opt, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREBADROOTS, "ignorebadroots") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREDATACSUMS, "ignoredatacsums") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREMETACSUMS, "ignoremetacsums") || +@@ -982,7 +973,7 @@ static int btrfs_fill_super(struct super_block *sb, + + ret = open_ctree(sb, fs_devices); + if (ret) { +- btrfs_err(fs_info, "open_ctree failed: %d", ret); ++ btrfs_err(fs_info, "open_ctree failed: %pe", ERR_PTR(ret)); + return ret; + } + +diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c +--- a/fs/btrfs/sysfs.c ++++ b/fs/btrfs/sysfs.c +@@ -1336,7 +1336,7 @@ char *btrfs_get_mod_read_policy(void) + return read_policy; + } + +-/* Set perms to 0, disable /sys/module/btrfs/parameter/read_policy interface. */ ++/* Set perms to 0, disable /sys/module/btrfs/parameters/read_policy interface. */ + module_param(read_policy, charp, 0); + MODULE_PARM_DESC(read_policy, + "Global read policy: pid (default), round-robin[:], devid[:]"); +diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c +--- a/fs/btrfs/transaction.c ++++ b/fs/btrfs/transaction.c +@@ -1528,12 +1528,8 @@ static noinline int commit_fs_roots(struct btrfs_trans_handle *trans) + ASSERT(atomic_read(&root->log_writers) == 0, + "atomic_read(&root->log_writers)=%d", + atomic_read(&root->log_writers)); +- ASSERT(atomic_read(&root->log_commit[0]) == 0, +- "atomic_read(&root->log_commit[0])=%d", +- atomic_read(&root->log_commit[0])); +- ASSERT(atomic_read(&root->log_commit[1]) == 0, +- "atomic_read(&root->log_commit[1])=%d", +- atomic_read(&root->log_commit[1])); ++ ASSERT(!root->log_commit[0]); ++ ASSERT(!root->log_commit[1]); + + radix_tree_tag_clear(&fs_info->fs_roots_radix, + (unsigned long)btrfs_root_id(root), +@@ -1651,7 +1647,7 @@ static int qgroup_account_snapshot(struct btrfs_trans_handle *trans, + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { + btrfs_err(fs_info, +-"error while writing out transaction during qgroup snapshot accounting: %d", ret); ++"error while writing out transaction during qgroup snapshot accounting: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -2597,7 +2593,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) + + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { +- btrfs_err(fs_info, "error while writing out transaction: %d", ret); ++ btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); + /* + * Abort before releasing tree_log_mutex, so a log sync waiting + * on it sees the fs error and skips writing super_for_commit +@@ -2764,8 +2760,8 @@ void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans, + WRITE_ONCE(trans->transaction->aborted, error); + trace_btrfs_transaction_abort(trans); + if (first_hit) { +- btrfs_err(fs_info, "Transaction %llu aborted (error %d)", +- trans->transid, error); ++ btrfs_err(fs_info, "Transaction %llu aborted (%pe)", ++ trans->transid, ERR_PTR(error)); + if (error == -ENOSPC) + btrfs_dump_space_info_for_trans_abort(fs_info); + } +diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c +--- a/fs/btrfs/tree-log.c ++++ b/fs/btrfs/tree-log.c +@@ -221,7 +221,7 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans, + static int link_to_fixup_dir(struct walk_control *wc, u64 objectid); + static noinline int replay_dir_deletes(struct walk_control *wc, + u64 dirid, bool del_all); +-static void wait_log_commit(struct btrfs_root *root, int transid); ++static bool wait_log_commit(struct btrfs_root *root, int transid); + + /* + * tree logging is a special write ahead log used to make sure that +@@ -305,24 +305,13 @@ static int start_log_trans(struct btrfs_trans_handle *trans, + + again: + if (root->log_root) { +- int index = (root->log_transid + 1) % 2; +- + if (btrfs_need_log_full_commit(trans)) { + ret = BTRFS_LOG_FORCE_COMMIT; + goto out; + } + +- if (zoned && atomic_read(&root->log_commit[index])) { +- wait_log_commit(root, root->log_transid - 1); ++ if (zoned && wait_log_commit(root, root->log_transid - 1)) + goto again; +- } +- +- if (!root->log_start_pid) { +- clear_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- root->log_start_pid = current->pid; +- } else if (root->log_start_pid != current->pid) { +- set_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- } + } else { + /* + * This means fs_info->log_root_tree was already created +@@ -340,8 +329,6 @@ static int start_log_trans(struct btrfs_trans_handle *trans, + goto out; + + set_bit(BTRFS_ROOT_HAS_LOG_TREE, &root->state); +- clear_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- root->log_start_pid = current->pid; + } + + atomic_inc(&root->log_writers); +@@ -372,13 +359,9 @@ static int join_running_log_trans(struct btrfs_root *root) + mutex_lock(&root->log_mutex); + again: + if (root->log_root) { +- int index = (root->log_transid + 1) % 2; +- + ret = 0; +- if (zoned && atomic_read(&root->log_commit[index])) { +- wait_log_commit(root, root->log_transid - 1); ++ if (zoned && wait_log_commit(root, root->log_transid - 1)) + goto again; +- } + atomic_inc(&root->log_writers); + } + mutex_unlock(&root->log_mutex); +@@ -2986,6 +2969,7 @@ static noinline int walk_down_log_tree(struct btrfs_path *path, int *level, + { + struct btrfs_trans_handle *trans = wc->trans; + struct btrfs_fs_info *fs_info = wc->log->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + u64 bytenr; + u64 ptr_gen; + struct extent_buffer *next; +@@ -3010,7 +2994,7 @@ static noinline int walk_down_log_tree(struct btrfs_path *path, int *level, + check.has_first_key = true; + btrfs_node_key_to_cpu(cur, &check.first_key, path->slots[*level]); + +- next = btrfs_find_create_tree_block(fs_info, bytenr, ++ next = btrfs_find_create_tree_block(fs_info, &pa, bytenr, + btrfs_header_owner(cur), + *level - 1); + if (IS_ERR(next)) { +@@ -3181,10 +3165,14 @@ static int update_log_root(struct btrfs_trans_handle *trans, + return ret; + } + +-static void wait_log_commit(struct btrfs_root *root, int transid) ++/* Returns true if we had to wait, false otherwise. */ ++static bool wait_log_commit(struct btrfs_root *root, int transid) + { + DEFINE_WAIT(wait); +- int index = transid % 2; ++ const int index = (transid >= 0 ? transid % 2 : -transid % 2); ++ ++ if (!root->log_commit[index]) ++ return false; + + /* + * we only allow two pending log transactions at a time, +@@ -3195,15 +3183,17 @@ static void wait_log_commit(struct btrfs_root *root, int transid) + prepare_to_wait(&root->log_commit_wait[index], + &wait, TASK_UNINTERRUPTIBLE); + +- if (!(root->log_transid_committed < transid && +- atomic_read(&root->log_commit[index]))) +- break; +- + mutex_unlock(&root->log_mutex); + schedule(); + mutex_lock(&root->log_mutex); ++ ++ if (!(root->log_transid_committed < transid && ++ root->log_commit[index])) ++ break; + } + finish_wait(&root->log_commit_wait[index], &wait); ++ ++ return true; + } + + static void wait_for_writer(struct btrfs_root *root) +@@ -3307,15 +3297,15 @@ static inline void btrfs_remove_all_log_ctxs(struct btrfs_root *root, + int btrfs_sync_log(struct btrfs_trans_handle *trans, + struct btrfs_root *root, struct btrfs_log_ctx *ctx) + { +- int index1; +- int index2; + int mark; + int ret; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_root *log = root->log_root; + struct btrfs_root *log_root_tree = fs_info->log_root_tree; + struct btrfs_root_item new_root_item; +- int log_transid = 0; ++ int log_transid = ctx->log_transid; ++ int index1 = log_transid % 2; ++ int index2; + struct btrfs_log_ctx root_log_ctx; + struct blk_plug plug; + u64 log_root_start; +@@ -3323,41 +3313,25 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + mutex_lock(&root->log_mutex); + trace_btrfs_sync_log_enter(trans, root, ctx); +- log_transid = ctx->log_transid; + if (root->log_transid_committed >= log_transid) { + trace_btrfs_sync_log_exit(trans, root, ctx, ctx->log_ret); + mutex_unlock(&root->log_mutex); + return ctx->log_ret; + } + +- index1 = log_transid % 2; +- if (atomic_read(&root->log_commit[index1])) { +- wait_log_commit(root, log_transid); ++ if (wait_log_commit(root, log_transid)) { + trace_btrfs_sync_log_exit(trans, root, ctx, ctx->log_ret); + mutex_unlock(&root->log_mutex); + return ctx->log_ret; + } + ASSERT(log_transid == root->log_transid, + "log_transid=%d root->log_transid=%d", log_transid, root->log_transid); +- atomic_set(&root->log_commit[index1], 1); ++ root->log_commit[index1] = true; + + /* wait for previous tree log sync to complete */ +- if (atomic_read(&root->log_commit[(index1 + 1) % 2])) +- wait_log_commit(root, log_transid - 1); ++ wait_log_commit(root, log_transid - 1); + +- while (1) { +- int batch = atomic_read(&root->log_batch); +- /* when we're on an ssd, just kick the log commit out */ +- if (!btrfs_test_opt(fs_info, SSD) && +- test_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state)) { +- mutex_unlock(&root->log_mutex); +- schedule_timeout_uninterruptible(1); +- mutex_lock(&root->log_mutex); +- } +- wait_for_writer(root); +- if (batch == atomic_read(&root->log_batch)) +- break; +- } ++ wait_for_writer(root); + + /* bail out if we need to do a full commit */ + if (btrfs_need_log_full_commit(trans)) { +@@ -3414,7 +3388,6 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + btrfs_set_root_log_transid(root, root->log_transid + 1); + log->log_transid = root->log_transid; +- root->log_start_pid = 0; + /* + * IO has been started, blocks of the log tree have WRITTEN flag set + * in their headers. new modifications of the log will be written to +@@ -3473,7 +3446,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + goto out; + } + +- if (atomic_read(&log_root_tree->log_commit[index2])) { ++ if (log_root_tree->log_commit[index2]) { + blk_finish_plug(&plug); + ret = btrfs_wait_tree_log_extents(log, mark); + wait_log_commit(log_root_tree, +@@ -3487,12 +3460,9 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + ASSERT(root_log_ctx.log_transid == log_root_tree->log_transid, + "root_log_ctx.log_transid=%d log_root_tree->log_transid=%d", + root_log_ctx.log_transid, log_root_tree->log_transid); +- atomic_set(&log_root_tree->log_commit[index2], 1); ++ log_root_tree->log_commit[index2] = true; + +- if (atomic_read(&log_root_tree->log_commit[(index2 + 1) % 2])) { +- wait_log_commit(log_root_tree, +- root_log_ctx.log_transid - 1); +- } ++ wait_log_commit(log_root_tree, root_log_ctx.log_transid - 1); + + /* + * now that we've moved on to the tree of log tree roots, +@@ -3590,7 +3560,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + /* + * We know there can only be one task here, since we have not yet set +- * root->log_commit[index1] to 0 and any task attempting to sync the ++ * root->log_commit[index1] to false and any task attempting to sync the + * log must wait for the previous log transaction to commit if it's + * still in progress or wait for the current log transaction commit if + * someone else already started it. We use <= and not < because the +@@ -3606,7 +3576,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + btrfs_remove_all_log_ctxs(log_root_tree, index2, ret); + + log_root_tree->log_transid_committed++; +- atomic_set(&log_root_tree->log_commit[index2], 0); ++ log_root_tree->log_commit[index2] = false; + mutex_unlock(&log_root_tree->log_mutex); + + /* +@@ -3619,7 +3589,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + mutex_lock(&root->log_mutex); + btrfs_remove_all_log_ctxs(root, index1, ret); + root->log_transid_committed++; +- atomic_set(&root->log_commit[index1], 0); ++ root->log_commit[index1] = false; + mutex_unlock(&root->log_mutex); + + /* +@@ -5622,6 +5592,15 @@ static int btrfs_log_holes(struct btrfs_trans_handle *trans, + if (!btrfs_fs_incompat(fs_info, NO_HOLES) || i_size == 0) + return 0; + ++ /* ++ * If there are no prealloc extents (which can be located past i_size), ++ * and disk space used is greater than or equals to i_size, then there ++ * are no holes. ++ */ ++ if (!(inode->flags & BTRFS_INODE_PREALLOC) && ++ i_size <= inode_get_bytes(&inode->vfs_inode)) ++ return 0; ++ + key.objectid = ino; + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = 0; +diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c +--- a/fs/btrfs/verity.c ++++ b/fs/btrfs/verity.c +@@ -652,7 +652,7 @@ static int btrfs_end_enable_verity(struct file *filp, const void *desc, + rollback_ret = rollback_verity(inode); + if (rollback_ret) + btrfs_err(inode->root->fs_info, +- "failed to rollback verity items: %d", rollback_ret); ++ "failed to rollback verity items: %pe", ERR_PTR(rollback_ret)); + return ret; + } + +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -740,41 +740,6 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) + return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; + } + +-static bool is_same_device(struct btrfs_device *device, const char *new_path) +-{ +- struct path old = { .mnt = NULL, .dentry = NULL }; +- struct path new = { .mnt = NULL, .dentry = NULL }; +- char AUTO_KFREE(old_path); +- bool is_same = false; +- int ret; +- +- if (!device->name) +- goto out; +- +- old_path = kzalloc(PATH_MAX, GFP_NOFS); +- if (!old_path) +- goto out; +- +- rcu_read_lock(); +- ret = strscpy(old_path, rcu_dereference(device->name), PATH_MAX); +- rcu_read_unlock(); +- if (ret < 0) +- goto out; +- +- ret = kern_path(old_path, LOOKUP_FOLLOW, &old); +- if (ret) +- goto out; +- ret = kern_path(new_path, LOOKUP_FOLLOW, &new); +- if (ret) +- goto out; +- if (path_equal(&old, &new)) +- is_same = true; +-out: +- path_put(&old); +- path_put(&new); +- return is_same; +-} +- + /* + * Add new device to list of registered devices + * +@@ -895,7 +860,7 @@ static noinline struct btrfs_device *device_list_add(const char *path, + MAJOR(path_devt), MINOR(path_devt), + current->comm, task_pid_nr(current)); + +- } else if (!device->name || !is_same_device(device, path)) { ++ } else if (!device->name || device->devt != path_devt) { + const char *old_name; + + /* +@@ -4592,7 +4557,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info) + if (ret == -ENOSPC) { + enospc_errors++; + } else if (ret == -ETXTBSY) { +- btrfs_info(fs_info, ++ btrfs_warn(fs_info, + "skipping relocation of block group %llu due to active swapfile", + found_key.offset); + ret = 0; +@@ -6053,6 +6018,19 @@ struct btrfs_chunk_map *btrfs_alloc_chunk_map(int num_stripes, gfp_t gfp) + return map; + } + ++static void set_real_chunk_type(struct btrfs_chunk_map *map) ++{ ++ map->type = map->on_disk_type; ++ if (likely((map->on_disk_type & BTRFS_BLOCK_GROUP_RAID56_MASK) == 0 || ++ nr_data_stripes(map) > 1)) ++ return; ++ if (map->on_disk_type & BTRFS_BLOCK_GROUP_RAID5) ++ map->type |= BTRFS_BLOCK_GROUP_RAID1; ++ else ++ map->type |= BTRFS_BLOCK_GROUP_RAID1C3; ++ map->type &= ~BTRFS_BLOCK_GROUP_RAID56_MASK; ++} ++ + static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, + struct alloc_chunk_ctl *ctl, + struct btrfs_device_info *devices_info) +@@ -6071,11 +6049,10 @@ static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, + map->start = start; + map->chunk_len = ctl->chunk_size; + map->stripe_size = ctl->stripe_size; +- map->type = type; +- map->io_align = BTRFS_STRIPE_LEN; +- map->io_width = BTRFS_STRIPE_LEN; ++ map->on_disk_type = type; + map->sub_stripes = ctl->sub_stripes; + map->num_stripes = ctl->num_stripes; ++ set_real_chunk_type(map); + + for (int i = 0; i < ctl->ndevs; i++) { + for (int j = 0; j < ctl->dev_stripes; j++) { +@@ -6254,7 +6231,7 @@ int btrfs_chunk_alloc_add_chunk_item(struct btrfs_trans_handle *trans, + btrfs_set_stack_chunk_length(chunk, bg->length); + btrfs_set_stack_chunk_owner(chunk, BTRFS_EXTENT_TREE_OBJECTID); + btrfs_set_stack_chunk_stripe_len(chunk, BTRFS_STRIPE_LEN); +- btrfs_set_stack_chunk_type(chunk, map->type); ++ btrfs_set_stack_chunk_type(chunk, map->on_disk_type); + btrfs_set_stack_chunk_num_stripes(chunk, map->num_stripes); + btrfs_set_stack_chunk_io_align(chunk, BTRFS_STRIPE_LEN); + btrfs_set_stack_chunk_io_width(chunk, BTRFS_STRIPE_LEN); +@@ -7636,9 +7613,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, + map->start = logical; + map->chunk_len = length; + map->num_stripes = num_stripes; +- map->io_width = btrfs_chunk_io_width(leaf, chunk); +- map->io_align = btrfs_chunk_io_align(leaf, chunk); +- map->type = type; ++ map->on_disk_type = type; + /* + * We can't use the sub_stripes value, as for profiles other than + * RAID10, they may have 0 as sub_stripes for filesystems created by +@@ -7649,6 +7624,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, + */ + map->sub_stripes = btrfs_raid_array[index].sub_stripes; + map->verified_stripes = 0; ++ set_real_chunk_type(map); + + if (num_stripes > 0) + map->stripe_size = btrfs_calc_stripe_length(map); +diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h +--- a/fs/btrfs/volumes.h ++++ b/fs/btrfs/volumes.h +@@ -632,9 +632,15 @@ struct btrfs_chunk_map { + u64 start; + u64 chunk_len; + u64 stripe_size; ++ /* ++ * The real type that is utilized during logical address mapping. ++ * ++ * For most profiles it matches @on_disk_type, but for single-data-RAID56, ++ * the real type will be set to RAID1/RAID1C3, to avoid unsupported ++ * operations from raid56 lib. ++ */ + u64 type; +- int io_align; +- int io_width; ++ u64 on_disk_type; + int num_stripes; + int sub_stripes; + struct btrfs_io_stripe stripes[]; +diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h +--- a/include/trace/events/btrfs.h ++++ b/include/trace/events/btrfs.h +@@ -1613,9 +1613,9 @@ TRACE_EVENT(btrfs_sync_log_enter, + __entry->log_transid_committed = + data_race(root->log_transid_committed); + __entry->log_committing = +- atomic_read(&root->log_commit[ctx->log_transid % 2]); ++ data_race(root->log_commit[ctx->log_transid % 2]); + __entry->log_committing_prev = +- atomic_read(&root->log_commit[(ctx->log_transid + 1) % 2]); ++ data_race(root->log_commit[(ctx->log_transid + 1) % 2]); + __entry->log_writers = atomic_read(&root->log_writers); + ), + diff --git a/pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch.sig b/pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..9d469a9b497626fdbcf95d776ce563836e1666e2 GIT binary patch literal 594 zcmV-Y0@tVu&bos zm`Gd2jfUt=UF%MPn`U>aY-a9Ke~9D%C^H=bHr+9IP&ZGkk{r$|XdM1 zU=`RPS-$4x=k`dyM7VnF@sge|)hc%FrXwn0Y6ZZX`3^3=hk=BB919k(|UKb>J$1g!k7l~G`XstlM{7{Z5E z5!8~RsX9gsc>7c3Mr%8VM;kryNN(1Oqw_XsRESoP+vXi<{E@P?{oY6LXNE4nhvTql zk7ZHNrLP#pZVPh~{r%=ITsHpEl|@!PiMiuA-37BZ1EplQsL`B^p;~N0-giS>xVBq( z?}-K&z?&DRj7RD@XfQsV6h+7wstAjIm*Ab70LUMbiC{$pjbAI++#O?GBkAyd{f-rgok?orfCiUfPk){51e;sW)#mI}cMwhHAjzA_V$pXX g;#PGC&9+(NuPHig-IlpvO7Gk}(2zv!M(6*ZDmGLe8vprwsem); + dev_replace->replace_task = current; +diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -384,6 +384,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags &= ~BTRFS_INODE_COMPRESS; + inode_flags |= BTRFS_INODE_NOCOMPRESS; + } else if (fsflags & FS_COMPR_FL) { ++ enum btrfs_compression_type comp_type; + + if (IS_SWAPFILE(&inode->vfs_inode)) + return -ETXTBSY; +@@ -391,9 +392,23 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags |= BTRFS_INODE_COMPRESS; + inode_flags &= ~BTRFS_INODE_NOCOMPRESS; + +- comp = btrfs_compress_type2str(fs_info->compress_type); +- if (!comp || comp[0] == 0) +- comp = btrfs_compress_type2str(BTRFS_COMPRESS_ZLIB); ++ /* ++ * Keep the algorithm recorded in the compression property, ++ * otherwise changing an unrelated attribute would reset it to ++ * the mount default, since FS_IOC_SETFLAGS callers write back ++ * the whole flag set they got from FS_IOC_GETFLAGS and that ++ * includes FS_COMPR_FL for any inode carrying the property. ++ * ++ * Inodes with the compress flag set but no property keep using ++ * the mount default, so they behave as before. ++ */ ++ if (inode->prop_compress) ++ comp_type = inode->prop_compress; ++ else if (fs_info->compress_type) ++ comp_type = fs_info->compress_type; ++ else ++ comp_type = BTRFS_COMPRESS_ZLIB; ++ comp = btrfs_compress_type2str(comp_type); + } else { + inode_flags &= ~(BTRFS_INODE_COMPRESS | BTRFS_INODE_NOCOMPRESS); + } +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -6417,6 +6417,13 @@ static int process_extent(struct send_ctx *sctx, + + if (S_ISLNK(sctx->cur_inode_mode)) + return 0; ++ if (unlikely(!S_ISREG(sctx->cur_inode_mode))) { ++ btrfs_crit(sctx->send_root->fs_info, ++ "send: extent for non-regular inode %llu root %llu mode 0%llo", ++ key->objectid, btrfs_root_id(sctx->send_root), ++ sctx->cur_inode_mode & S_IFMT); ++ return -EUCLEAN; ++ } + + if (sctx->parent_root && !sctx->cur_inode_new) { + ret = is_extent_unchanged(sctx, path, key); +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2710,6 +2710,7 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + { + struct btrfs_block_group *block_group; + u64 min_alloc_bytes; ++ int ret = 0; + + if (!btrfs_is_zoned(fs_info)) + return 0; +@@ -2729,11 +2730,11 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + block_group->start + block_group->zone_capacity) + goto out; + +- do_zone_finish(block_group, true); ++ ret = do_zone_finish(block_group, true); + + out: + btrfs_put_block_group(block_group); +- return 0; ++ return ret; + } + + static void btrfs_zone_finish_endio_workfn(struct work_struct *work) diff --git a/pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f05ee3b0434e2040caee4f99b6ee3927a8eefcde GIT binary patch literal 594 zcmV-Y0s?c9D5>{-Q5_JFSze$cIrzx zdDI1DTcZ*f!0CnCx(o9jt)>5o}1o3SiW%`G3&xY=;orV%qj7WqW=n5~FgG95t;4`Vj~qT-~d|NIl%yLDDC|SfSND-Xld*Fp gOx%_=Sx+wS3-Tn`FDt<6ie9z+-nUUBG}Op8DC%?`4FCWD literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch b/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch new file mode 100644 index 0000000..d949f95 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch @@ -0,0 +1,109 @@ +diff --git c/fs/btrfs/zstd.c i/fs/btrfs/zstd.c +--- c/fs/btrfs/zstd.c ++++ i/fs/btrfs/zstd.c +@@ -589,10 +589,48 @@ int zstd_compress_bio(struct list_head *ws, struct compressed_bio *cb) + return ret; + } + ++/* ++ * Map the destination for the next chunk of output. ++ * ++ * @decompressed is the offset of the next output byte inside the fully ++ * decompressed extent. If that offset has reached the current destination ++ * segment, its page-bounded bio_vec is kmapped so that zstd can write into the ++ * page cache directly, and the number of bytes writable there is returned. ++ * Otherwise @kaddr_ret is set to NULL and the number of bytes to skip before ++ * that segment is returned. This covers both the initial prefix and gaps in ++ * the destination bio. ++ */ ++static u32 zstd_map_dest(struct compressed_bio *cb, u32 decompressed, ++ void **kaddr_ret) ++{ ++ struct bio *orig_bio = &cb->orig_bbio->bio; ++ struct bio_vec bvec; ++ u32 bvec_offset; ++ u32 off; ++ ++ bvec = bio_iter_iovec(orig_bio, orig_bio->bi_iter); ++ /* ++ * cb->start may underflow, but subtracting that value can still give us ++ * the correct offset inside the full decompressed extent. ++ */ ++ bvec_offset = page_offset(bvec.bv_page) + bvec.bv_offset - cb->start; ++ ++ if (decompressed < bvec_offset) { ++ *kaddr_ret = NULL; ++ return bvec_offset - decompressed; ++ } ++ ++ off = decompressed - bvec_offset; ++ ASSERT(off < bvec.bv_len); ++ *kaddr_ret = bvec_kmap_local(&bvec) + off; ++ return bvec.bv_len - off; ++} ++ + int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + { + struct btrfs_fs_info *fs_info = cb_to_fs_info(cb); + struct workspace *workspace = list_entry(ws, struct workspace, list); ++ struct bio *orig_bio = &cb->orig_bbio->bio; + struct folio_iter fi; + size_t srclen = bio_get_size(&cb->bbio.bio); + zstd_dstream *stream; +@@ -600,7 +638,6 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + const unsigned int min_folio_size = btrfs_min_folio_size(fs_info); + unsigned long folio_in_index = 0; + unsigned long total_folios_in = DIV_ROUND_UP(srclen, min_folio_size); +- unsigned long buf_start; + unsigned long total_out = 0; + + bio_first_folio(&fi, &cb->bbio.bio, 0); +@@ -624,15 +661,26 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + workspace->in_buf.pos = 0; + workspace->in_buf.size = min_t(size_t, srclen, min_folio_size); + +- workspace->out_buf.dst = workspace->buf; +- workspace->out_buf.pos = 0; +- workspace->out_buf.size = fs_info->sectorsize; +- +- while (1) { ++ while (orig_bio->bi_iter.bi_size) { + size_t ret2; ++ void *kaddr; ++ u32 dstlen; ++ ++ dstlen = zstd_map_dest(cb, total_out, &kaddr); ++ if (kaddr) { ++ workspace->out_buf.dst = kaddr; ++ workspace->out_buf.size = dstlen; ++ } else { ++ workspace->out_buf.dst = workspace->buf; ++ workspace->out_buf.size = min_t(u32, dstlen, ++ fs_info->sectorsize); ++ } ++ workspace->out_buf.pos = 0; + + ret2 = zstd_decompress_stream(stream, &workspace->out_buf, + &workspace->in_buf); ++ if (kaddr) ++ kunmap_local(kaddr); + if (unlikely(zstd_is_error(ret2))) { + struct btrfs_inode *inode = cb->bbio.inode; + +@@ -643,14 +691,9 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + ret = -EIO; + goto done; + } +- buf_start = total_out; + total_out += workspace->out_buf.pos; +- workspace->out_buf.pos = 0; +- +- ret = btrfs_decompress_buf2page(workspace->out_buf.dst, +- total_out - buf_start, cb, buf_start); +- if (ret == 0) +- break; ++ if (kaddr) ++ bio_advance(orig_bio, workspace->out_buf.pos); + + if (workspace->in_buf.pos >= srclen) + break; diff --git a/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch.sig b/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..a75cf5126579aae916bf666e2ddc9a3e1465e0ee GIT binary patch literal 594 zcmV-Y0O3-I9lG z!(z{ndY=E5=oQWPC5wl%YA`w2BqoZOgwmC$nZ=q-xgpu1h>}6wddSXm%N1xf2II_x%1t$9d_{ zEQpXsv;~x;!?MXZd!{8v_d9Hd*@^N`+$dEd6)@w2RodNOL4@Y^Uh_?B3d2&=B`|P@jG;`{CaJg_L<~q|61#rF6ut*o{`0r zgBHYaQYxjDy~`;!dKs5-%>bYQf%D#CpWvc$)B~v9$kYp4eJ1nb_>U=2RQnTzjK${l g=hLh{#D*Zdp?$b&>3$9cdvrV+pnj3jmruGP^#A|> literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch new file mode 100644 index 0000000..42654e7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch @@ -0,0 +1,97 @@ +diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c +--- a/fs/btrfs/block-group.c ++++ b/fs/btrfs/block-group.c +@@ -3074,6 +3074,18 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran + return ERR_PTR(ret); + } + ++ /* ++ * Ensure the corresponding space_info object is created and ++ * assigned to our block group. We want our bg to be added to the rbtree ++ * with its ->space_info set. ++ * ++ * On a zoned filesystem btrfs_add_new_free_space() ends up in ++ * __btrfs_add_free_space_zoned(), which dereferences ++ * block_group->space_info, so it has to be set beforehand. ++ */ ++ cache->space_info = space_info; ++ ASSERT(cache->space_info); ++ + ret = btrfs_add_new_free_space(cache, chunk_offset, chunk_offset + size, NULL); + btrfs_free_excluded_extents(cache); + if (ret) { +@@ -3081,14 +3093,6 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran + return ERR_PTR(ret); + } + +- /* +- * Ensure the corresponding space_info object is created and +- * assigned to our block group. We want our bg to be added to the rbtree +- * with its ->space_info set. +- */ +- cache->space_info = space_info; +- ASSERT(cache->space_info); +- + ret = btrfs_add_block_group_cache(cache); + if (ret) { + btrfs_remove_free_space_cache(cache); +diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c +--- a/fs/btrfs/tree-checker.c ++++ b/fs/btrfs/tree-checker.c +@@ -1909,6 +1909,16 @@ static int check_inode_ref(struct extent_buffer *leaf, + return -EUCLEAN; + } + ++ if (unlikely(btrfs_is_fstree(btrfs_header_owner(leaf)) && ++ (key->offset < BTRFS_FIRST_FREE_OBJECTID || ++ key->offset > BTRFS_LAST_FREE_OBJECTID))) { ++ inode_ref_err(leaf, slot, ++ "invalid offset for ref key, have %llu expect [%llu, %lld]", ++ key->offset, BTRFS_FIRST_FREE_OBJECTID, ++ BTRFS_LAST_FREE_OBJECTID); ++ return -EUCLEAN; ++ } ++ + ptr = btrfs_item_ptr_offset(leaf, slot); + end = ptr + btrfs_item_size(leaf, slot); + while (ptr < end) { +@@ -1952,12 +1962,14 @@ static int check_inode_extref(struct extent_buffer *leaf, + { + unsigned long ptr = btrfs_item_ptr_offset(leaf, slot); + unsigned long end = ptr + btrfs_item_size(leaf, slot); ++ const bool is_fstree = btrfs_is_fstree(btrfs_header_owner(leaf)); + + if (unlikely(!check_prev_ino(leaf, key, slot, prev_key))) + return -EUCLEAN; + + while (ptr < end) { + struct btrfs_inode_extref *extref = (struct btrfs_inode_extref *)ptr; ++ u64 parent; + u16 namelen; + + if (unlikely(ptr + sizeof(*extref) > end)) { +@@ -1967,7 +1979,24 @@ static int check_inode_extref(struct extent_buffer *leaf, + return -EUCLEAN; + } + ++ parent = btrfs_inode_extref_parent(leaf, extref); ++ if (unlikely(is_fstree && (parent < BTRFS_FIRST_FREE_OBJECTID || ++ parent > BTRFS_LAST_FREE_OBJECTID))) { ++ inode_ref_err(leaf, slot, ++ "invalid parent for extref key, have %llu expect [%llu, %lld]", ++ parent, BTRFS_FIRST_FREE_OBJECTID, ++ BTRFS_LAST_FREE_OBJECTID); ++ return -EUCLEAN; ++ } ++ + namelen = btrfs_inode_extref_name_len(leaf, extref); ++ if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) { ++ inode_ref_err(leaf, slot, ++ "invalid inode extref name length, has %u expect [1, %u]", ++ namelen, BTRFS_NAME_LEN); ++ return -EUCLEAN; ++ } ++ + if (unlikely(ptr + sizeof(*extref) + namelen > end)) { + inode_ref_err(leaf, slot, + "inode extref overflow, ptr %lu end %lu namelen %u", diff --git a/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..c75c8f33fe5385822cc43dc91ca3674575ffea4d GIT binary patch literal 594 zcmV-Y0{I-5_$@nEuz-w4;?4E+0I&Cgsb2NBy(kZeYEx&>;M1& literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch b/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch new file mode 100644 index 0000000..7d1ab48 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch @@ -0,0 +1,77 @@ +diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c +--- a/fs/btrfs/tree-checker.c ++++ b/fs/btrfs/tree-checker.c +@@ -2306,7 +2306,7 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key + + if (unlikely(btrfs_item_size(leaf, slot) != 0)) { + generic_err(leaf, slot, +- "invalid item size for free space info, has %u expect 0", ++ "invalid item size for free space extent, has %u expect 0", + btrfs_item_size(leaf, slot)); + return -EUCLEAN; + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -740,6 +740,36 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) + return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; + } + ++static bool should_rename_device(const struct btrfs_device *dev) ++{ ++ bool ret; ++ const char *old_name; ++ ++ rcu_read_lock(); ++ old_name = rcu_dereference(dev->name); ++ /* ++ * For systems booted without an initramfs, the rootfs has the device ++ * name "/dev/root". ++ * ++ * Although using btrfs without an initramfs is not recommended (if a ++ * new device is added to the rootfs, the system can no longer boot, as ++ * there is no way to register all devices), there is still a minority ++ * of users doing this. ++ * ++ * And after the system is up, a later device scan on the real block ++ * device file will never get this device's name updated, as the ++ * device->devt is still the same. ++ * ++ * Here we add one and only one exception for "/dev/root", to allow the ++ * device name to be updated even if the new path points to the same ++ * block device. ++ */ ++ ret = (strcmp(old_name, "/dev/root") == 0); ++ rcu_read_unlock(); ++ ++ return ret; ++} ++ + /* + * Add new device to list of registered devices + * +@@ -860,7 +890,8 @@ static noinline struct btrfs_device *device_list_add(const char *path, + MAJOR(path_devt), MINOR(path_devt), + current->comm, task_pid_nr(current)); + +- } else if (!device->name || device->devt != path_devt) { ++ } else if (!device->name || device->devt != path_devt || ++ should_rename_device(device)) { + const char *old_name; + + /* +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2688,6 +2688,11 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags) + + switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) { + case 0: /* single */ ++ case BTRFS_BLOCK_GROUP_RAID0: ++ case BTRFS_BLOCK_GROUP_RAID1: ++ case BTRFS_BLOCK_GROUP_RAID1C3: ++ case BTRFS_BLOCK_GROUP_RAID1C4: ++ case BTRFS_BLOCK_GROUP_RAID10: + ret = (atomic_read(&zinfo->active_zones_left) >= (1 + reserved)); + break; + case BTRFS_BLOCK_GROUP_DUP: diff --git a/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..d471f2e0fb1ddb057134aed178bf1dedad2735c4 GIT binary patch literal 594 zcmV-Y0jlmKAW5B))`;YxI^7^MFnY3)MkD zF$FC-pIo`EHq%$gI60(Sq@Nc$xd-TBVjig@&j6h zGD%w7IGpEz0IRdo;cZq`XUz7hKYdEZ`=*v^=XubrIkQ6>+}>n1Jk43F1dC^U(wied zqwT5G{0U+GYFxgktl7j9PcWfyn8S__|8GJf-P~>&&_apEp+?J-88C?iET5njt%ZE{ zSe8rTWvaulm~CnI#J)464uSpBkft7?=$&tx+KBsZaS&qpj017JhxwALYlW7mn4o!t>_`ld4063u|fN>pn3A%ym4uS)T4F|^ zq4o~`47@YDxg+>jnaphYM*E6@j1EH+PMRArYkdv6G#hKvOZrlXK4I}Zf9HtKD@@m% z2v70Y1YG=A9r5BInDKY}#X*l4F&(7NWACnIKo_j(n)m?BR z^lKnHg22x!KqxmYi6_&!h>3W>@Rj4doGC>RX+N^fgZVqMS6t;N-DDm&Lf}6P$hA)J g-SInvb1y4ba#yYa)YNv|$AzF|YVamH72}iki_mode) && oldsize != outarg.attr.size) { ++ if (outarg.attr.size > oldsize) ++ truncate_pagecache_range(inode, oldsize, ++ outarg.attr.size - 1); + truncate_pagecache(inode, outarg.attr.size); + invalidate_inode_pages2(mapping); + } +diff --git a/fs/fuse/file.c b/fs/fuse/file.c +--- a/fs/fuse/file.c ++++ b/fs/fuse/file.c +@@ -1360,9 +1360,13 @@ static ssize_t fuse_perform_write(struct kiocb *iocb, struct iov_iter *ii) + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_inode *fi = get_fuse_inode(inode); + loff_t pos = iocb->ki_pos; ++ loff_t old_size = i_size_read(inode); + int err = 0; + ssize_t res = 0; + ++ if (pos > old_size) ++ truncate_pagecache_range(inode, old_size, pos - 1); ++ + if (inode->i_size < pos + iov_iter_count(ii)) + set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); + +@@ -2909,6 +2913,11 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, + + /* we could have extended the file */ + if (!(mode & FALLOC_FL_KEEP_SIZE)) { ++ loff_t oldsize = i_size_read(inode); ++ ++ if (offset + length > oldsize) ++ truncate_pagecache_range(inode, oldsize, ++ offset + length - 1); + if (fuse_write_update_attr(inode, offset + length, length)) + file_update_time(file); + } diff --git a/pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch.sig b/pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..e4796a224d7942f8261fa53eb612353e9cbb7f79 GIT binary patch literal 594 zcmV-Y051%TIr zg*_w1c4u|-im(Z#X3)?ku^LKGW5RBqHAZvdLo}-*Z!t>W&lQ@x1UO@#TybH-*Y!h&NWi{z#F%UONl}4Jx6pZVbITyGEh{F8iIP&4l2e@9 z#PNQ378W8bod^8}HTHBToVOci4-h+1mQE__9M!cQQL19`xgk4|qZt7-=pCFF&i?Vk z^5=9Rzsra&D1)mCmu)$H(9~dT!l9t6Nalwaitq + */ +-static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq) ++static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq, bool sync) + __releases(fiq->lock) + { +- wake_up(&fiq->waitq); ++ if (sync) ++ wake_up_sync(&fiq->waitq); ++ else ++ wake_up(&fiq->waitq); + kill_fasync(&fiq->fasync, SIGIO, POLL_IN); + spin_unlock(&fiq->lock); + } +@@ -233,7 +236,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, + if (fiq->connected) { + fiq->forget_list_tail->next = forget; + fiq->forget_list_tail = forget; +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } else { + kfree(forget); + spin_unlock(&fiq->lock); +@@ -255,7 +258,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) + list_del_init(&req->intr_entry); + spin_unlock(&fiq->lock); + } else { +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } + } else { + spin_unlock(&fiq->lock); +@@ -285,11 +288,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique); + + static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req) + { ++ bool sync = test_and_clear_bit(FR_SYNC_WAKEUP, &req->flags); ++ + spin_lock(&fiq->lock); + if (fiq->connected) { + fuse_request_assign_unique_locked(fiq, req); + list_add_tail(&req->list, &fiq->pending); +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, sync); + } else { + spin_unlock(&fiq->lock); + req->out.h.error = -ENOTCONN; +@@ -752,6 +757,11 @@ static void __fuse_request_send(struct fuse_req *req) + /* acquire extra reference, since request is still needed after + fuse_request_end() */ + __fuse_get_request(req); ++ /* ++ * This is a synchronous request: the caller will block waiting for ++ * the answer. Hint the scheduler via wake_up_sync(). ++ */ ++ set_bit(FR_SYNC_WAKEUP, &req->flags); + fuse_send_one(fiq, req); + + request_wait_answer(req); +@@ -1806,7 +1816,7 @@ void fuse_chan_resend(struct fuse_chan *fch) + } + /* iq and pq requests are both oldest to newest */ + list_splice(&to_queue, &fiq->pending); +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } + + /* Look up request on processing list by unique ID */ +diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h +--- a/fs/fuse/fuse_dev_i.h ++++ b/fs/fuse/fuse_dev_i.h +@@ -38,6 +38,8 @@ struct fuse_iqueue; + * @FR_PRIVATE: request is on private list + * @FR_ASYNC: request is asynchronous + * @FR_URING: request is handled through fuse-io-uring ++ * @FR_SYNC_WAKEUP: use synchronous wakeup when queueing this request to ++ * give the scheduler a hint about the waker task + */ + enum fuse_req_flag { + FR_ISREPLY, +@@ -53,6 +55,7 @@ enum fuse_req_flag { + FR_PRIVATE, + FR_ASYNC, + FR_URING, ++ FR_SYNC_WAKEUP, + }; + + /** +diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c +--- a/fs/fuse/inode.c ++++ b/fs/fuse/inode.c +@@ -1420,6 +1420,7 @@ static void process_init_reply(struct fuse_args *args, int error) + + fm->sb->s_bdi->ra_pages = + min(fm->sb->s_bdi->ra_pages, ra_pages); ++ fm->sb->s_bdi->io_pages = fc->max_pages; + fc->minor = arg->minor; + fc->max_write = arg->minor < 5 ? 4096 : arg->max_write; + fc->max_write = max_t(unsigned, 4096, fc->max_write); diff --git a/pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch.sig b/pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..9bbc4c6971ff57ac60c7818bfae738466a30aa80 GIT binary patch literal 594 zcmV-Y0gQON) zlrR8ADNjoFxb^|5-n7=4i>e(Z$0rsVg~X%*_4gW8Ajj+;nbDkQ4$;n*2e54(nhAFCyELN3 zW4vN|%Y@U=>f^rqh_14-Xq}uiaN?iXn-_Pkfby$mjGX2nTTSLHJ${`xSR=Cviwm8P zuT$o$Tm&lBa1f8$g%g~_m8mCAU9d%Nx`vAQ=$d$Aj_~j8$5v3Qfyk%NisD2G><_F{ zRrQC` zAJ4Nmw%f-d4yeYX1kESO&5NyeT>PdzdDvRKl|O@uLpujx(Of?&+}QPFN^#1KeNR=c zfat)%{Lo4(z2A67U-C<EcV3B-0a4`c(TO`qnUW?ti zJ?P;eFaC2AP&1yt=AMj5N8}Y#u7XHZzH@DYctSxdAt_pD?qZNxPr{mGtIzv1g!6TvCY_e!kXIFt;E4~iNKg6 gO8hj6gA1p|U4>yu*UW)p)4)mdn3dCr%dNF?zQsfxi~s-t literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch b/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch new file mode 100644 index 0000000..8aaf4c6 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch @@ -0,0 +1,38 @@ +diff --git a/fs/fuse/file.c b/fs/fuse/file.c +--- a/fs/fuse/file.c ++++ b/fs/fuse/file.c +@@ -1219,8 +1219,7 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia, + struct file *file = iocb->ki_filp; + struct fuse_file *ff = file->private_data; + struct fuse_mount *fm = ff->fm; +- unsigned int offset, i; +- bool short_write; ++ unsigned int i; + int err; + + for (i = 0; i < ap->num_folios; i++) +@@ -1235,24 +1234,9 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia, + if (!err && ia->write.out.size > count) + err = -EIO; + +- short_write = ia->write.out.size < count; +- offset = ap->descs[0].offset; +- count = ia->write.out.size; + for (i = 0; i < ap->num_folios; i++) { + struct folio *folio = ap->folios[i]; + +- if (err) { +- folio_clear_uptodate(folio); +- } else { +- if (count >= folio_size(folio) - offset) +- count -= folio_size(folio) - offset; +- else { +- if (short_write) +- folio_clear_uptodate(folio); +- count = 0; +- } +- offset = 0; +- } + if (ia->write.folio_locked && (i == ap->num_folios - 1)) + folio_unlock(folio); + folio_put(folio); diff --git a/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch.sig b/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..391ede6f95fc98d4884f13c9b114520dd48768bc GIT binary patch literal 594 zcmV-Y06&Ea;MZ#g- z=N$lxogZqKxKLz3gE6eF4I|xV2|7E37jO0x5w`Re>89wch5FYVH@mg&3G}?M<6zzjoi%hlL5cSi5Uo#Fi5bniD4AO zcfEN=KOi0Vb}K0jDUnD~d`|{vo)1jEnjWqnqA)zio?GP-l+;W>2S*Xlw1grOVgG#7 zj7hf9RiMK5^qD-Qpt2)or8vl@!oKU4;{kP4%)o&En;G4 zqD9pZK4(F8ak9)?eE|z9X3DEh^L)Tqj75A@_WG?2>P|+jC4~AQiu*$}G;b1p27;c3 gW}0*OVcndteInzYkbz}j`Dl@rYz;BEmR-36nwy~xcK`qY literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch b/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch new file mode 100644 index 0000000..27fbcdc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch @@ -0,0 +1,13 @@ +diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c +--- a/fs/fuse/dev.c ++++ b/fs/fuse/dev.c +@@ -406,7 +406,8 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val) + fch->max_background = val; + fch->blocked = fch->num_background >= fch->max_background; + if (!fch->blocked) +- wake_up(&fch->blocked_waitq); ++ wake_up_nr(&fch->blocked_waitq, ++ fch->max_background - fch->num_background); + spin_unlock(&fch->bg_lock); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch.sig b/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..cfd1cd7ad09dbdbaced6eca5e005f39b43b30998 GIT binary patch literal 594 zcmV-Y0)XN1e?|dDwVn&K}}&35T84N z``!3`QkGV1^=i>O^cmRf(eQg@)TW4vBn)642ow4$om`S~Zr##33OqFF48_4Ujv1VH zy2thVQ&5YOFS*IT+yEa=M-A#{54+JxF8PdEgXJjU-gR1JYf|yItrfmu7_n;ZYs5FE z)!tgzwBuzGmV^K%mF7sntpect6HlOLmuu|!&}J)U`(^fdZA14P)RVo{Jtpq{w_I$i z`fIH8D((nGfNg80d^x+P0%M@v-wZ6u+5x$~ za4`WG2XLsh_`?_#j7nboD+)=om0N#ernFi?M<~zQg;<*K@exCdVt|2|4(HXJL=M9z zOjy8hKgZ;$2??x0%9 z*R=7=rl!^w3>J~}VJRr7EFov0SapGuRZhts?<{*wqT_8JuJUvYV-QY8aX_L9`e|qx gaiyROwg`DpBQGTz!)4Da7a@)VN2p2uM|zHV!7cq4qW}N^ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch b/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch new file mode 100644 index 0000000..eba1599 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch @@ -0,0 +1,119 @@ +diff --git a/fs/internal.h b/fs/internal.h +--- a/fs/internal.h ++++ b/fs/internal.h +@@ -204,6 +204,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, + int flag); + int chown_common(const struct path *path, uid_t user, gid_t group); + extern int vfs_open(const struct path *, struct file *); ++int vfs_open_consume(struct path *, struct file *); + + /* + * inode.c +diff --git a/fs/namei.c b/fs/namei.c +--- a/fs/namei.c ++++ b/fs/namei.c +@@ -4652,6 +4652,7 @@ static const char *open_last_lookups(struct nameidata *nd, + static int do_open(struct nameidata *nd, + struct file *file, const struct open_flags *op) + { ++ struct vfsmount *mnt; + struct mnt_idmap *idmap; + int open_flag = op->open_flag; + bool do_truncate; +@@ -4693,11 +4694,17 @@ static int do_open(struct nameidata *nd, + error = mnt_want_write(nd->path.mnt); + if (error) + return error; ++ /* ++ * A dedicated reference is needed because after the call to ++ * vfs_open_consume() we no longer own the reference in nd->path.mnt ++ * while we need to undo write acess below. ++ */ ++ mnt = mntget(nd->path.mnt); + do_truncate = true; + } + error = may_open(idmap, &nd->path, acc_mode, open_flag); + if (!error && !(file->f_mode & FMODE_OPENED)) +- error = vfs_open(&nd->path, file); ++ error = vfs_open_consume(&nd->path, file); + if (!error) + error = security_file_post_open(file, op->acc_mode); + if (!error && do_truncate) +@@ -4706,8 +4713,10 @@ static int do_open(struct nameidata *nd, + WARN_ON(1); + error = -EINVAL; + } +- if (do_truncate) +- mnt_drop_write(nd->path.mnt); ++ if (do_truncate) { ++ mnt_drop_write(mnt); ++ mntput(mnt); ++ } + return error; + } + +diff --git a/fs/open.c b/fs/open.c +--- a/fs/open.c ++++ b/fs/open.c +@@ -882,6 +882,11 @@ static inline int file_get_write_access(struct file *f) + return error; + } + ++/* ++ * Populate struct file ++ * ++ * NOTE: it assumes f_path is populated and consumes the caller's reference. ++ */ + static int do_dentry_open(struct file *f, + int (*open)(struct inode *, struct file *)) + { +@@ -889,7 +894,6 @@ static int do_dentry_open(struct file *f, + struct inode *inode = f->f_path.dentry->d_inode; + int error; + +- path_get(&f->f_path); + f->f_inode = inode; + f->f_mapping = inode->i_mapping; + f->f_wb_err = filemap_sample_wb_err(f->f_mapping); +@@ -1006,6 +1010,7 @@ int finish_open(struct file *file, struct dentry *dentry, + BUG_ON(file->f_mode & FMODE_OPENED); /* once it's opened, it's opened */ + + file->__f_path.dentry = dentry; ++ path_get(&file->f_path); + return do_dentry_open(file, open); + } + EXPORT_SYMBOL(finish_open); +@@ -1049,6 +1054,7 @@ int vfs_open(const struct path *path, struct file *file) + int ret; + + file->__f_path = *path; ++ path_get(&file->f_path); + ret = do_dentry_open(file, NULL); + if (!ret) { + /* +@@ -1061,6 +1067,25 @@ int vfs_open(const struct path *path, struct file *file) + return ret; + } + ++/** ++ * vfs_open_consume - open the file at the given path and consume the reference ++ * @path: path to open ++ * @file: newly allocated file with f_flag initialized ++ */ ++int vfs_open_consume(struct path *path, struct file *file) ++{ ++ int ret; ++ ++ file->__f_path = *path; ++ path->mnt = NULL; ++ path->dentry = NULL; ++ ret = do_dentry_open(file, NULL); ++ if (!ret) { ++ fsnotify_open(file); ++ } ++ return ret; ++} ++ + struct file *dentry_open(const struct path *path, int flags, + const struct cred *cred) + { diff --git a/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch.sig b/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..c0764effd48a9125b75594eb0e3de2452f447798 GIT binary patch literal 594 zcmV-Y0gLcueDQG9{+nKJskxanc37wXsLj$rHXtX_VtUU}e7%?ts%NC-37!L)A<+ZIPF_R2G(-r?DW$9%0I9m<=gcy&*hrQs(tLf4m^C~x=vzYU2*M(}0tJJ=UUTtI=W_R{CUxsv z%|mMqbe@pIt8YRlg%_rUTcPoSp*Raa&5RJ9W_XvF^-fA-puw0O*YrHanxXAM#zvvPA z4AwNFoLw3~W0}JL&KU2>Y*qZJK|ER`)`Y&E1u9$?EC4$w7%x?CFI`uGnaL=yR_aSi z+Gp1e5o9CFZ`{h97)7<^mt+k<2KWPCO3UFTLj~IWTANr0(C`O;Rck3xAs#<(5kry8 zVy)H4P|v{{qT2IbPJe)k+7pIaZrEN>B(kO-H9{Oe(=U%`20t8*Pb0Eq%8>Q*e-IaA gmonitor_range.min_vfreq, info->monitor_range.max_vfreq); + } + ++#define DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE 6 ++ ++static bool drm_update_displayid_adaptive_sync_range(struct drm_connector *connector, ++ const struct displayid_block *block) ++{ ++ struct drm_monitor_range_info *range = &connector->display_info.monitor_range; ++ const u8 *data = (const u8 *)(block + 1); ++ u16 best_min = 0, best_max = 0; ++ unsigned int best_span = 0; ++ int i; ++ ++ if (block->rev != 0 || block->num_bytes < DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE || ++ block->num_bytes % DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE) ++ return false; ++ ++ for (i = 0; i < block->num_bytes; i += DISPLAYID_ADAPTIVE_SYNC_DESC_SIZE) { ++ const u8 *desc = &data[i]; ++ u16 min_vfreq = desc[2]; ++ u16 max_vfreq = ((((u16)desc[4]) & 0x3) << 8) | desc[3]; ++ unsigned int span; ++ ++ max_vfreq += 1; ++ if (!min_vfreq || max_vfreq <= min_vfreq) ++ continue; ++ ++ span = max_vfreq - min_vfreq; ++ if (span <= best_span) ++ continue; ++ ++ best_min = min_vfreq; ++ best_max = max_vfreq; ++ best_span = span; ++ } ++ ++ if (best_span) { ++ range->min_vfreq = best_min; ++ range->max_vfreq = best_max; ++ return true; ++ } ++ ++ return false; ++} ++ + static void drm_parse_vesa_mso_data(struct drm_connector *connector, + const struct displayid_block *block) + { +@@ -6720,26 +6763,43 @@ static void update_displayid_info(struct drm_connector *connector, + { + struct drm_display_info *info = &connector->display_info; + const struct displayid_block *block; ++ bool adaptive_sync_range = false; ++ bool displayid_base_logged = false; + struct displayid_iter iter; + + displayid_iter_edid_begin(drm_edid, &iter); + displayid_iter_for_each(block, &iter) { +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] DisplayID extension version 0x%02x, primary use 0x%02x\n", +- connector->base.id, connector->name, +- displayid_version(&iter), +- displayid_primary_use(&iter)); +- if (displayid_version(&iter) == DISPLAY_ID_STRUCTURE_VER_20 && +- (displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_VR || +- displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_AR)) +- info->non_desktop = true; +- + /* +- * We're only interested in the base section here, no need to +- * iterate further. ++ * Primary use is a DisplayID base section property, but later ++ * blocks may still carry useful metadata like adaptive sync ranges. + */ +- break; ++ if (!displayid_base_logged) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DisplayID extension version 0x%02x, primary use 0x%02x\n", ++ connector->base.id, connector->name, ++ displayid_version(&iter), ++ displayid_primary_use(&iter)); ++ if (displayid_version(&iter) == DISPLAY_ID_STRUCTURE_VER_20 && ++ (displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_VR || ++ displayid_primary_use(&iter) == PRIMARY_USE_HEAD_MOUNTED_AR)) ++ info->non_desktop = true; ++ ++ displayid_base_logged = true; ++ } ++ ++ if (!info->monitor_range.min_vfreq && !info->monitor_range.max_vfreq && ++ block->tag == DATA_BLOCK_2_ADAPTIVE_SYNC) ++ adaptive_sync_range = ++ drm_update_displayid_adaptive_sync_range(connector, block); + } ++ ++ if (adaptive_sync_range) ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DisplayID adaptive sync refresh rate range is %d Hz - %d Hz\n", ++ connector->base.id, connector->name, ++ info->monitor_range.min_vfreq, ++ info->monitor_range.max_vfreq); ++ + displayid_iter_end(&iter); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig b/pkgbuilds/linux-omarchy-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f46a1a472991a4580b9e007111035758315c47fa GIT binary patch literal 594 zcmV-Y0d!bl^Isp zE(_-)9#fmedC)9#1OSP~8Q25NqiUZPc3oy1aeY#EPYTQiJ7c%Jw5|v?iL`H)O)8!` zbP#=z5Nb+7h>(rj-qzdDkB_f+J=3YUMZ&4Q%}Q`0Uf(f5NmnN_3Z#n5wXi(-lOK#? z>3l(c-GzSGm9wx(Ws8!-Bv`h{D1(Ovq06)9Pcduw&x5+q4ZCJt=ZOyf_jxVt*_AX= z`>-saIWxvofXJi9IiP)G1Zarup?dHea zV=*}&V!~>Ieve~#GdC^^okx7O(Mly3func = &nouveau_vram_manager; + ++ man->cg = drmm_cgroup_register_region(drm->dev, "vram", ++ drm->gem.vram_available); ++ if (IS_ERR(man->cg)) ++ return PTR_ERR(man->cg); ++ + ttm_resource_manager_init(man, &drm->ttm.bdev, + drm->gem.vram_available >> PAGE_SHIFT); + ttm_set_driver_manager(&drm->ttm.bdev, TTM_PL_VRAM, man); +diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c +--- a/drivers/gpu/drm/ttm/ttm_bo.c ++++ b/drivers/gpu/drm/ttm/ttm_bo.c +@@ -488,6 +488,118 @@ int ttm_bo_evict_first(struct ttm_device *bdev, struct ttm_resource_manager *man + return ret; + } + ++struct ttm_bo_alloc_state { ++ /** @charge_pool: The memory pool the resource is charged to */ ++ struct dmem_cgroup_pool_state *charge_pool; ++ /** @limit_pool: Which pool limit we should test against */ ++ struct dmem_cgroup_pool_state *limit_pool; ++ /** @in_evict: Whether we are currently evicting buffers */ ++ bool in_evict; ++ /** @may_try_low: If only unprotected BOs, i.e. BOs whose cgroup ++ * is exceeding its dmem low/min protection, should be considered for eviction ++ */ ++ bool may_try_low; ++}; ++ ++/** ++ * ttm_bo_alloc_at_place - Attempt allocating a BO's backing store in a place ++ * ++ * @bo: The buffer to allocate the backing store of ++ * @place: The place to attempt allocation in ++ * @ctx: ttm_operation_ctx associated with this allocation ++ * @force_space: If we should evict buffers to force space ++ * @res: On allocation success, the resulting struct ttm_resource. ++ * @alloc_state: Object holding allocation state such as charged cgroups. ++ * ++ * Returns: ++ * -EBUSY: No space available, but allocation should be retried with ttm_bo_evict_alloc. ++ * -ENOSPC: No space available, allocation should not be retried. ++ * -ERESTARTSYS: An interruptible sleep was interrupted by a signal. ++ * ++ */ ++static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ bool force_space, ++ struct ttm_resource **res, ++ struct ttm_bo_alloc_state *alloc_state) ++{ ++ bool may_evict; ++ int ret; ++ ++ may_evict = !alloc_state->in_evict && force_space && ++ place->mem_type != TTM_PL_SYSTEM; ++ if (!alloc_state->charge_pool) { ++ ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, ++ force_space ? &alloc_state->limit_pool ++ : NULL); ++ if (ret) { ++ /* ++ * -EAGAIN means the charge failed, which we treat ++ * like an allocation failure. Therefore, return an ++ * error code indicating the allocation failed - ++ * either -EBUSY if the allocation should be ++ * retried with eviction, or -ENOSPC if there should ++ * be no second attempt. ++ */ ++ if (!alloc_state->in_evict) ++ alloc_state->may_try_low = may_evict; ++ if (ret == -EAGAIN) ++ ret = may_evict ? -EBUSY : -ENOSPC; ++ return ret; ++ } ++ } ++ ++ /* ++ * cgroup protection plays a special role in eviction. ++ * Conceptually, protection of memory via the dmem cgroup controller ++ * entitles the protected cgroup to use a certain amount of memory. ++ * There are two types of protection - the 'low' limit is a ++ * "best-effort" protection, whereas the 'min' limit provides a hard ++ * guarantee that memory within the cgroup's allowance will not be ++ * evicted under any circumstance. ++ * ++ * To faithfully model this concept in TTM, we also need to take cgroup ++ * protection into account when allocating. When allocation in one ++ * place fails, TTM will default to trying other places first before ++ * evicting. ++ * If the allocation is covered by dmem cgroup protection, however, ++ * this prevents the allocation from using the memory it is "entitled" ++ * to. To make sure unprotected allocations cannot push new protected ++ * allocations out of places they are "entitled" to use, we should ++ * evict buffers not covered by any cgroup protection, if this ++ * allocation is covered by cgroup protection. ++ * ++ * Buffers covered by 'min' protection are a special case - the 'min' ++ * limit is a stronger guarantee than 'low', and thus buffers protected ++ * by 'low' but not 'min' should also be considered for eviction. ++ * Buffers protected by 'min' will never be considered for eviction ++ * anyway, so the regular eviction path should be triggered here. ++ * Buffers protected by 'low' but not 'min' will take a special ++ * eviction path that only evicts buffers covered by neither 'low' or ++ * 'min' protections. ++ */ ++ if (!alloc_state->in_evict) { ++ may_evict |= dmem_cgroup_below_min(NULL, alloc_state->charge_pool); ++ alloc_state->may_try_low = may_evict; ++ ++ may_evict |= dmem_cgroup_below_low(NULL, alloc_state->charge_pool); ++ } ++ ++ ret = ttm_resource_alloc(bo, place, res, alloc_state->charge_pool); ++ if (ret) { ++ if (ret == -ENOSPC && may_evict) ++ ret = -EBUSY; ++ return ret; ++ } ++ ++ /* ++ * Ownership of charge_pool has been transferred to the TTM resource, ++ * don't make the caller think we still hold a reference to it. ++ */ ++ alloc_state->charge_pool = NULL; ++ return 0; ++} ++ + /** + * struct ttm_bo_evict_walk - Parameters for the evict walk. + */ +@@ -503,22 +615,61 @@ struct ttm_bo_evict_walk { + /** @evicted: Number of successful evictions. */ + unsigned long evicted; + +- /** @limit_pool: Which pool limit we should test against */ +- struct dmem_cgroup_pool_state *limit_pool; + /** @try_low: Whether we should attempt to evict BO's with low watermark threshold */ + bool try_low; + /** @hit_low: If we cannot evict a bo when @try_low is false (first pass) */ + bool hit_low; ++ ++ /** @alloc_state: State associated with the allocation attempt. */ ++ struct ttm_bo_alloc_state *alloc_state; + }; + + static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) + { + struct ttm_bo_evict_walk *evict_walk = + container_of(walk, typeof(*evict_walk), walk); ++ struct dmem_cgroup_pool_state *limit_pool, *ancestor = NULL; ++ bool evict_valuable; + s64 lret; + +- if (!dmem_cgroup_state_evict_valuable(evict_walk->limit_pool, bo->resource->css, +- evict_walk->try_low, &evict_walk->hit_low)) ++ /* ++ * If may_try_low is not set, then we're trying to evict unprotected ++ * buffers in favor of a protected allocation for charge_pool. Explicitly skip ++ * buffers belonging to the same cgroup here - that cgroup is definitely protected, ++ * even though dmem_cgroup_state_evict_valuable would allow the eviction because a ++ * cgroup is always allowed to evict from itself even if it is protected. ++ */ ++ if (!evict_walk->alloc_state->may_try_low && ++ bo->resource->css == evict_walk->alloc_state->charge_pool) ++ return 0; ++ ++ limit_pool = evict_walk->alloc_state->limit_pool; ++ /* ++ * If there is no explicit limit pool, find the root of the shared subtree between ++ * evictor and evictee. This is important so that recursive protection rules can ++ * apply properly: Recursive protection distributes cgroup protection afforded ++ * to a parent cgroup but not used explicitly by a child cgroup between all child ++ * cgroups (see docs of effective_protection in mm/page_counter.c). However, when ++ * direct siblings compete for memory, siblings that were explicitly protected ++ * should get prioritized over siblings that weren't. This only happens correctly ++ * when the root of the shared subtree is passed to ++ * dmem_cgroup_state_evict_valuable. Otherwise, the effective-protection ++ * calculation cannot distinguish direct siblings from unrelated subtrees and the ++ * calculated protection ends up wrong. ++ */ ++ if (!limit_pool) { ++ ancestor = dmem_cgroup_get_common_ancestor(bo->resource->css, ++ evict_walk->alloc_state->charge_pool); ++ limit_pool = ancestor; ++ } ++ ++ evict_valuable = dmem_cgroup_state_evict_valuable(limit_pool, bo->resource->css, ++ evict_walk->try_low, ++ &evict_walk->hit_low); ++ if (ancestor) ++ dmem_cgroup_pool_state_put(ancestor); ++ ++ if (!evict_valuable) + return 0; + + if (bo->pin_count || !bo->bdev->funcs->eviction_valuable(bo, evict_walk->place)) +@@ -537,8 +688,10 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * + + evict_walk->evicted++; + if (evict_walk->res) +- lret = ttm_resource_alloc(evict_walk->evictor, evict_walk->place, +- evict_walk->res, NULL); ++ lret = ttm_bo_alloc_at_place(evict_walk->evictor, ++ evict_walk->place, false, ++ evict_walk->res, ++ evict_walk->alloc_state); + if (lret == 0) + return 1; + out: +@@ -560,7 +713,7 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + struct ttm_operation_ctx *ctx, + struct ww_acquire_ctx *ticket, + struct ttm_resource **res, +- struct dmem_cgroup_pool_state *limit_pool) ++ struct ttm_bo_alloc_state *state) + { + struct ttm_bo_evict_walk evict_walk = { + .walk = { +@@ -573,15 +726,21 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + .place = place, + .evictor = evictor, + .res = res, +- .limit_pool = limit_pool, ++ .alloc_state = state, + }; + s64 lret; + ++ state->in_evict = true; ++ + evict_walk.walk.arg.trylock_only = true; + lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); + +- /* One more attempt if we hit low limit? */ +- if (!lret && evict_walk.hit_low) { ++ /* If we failed to find enough BOs to evict, but we skipped over ++ * some BOs because they were covered by dmem low protection, retry ++ * evicting these protected BOs too, except if we're told not to ++ * consider protected BOs at all. ++ */ ++ if (!lret && evict_walk.hit_low && state->may_try_low) { + evict_walk.try_low = true; + lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); + } +@@ -602,11 +761,13 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, + } while (!lret && evict_walk.evicted); + + /* We hit the low limit? Try once more */ +- if (!lret && evict_walk.hit_low && !evict_walk.try_low) { ++ if (!lret && evict_walk.hit_low && !evict_walk.try_low && ++ state->may_try_low) { + evict_walk.try_low = true; + goto retry; + } + out: ++ state->in_evict = false; + if (lret < 0) + return lret; + if (lret == 0) +@@ -724,9 +885,8 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, + + for (i = 0; i < placement->num_placement; ++i) { + const struct ttm_place *place = &placement->placement[i]; +- struct dmem_cgroup_pool_state *limit_pool = NULL; ++ struct ttm_bo_alloc_state alloc_state = {}; + struct ttm_resource_manager *man; +- bool may_evict; + + man = ttm_manager_type(bdev, place->mem_type); + if (!man || !ttm_resource_manager_used(man)) +@@ -736,25 +896,30 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, + TTM_PL_FLAG_FALLBACK)) + continue; + +- may_evict = (force_space && place->mem_type != TTM_PL_SYSTEM); +- ret = ttm_resource_alloc(bo, place, res, force_space ? &limit_pool : NULL); +- if (ret) { +- if (ret != -ENOSPC) { +- dmem_cgroup_pool_state_put(limit_pool); +- return ret; +- } +- if (!may_evict) { +- dmem_cgroup_pool_state_put(limit_pool); +- continue; +- } ++ ret = ttm_bo_alloc_at_place(bo, place, force_space, res, ++ &alloc_state); + ++ if (ret == -ENOSPC) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size); ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ continue; ++ } else if (ret == -EBUSY) { + ret = ttm_bo_evict_alloc(bdev, man, place, bo, ctx, +- ticket, res, limit_pool); +- dmem_cgroup_pool_state_put(limit_pool); +- if (ret == -EBUSY) +- continue; +- if (ret) ++ ticket, res, &alloc_state); ++ ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ ++ if (ret) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, ++ bo->base.size); ++ if (ret == -EBUSY) ++ continue; + return ret; ++ } ++ } else if (ret) { ++ dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size); ++ dmem_cgroup_pool_state_put(alloc_state.limit_pool); ++ return ret; + } + + ret = ttm_bo_add_pipelined_eviction_fences(bo, man, ctx->no_wait_gpu); +diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c +--- a/drivers/gpu/drm/ttm/ttm_resource.c ++++ b/drivers/gpu/drm/ttm/ttm_resource.c +@@ -386,33 +386,52 @@ void ttm_resource_fini(struct ttm_resource_manager *man, + } + EXPORT_SYMBOL(ttm_resource_fini); + ++/** ++ * ttm_resource_try_charge - charge a resource manager's cgroup pool ++ * @bo: buffer for which an allocation should be charged ++ * @place: where the allocation is attempted to be placed ++ * @ret_pool: on charge success, the pool that was charged ++ * @ret_limit_pool: on charge failure, the pool responsible for the failure ++ * ++ * Should be used to charge cgroups before attempting resource allocation. ++ * When charging succeeds, the value of ret_pool should be passed to ++ * ttm_resource_alloc. ++ * ++ * Returns: 0 on charge success, negative errno on failure. ++ */ ++int ttm_resource_try_charge(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ struct dmem_cgroup_pool_state **ret_pool, ++ struct dmem_cgroup_pool_state **ret_limit_pool) ++{ ++ struct ttm_resource_manager *man = ++ ttm_manager_type(bo->bdev, place->mem_type); ++ ++ if (!man->cg) { ++ *ret_pool = NULL; ++ if (ret_limit_pool) ++ *ret_limit_pool = NULL; ++ return 0; ++ } ++ ++ return dmem_cgroup_try_charge(man->cg, bo->base.size, ret_pool, ++ ret_limit_pool); ++} ++ + int ttm_resource_alloc(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct ttm_resource **res_ptr, +- struct dmem_cgroup_pool_state **ret_limit_pool) ++ struct dmem_cgroup_pool_state *charge_pool) + { + struct ttm_resource_manager *man = + ttm_manager_type(bo->bdev, place->mem_type); +- struct dmem_cgroup_pool_state *pool = NULL; + int ret; + +- if (man->cg) { +- ret = dmem_cgroup_try_charge(man->cg, bo->base.size, &pool, ret_limit_pool); +- if (ret) { +- if (ret == -EAGAIN) +- ret = -ENOSPC; +- return ret; +- } +- } +- + ret = man->func->alloc(man, bo, place, res_ptr); +- if (ret) { +- if (pool) +- dmem_cgroup_uncharge(pool, bo->base.size); ++ if (ret) + return ret; +- } + +- (*res_ptr)->css = pool; ++ (*res_ptr)->css = charge_pool; + + spin_lock(&bo->bdev->lru_lock); + ttm_resource_add_bulk_move(*res_ptr, bo); +diff --git a/include/drm/ttm/ttm_resource.h b/include/drm/ttm/ttm_resource.h +--- a/include/drm/ttm/ttm_resource.h ++++ b/include/drm/ttm/ttm_resource.h +@@ -458,10 +458,14 @@ void ttm_resource_init(struct ttm_buffer_object *bo, + void ttm_resource_fini(struct ttm_resource_manager *man, + struct ttm_resource *res); + ++int ttm_resource_try_charge(struct ttm_buffer_object *bo, ++ const struct ttm_place *place, ++ struct dmem_cgroup_pool_state **ret_pool, ++ struct dmem_cgroup_pool_state **ret_limit_pool); + int ttm_resource_alloc(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct ttm_resource **res, +- struct dmem_cgroup_pool_state **ret_limit_pool); ++ struct dmem_cgroup_pool_state *charge_pool); + void ttm_resource_free(struct ttm_buffer_object *bo, struct ttm_resource **res); + bool ttm_resource_intersects(struct ttm_device *bdev, + struct ttm_resource *res, +diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h +--- a/include/linux/cgroup.h ++++ b/include/linux/cgroup.h +@@ -623,6 +623,27 @@ static inline struct cgroup *cgroup_ancestor(struct cgroup *cgrp, + return cgrp->ancestors[ancestor_level]; + } + ++/** ++ * cgroup_common_ancestor - find common ancestor of two cgroups ++ * @a: first cgroup to find common ancestor of ++ * @b: second cgroup to find common ancestor of ++ * ++ * Find the first cgroup that is an ancestor of both @a and @b, if it exists ++ * and return a pointer to it. If such a cgroup doesn't exist, return NULL. ++ * ++ * This function is safe to call as long as both @a and @b are accessible. ++ */ ++static inline struct cgroup *cgroup_common_ancestor(struct cgroup *a, ++ struct cgroup *b) ++{ ++ int level; ++ ++ for (level = min(a->level, b->level); level >= 0; level--) ++ if (a->ancestors[level] == b->ancestors[level]) ++ return a->ancestors[level]; ++ return NULL; ++} ++ + /** + * task_under_cgroup_hierarchy - test task's membership of cgroup ancestry + * @task: the task to be tested +diff --git a/include/linux/cgroup_dmem.h b/include/linux/cgroup_dmem.h +--- a/include/linux/cgroup_dmem.h ++++ b/include/linux/cgroup_dmem.h +@@ -24,6 +24,12 @@ void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size); + bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, + struct dmem_cgroup_pool_state *test_pool, + bool ignore_low, bool *ret_hit_low); ++bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test); ++bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test); ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b); + + void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool); + #else +@@ -59,6 +65,25 @@ bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, + return true; + } + ++static inline bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ return false; ++} ++ ++static inline bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ return false; ++} ++ ++static inline ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b) ++{ ++ return NULL; ++} ++ + static inline void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool) + { } + +diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c +--- a/kernel/cgroup/dmem.c ++++ b/kernel/cgroup/dmem.c +@@ -695,6 +695,110 @@ int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, + } + EXPORT_SYMBOL_GPL(dmem_cgroup_try_charge); + ++/** ++ * dmem_cgroup_below_min() - Tests whether current usage is within min limit. ++ * ++ * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection. ++ * @test: The pool to test the usage/min limit of. ++ * ++ * Return: true if usage is below min and the cgroup is protected, false otherwise. ++ */ ++bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ if (root == test || !pool_parent(test)) ++ return false; ++ ++ if (!root) { ++ for (root = test; pool_parent(root); root = pool_parent(root)) ++ {} ++ } ++ ++ /* ++ * In mem_cgroup_below_min(), the memcg pendant, this call is missing. ++ * mem_cgroup_below_min() gets called during traversal of the cgroup tree, where ++ * protection is already calculated as part of the traversal. dmem cgroup eviction ++ * does not traverse the cgroup tree, so we need to recalculate effective protection ++ * here. ++ */ ++ dmem_cgroup_calculate_protection(root, test); ++ return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.emin); ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_below_min); ++ ++/** ++ * dmem_cgroup_below_low() - Tests whether current usage is within low limit. ++ * ++ * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection. ++ * @test: The pool to test the usage/low limit of. ++ * ++ * Return: true if usage is below low and the cgroup is protected, false otherwise. ++ */ ++bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, ++ struct dmem_cgroup_pool_state *test) ++{ ++ if (root == test || !pool_parent(test)) ++ return false; ++ ++ if (!root) { ++ for (root = test; pool_parent(root); root = pool_parent(root)) ++ {} ++ } ++ ++ /* ++ * In mem_cgroup_below_low(), the memcg pendant, this call is missing. ++ * mem_cgroup_below_low() gets called during traversal of the cgroup tree, where ++ * protection is already calculated as part of the traversal. dmem cgroup eviction ++ * does not traverse the cgroup tree, so we need to recalculate effective protection ++ * here. ++ */ ++ dmem_cgroup_calculate_protection(root, test); ++ return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.elow); ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_below_low); ++ ++/** ++ * dmem_cgroup_get_common_ancestor(): Find the first common ancestor of two pools. ++ * @a: First pool to find the common ancestor of. ++ * @b: First pool to find the common ancestor of. ++ * ++ * Return: The first pool that is a parent of both @a and @b, or NULL if either @a or @b are NULL, ++ * or if such a pool does not exist. A reference to the returned pool is grabbed and must be ++ * released by the caller when it is done using the pool. ++ */ ++struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, ++ struct dmem_cgroup_pool_state *b) ++{ ++ struct cgroup *ancestor_cgroup; ++ struct cgroup_subsys_state *ancestor_css; ++ struct dmemcg_state *ancestor_dmemcs = NULL; ++ struct dmem_cgroup_pool_state *pool = NULL; ++ ++ if (!a || !b) ++ return NULL; ++ ++ ancestor_cgroup = cgroup_common_ancestor(a->cs->css.cgroup, b->cs->css.cgroup); ++ if (!ancestor_cgroup) ++ return NULL; ++ ++ rcu_read_lock(); ++ ancestor_css = cgroup_e_css(ancestor_cgroup, &dmem_cgrp_subsys); ++ if (css_tryget(ancestor_css)) ++ ancestor_dmemcs = css_to_dmemcs(ancestor_css); ++ rcu_read_unlock(); ++ ++ if (ancestor_dmemcs) { ++ pool = get_cg_pool_unlocked(css_to_dmemcs(ancestor_css), ++ a->region); ++ if (WARN_ON(IS_ERR(pool))) { ++ pool = NULL; ++ css_put(ancestor_css); ++ } ++ } ++ return pool; ++} ++EXPORT_SYMBOL_GPL(dmem_cgroup_get_common_ancestor); ++ + static int dmem_cgroup_region_capacity_show(struct seq_file *sf, void *v) + { + struct dmem_cgroup_region *region; diff --git a/pkgbuilds/linux-omarchy-muqss/0360-gpu-mem-cgroup.patch.sig b/pkgbuilds/linux-omarchy-muqss/0360-gpu-mem-cgroup.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..ddced6d86fc2828e241cfbf102ff03e0b91eee16 GIT binary patch literal 594 zcmV-Y0LK00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=S#H@|8a35vt(ht*h16(oYf|K ztHixZ(fP?1hSoXQd=msb;j_~*ZI+VMROPl~oeR%J<*}qE7@C1~kJGifzgx;FCARVVVmDY+2oiA8yYg7t#K~%zrA6*xGtuHY z_B0lTw4&V2YyM#%EGvSH=Hl?mMNYLhC}^# zUSEXE@>ovTlXnDBD1oBQtBM!>NA$e@!tLa>f$Q z9mSy}ds$Dv8q|0R`(W#NmtbYi?`ilY@}%OoRoiU8O~Pn>^p>#lL{Jd1BSkag z#xCp9as5FRO3FQv>44XYhA4n@Gp0F7?@92@7vKK#=A&-diuq6=7FXvwBX|{}(Jg3! gMt{=wZ5W)_G=Xi{JE%jvVV+SV3VD=(AjDp9CGY7Z%m4rY literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch b/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch new file mode 100644 index 0000000..32f3010 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_alpm.c b/drivers/gpu/drm/i915/display/intel_alpm.c +index c6963ea420cc..c38d613f15d6 100644 +--- a/drivers/gpu/drm/i915/display/intel_alpm.c ++++ b/drivers/gpu/drm/i915/display/intel_alpm.c +@@ -399,7 +399,7 @@ static void lnl_alpm_configure(struct intel_dp *intel_dp, + ALPM_CTL_AUX_LESS_SLEEP_HOLD_TIME_50_SYMBOLS | + ALPM_CTL_AUX_LESS_WAKE_TIME(crtc_state->alpm_state.aux_less_wake_lines); + +- if (intel_dp->as_sdp_supported) { ++ if (intel_dp->as_sdp_supported && crtc_state->has_panel_replay) { + u32 pr_alpm_ctl = get_pr_alpm_as_sdp_transmission_time(crtc_state); + + if (crtc_state->link_off_after_as_sdp_when_pr_active) diff --git a/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig b/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..b591f47302cf6fbccf98ad9aaef2bf090dc3c1e8 GIT binary patch literal 594 zcmV-Y02(vh8XX1p*n$PtMEJ5dOZMUWHmx`bvKDS*Kb>KY{||XDCFHNCmDd_PFOIQ z4(UU#JQFONG^OSm-2#MEDL zkEsD55zq+PI8vsw7;+>XAokET>hkxtAM`l=AOlz;Ku9kQ3H*d^Z9*bW-@aJ;I$`Ii zjT94q#2*jqz#q$>bGcCx$*BRiISu?sUbbAQP&T4g_w)`&j>wjZ``hSglJ>NT?`ha- z_a6n(&iOPMQr;HWJ~ZajpaZGzR`$3pH6i@9HdLdTTh=zy3B~=klXNa1x&;LgST^V} zl77YB)7yvMmb_6ojVuwt_1I0ScG$d{@86bND>UaqCE?8Q&{#BamJgZzqe-{j;t^Kd zL7pQ1oF4D2IVZX>Qn?6-hFA87m6#Hnx0MKcz;H;BY0gg=zRtOKthA<|j+|^3JA$;B zz;J$S2wMYmYaCsvv@8k8E`>eAFMQ_CP=`_gbC3ZbXPjz=|D{w(4arZ- z?nhKt1SM=RsMd3SkOR9GMJ2$R=K7U{?xNrus_q){g{wEHoOALxdp.psr_caps.su_support = y_req && ++ connector->dp.psr_caps.su_support = ++ (y_req || connector->dp.psr_caps.dpcd[0] == ++ DP_PSR2_WITH_Y_COORD_ET_SUPPORTED) && + intel_alpm_aux_wake_supported(intel_dp); + drm_dbg_kms(display->drm, "PSR2 %ssupported\n", + connector->dp.psr_caps.su_support ? "" : "not "); diff --git a/pkgbuilds/linux-omarchy-muqss/0401-psr2-early-transport-panels.patch.sig b/pkgbuilds/linux-omarchy-muqss/0401-psr2-early-transport-panels.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..1b2c03944de353798157447aa165f62787a9d18b GIT binary patch literal 594 zcmV-Y03i}EwRr&!^@N}*BnMP zWV*QfJ93-Dz^vA$^egv;0Odu>@?EEZg!ck0TSwU3dmetfrk#GkYw-@1s-GCbOz(q| zQH}LcyEqLx44tT#prH=GI#{|n6a_TUhzv>Bu1nc}s7bc^xw_>$;Q-}oD0**y5n0)_ z05)Iun%&)p?MJ;g)?t2O7?g}%kJXg|i-pEcQOQk?xPI&R+I@-t4JzEJ=iSn+{z5B8 zj?z=n2@CY{sC}GI8+NWM7&>-mv^U&MPsqLD5M)9RTTMtTIkUJyV-CFiL_8BGu7!K|Poy77*Wh;JFZI`B+ z&;4No`XOCAc#5fF?bFY+gb=<>!Vs#HE``E*fXJyaCe7WI8ET>|WJxkkM2EayaPc?g1Kd+40#J zAYB)a-P}~tNW$~{N8+l`@+DYwT;?C#BCg{G@|#-?SvjrJr^edqf`Xyrzu<}a=!_Zg gt=~h5Cpsr.lock); + cancel_work_sync(&intel_dp->psr.work); + cancel_delayed_work_sync(&intel_dp->psr.dc3co_work); ++ cancel_delayed_work_sync(&intel_dp->psr.panel_replay_reenable_work); + } + + /** +@@ -2512,6 +2513,7 @@ void intel_psr_pause(struct intel_dp *intel_dp) + + cancel_work_sync(&psr->work); + cancel_delayed_work_sync(&psr->dc3co_work); ++ cancel_delayed_work_sync(&psr->panel_replay_reenable_work); + } + + /** +@@ -3545,6 +3547,17 @@ static void intel_psr_handle_irq(struct intel_dp *intel_dp) + drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + } + ++#define PANEL_REPLAY_REENABLE_DELAY_MS 50 ++ ++static bool panel_replay_exit_on_flush_enabled(struct intel_dp *intel_dp) ++{ ++ return intel_dp_is_edp(intel_dp) && ++ intel_dp->psr.panel_replay_enabled && ++ intel_dp->psr.sel_update_enabled && ++ intel_has_dpcd_quirk(intel_dp, ++ QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH); ++} ++ + static void intel_psr_work(struct work_struct *work) + { + struct intel_dp *intel_dp = +@@ -3563,6 +3576,11 @@ static void intel_psr_work(struct work_struct *work) + if (intel_dp->psr.pause_counter) + goto unlock; + ++ /* The dedicated delayed work owns re-entry while the quirk is armed. */ ++ if (panel_replay_exit_on_flush_enabled(intel_dp) && ++ delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) ++ goto unlock; ++ + /* + * We have to make sure PSR is ready for re-enable + * otherwise it keeps disabled until next full enable/disable cycle. +@@ -3572,6 +3590,10 @@ static void intel_psr_work(struct work_struct *work) + if (!__psr_wait_for_idle_locked(intel_dp)) + goto unlock; + ++ if (panel_replay_exit_on_flush_enabled(intel_dp) && ++ delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) ++ goto unlock; ++ + /* + * The delayed work can race with an invalidate hence we need to + * recheck. Since psr_flush first clears this and then reschedules we +@@ -3585,6 +3607,33 @@ static void intel_psr_work(struct work_struct *work) + mutex_unlock(&intel_dp->psr.lock); + } + ++static void panel_replay_reenable_work(struct work_struct *work) ++{ ++ struct intel_dp *intel_dp = ++ container_of(work, typeof(*intel_dp), ++ psr.panel_replay_reenable_work.work); ++ ++ mutex_lock(&intel_dp->psr.lock); ++ ++ if (!intel_dp->psr.enabled || ++ !panel_replay_exit_on_flush_enabled(intel_dp) || ++ intel_dp->psr.pause_counter || ++ READ_ONCE(intel_dp->psr.irq_aux_error)) ++ goto unlock; ++ ++ if (!__psr_wait_for_idle_locked(intel_dp)) ++ goto unlock; ++ ++ /* Recheck for activity or a newer deadline after the unlocked wait. */ ++ if (delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work) || ++ intel_dp->psr.busy_frontbuffer_bits || intel_dp->psr.active) ++ goto unlock; ++ ++ intel_psr_activate(intel_dp); ++unlock: ++ mutex_unlock(&intel_dp->psr.lock); ++} ++ + static void intel_psr_configure_full_frame_update(struct intel_dp *intel_dp) + { + struct intel_display *display = to_intel_display(intel_dp); +@@ -3656,8 +3705,11 @@ void intel_psr_invalidate(struct intel_display *display, + INTEL_FRONTBUFFER_ALL_MASK(intel_dp->psr.pipe); + intel_dp->psr.busy_frontbuffer_bits |= pipe_frontbuffer_bits; + +- if (pipe_frontbuffer_bits) ++ if (pipe_frontbuffer_bits) { ++ if (panel_replay_exit_on_flush_enabled(intel_dp)) ++ cancel_delayed_work(&intel_dp->psr.panel_replay_reenable_work); + _psr_invalidate_handle(intel_dp); ++ } + + mutex_unlock(&intel_dp->psr.lock); + } +@@ -3773,6 +3825,15 @@ void intel_psr_flush(struct intel_display *display, + if (intel_dp->psr.pause_counter) + goto unlock; + ++ if (pipe_frontbuffer_bits && ++ panel_replay_exit_on_flush_enabled(intel_dp)) { ++ intel_psr_exit(intel_dp); ++ mod_delayed_work(display->wq.unordered, ++ &intel_dp->psr.panel_replay_reenable_work, ++ msecs_to_jiffies(PANEL_REPLAY_REENABLE_DELAY_MS)); ++ goto unlock; ++ } ++ + if (origin == ORIGIN_FLIP || + (origin == ORIGIN_CURSOR_UPDATE && + !intel_dp->psr.psr2_sel_fetch_enabled)) { +@@ -3836,6 +3897,8 @@ void intel_psr_init(struct intel_dp *intel_dp) + + INIT_WORK(&intel_dp->psr.work, intel_psr_work); + INIT_DELAYED_WORK(&intel_dp->psr.dc3co_work, tgl_dc3co_disable_work); ++ INIT_DELAYED_WORK(&intel_dp->psr.panel_replay_reenable_work, ++ panel_replay_reenable_work); + mutex_init(&intel_dp->psr.lock); + } + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -94,6 +94,14 @@ static void quirk_disable_edp_panel_replay(struct intel_dp *intel_dp) + drm_info(display->drm, "Applying disable Panel Replay quirk\n"); + } + ++static void quirk_panel_replay_exit_on_flush(struct intel_dp *intel_dp) ++{ ++ struct intel_display *display = to_intel_display(intel_dp); ++ ++ intel_set_dpcd_quirk(intel_dp, QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH); ++ drm_info(display->drm, "Applying Panel Replay exit on flush quirk\n"); ++} ++ + static void quirk_disable_psr2(struct intel_display *display) + { + intel_set_quirk(display, QUIRK_DISABLE_PSR2); +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h b/drivers/gpu/drm/i915/display/intel_quirks.h +--- a/drivers/gpu/drm/i915/display/intel_quirks.h ++++ b/drivers/gpu/drm/i915/display/intel_quirks.h +@@ -23,6 +23,7 @@ enum intel_quirk_id { + QUIRK_EDP_LIMIT_RATE_HBR2, + QUIRK_DISABLE_EDP_PANEL_REPLAY, + QUIRK_DISABLE_PSR2, ++ QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH, + }; + + void intel_init_quirks(struct intel_display *display); diff --git a/pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..196c3ee10a1e83535d48bfb4f23794a991778b34 GIT binary patch literal 594 zcmV-Y0zMLT(pW^3oIY>fsz}Ah~DFeLgv(Q$$CqsxZ@w z6>v8k!N7j~^&G>%1O7eoLwzbO{wv1ea$VOBA)iaXCrHsmtn-%T#y0Na(yOqYg6DL_7k zKInL^t$cWpk+1!tW`R0vVhkx1bdzd>)7Rw!uN_}~FHuYU gf^{DEoB`rG!KpS-Zqe4b(?K6*cf^*s6x_zyRiqCMbpQYW literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..24c203e --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -284,7 +284,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0db9, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_exit_on_flush, + }, + /* Dell XPS 16 DA16260 */ + { diff --git a/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..0393b909a374910f63f34125ec387a72497db3b1 GIT binary patch literal 594 zcmV-Y0KX=JHSmKn!SP-JW08LI2iFxit3;dxe=jshA}DbMFzHeHmLL3Ms!zy86Y(-res zw=;G80)bs;^`TENGi4jzhnc?U#uyOxmx5GEg(ulhvPTQcDiKsQ*crM)zy72iZW!%D zVlfsS-pp^A#ZsbC#6!NhnV9=9I1V1Bit1EoQb+!vsvQ{cjCws`q@`9mKu;9NEsL8v zh6~K?%?T=)Lt>Hl20}dc;1-+jpgC)}k&e^*9H2EbL9xD@7p3bO22Q6+TR<2hhM$6d z|Lp2f6ygB27#W7u=)i zQQvE=M8zlJXPq^9OHQ<&O-#45whMziy;KY>_)yHUQg zFAo0i?CQsSe5C$u9*K9pr9(^H{62{$g2)yI;j_x}=L&`A=?fH_N~w&J|4XDHoq8_b gDV3j3i+({snrn;c06jxzK$4hLgGqTb$+SX4AL1PoYybcN literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..8eb9b65 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -292,7 +292,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0dba, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_exit_on_flush, + }, + }; + diff --git a/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..095745ca14275ee3b7232da98eee8f6e30574f3b GIT binary patch literal 594 zcmV-Y0Wb&R!x>^vqOSWZs+bM415=JL#oovHF1vt&{fz&E}LClD%_rN+EM9 zhE1rG>(PCtb0D?GO<@n2v;`cP7KyZ;=J35>LVW#FT4|LG+!HOX1u-~UkXd}(bQ-?w z!_p0+eWLOQg0QA2u?`*;?50H222+un{>Kz4GpOHOua#5P1s*HusUYx2``u)H0%ZTK zfo5Aeg8YjYl8Khig;5)BRHs=tMOswDj(|ZQvXlM+l3`+^8ebN_hN#;oCVGS(pqf|^ z{?ZyCm}UDF-sAkownOyo-k}YtK~?iBS+mRFPWaC3wLEa5y`@6udDLN1;4%_cr-Kck zi2WT*B-NQYXUCtawUN8AR{GeplvSSYCQy7A+z8n;zrEEXJ>F?H1+AJ>jDvIL3i1QC zgGe}B3eYr1W&F{}#)wSkDm?s}6_dK# zuy2<}(Xo0TE4|2Bi#?VK0yI6p=MGGMIG@w&55MvvO)sq|^&YEVsBLXY9(M6pfy=eHq9V g!iK?UXC>zr;AU$Wst=U^fChv;PrYDmU=PNZE#T7@hyVZp literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch b/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch new file mode 100644 index 0000000..1da7f71 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -267,6 +267,9 @@ static struct intel_quirk intel_quirks[] = { + + /* Xiaomi Book Pro 14 2026 */ + { 0xb081, 0x1d72, 0x2424, quirk_disable_psr2 }, ++ ++ /* Dell XPS 13 DX13260 */ ++ { DEVICE_ID_ANY, 0x1028, 0x0e54, quirk_disable_psr2 }, + }; + + static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { +@@ -294,6 +297,14 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), + .hook = quirk_panel_replay_exit_on_flush, + }, ++ /* Dell XPS 13 DX13260 */ ++ { ++ .device = DEVICE_ID_ANY, ++ .subsystem_vendor = 0x1028, ++ .subsystem_device = 0x0e54, ++ .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), ++ .hook = quirk_disable_edp_panel_replay, ++ }, + }; + + void intel_init_quirks(struct intel_display *display) diff --git a/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig b/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..16a1fb9608b66e9266f3c649728a70cc98495480 GIT binary patch literal 594 zcmV-Y0tK02Q_py_<9`z+j z*+qQEj<{K}sFE`0G4#~}ye0oveasJ=rjfP=++>!4YK#WRazC4%OMXmLd}^6|pJ(Tl z;wWd{=Pd-V08YTd-MrYS4H?j$(f23lL=@?BdQjkH)GjOWdp)<`Mp#lq)5WlC1Az(^ zGpl(>VC;5T4F{U6w=2FQ2!}eps^rV<(n+_R0F^88XEXAppz$)!71B=}VXg|)%`Qga z4Ka4F%EAV}88Z-vE_g&+pIDKTmL_GJo_LMN8(W0V{F+6EF;ElBi4f)fP{p~bPx(xD z7=z(2+~j4w|I9oLD#LJ}&y))96AbyZmVpFGdH)$Xh+ zdCig{FtifLd^TNK_D!=U^rvkgiq6f%<@F2Xq@3!mUgv|;BkTD$^M?aV%fRg0ZVLd0 z{Z-ajHRCpk0!Dc(AwHzc5R6Z@;@)zG5zj*IBjfva#eY2DYNciHwS1;l7%_p54GP=w(V6@wWh?8g-;{Y$WpT;7?vUh_07{&PI@>Vj{pDw literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..94d0d55 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch @@ -0,0 +1,19 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -305,6 +305,15 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), + .hook = quirk_disable_edp_panel_replay, + }, ++ /* Dell XPS 13 DX13260 (Wildcat Lake), sink "Bamboo" */ ++ { ++ .device = DEVICE_ID_ANY, ++ .subsystem_vendor = 0x1028, ++ .subsystem_device = 0x0e53, ++ .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), ++ .sink_device_id = SINK_DEVICE_ID(0x42, 0x61, 0x6d, 0x62, 0x6f, 0x6f), ++ .hook = quirk_panel_replay_exit_on_flush, ++ }, + }; + + void intel_init_quirks(struct intel_display *display) diff --git a/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..36a38a49cd211ca0e0b2462f0440f5b97b143674 GIT binary patch literal 594 zcmV-Y0(vzz9S>9?hHwS<5FU&L~k-`v{wg3)h3I*69dVq>c=vWF+>uWT-k>oc3y$h-Mhu22F}xO#yT|<0d&t5pSpB-C$%&zn3hO23P%h1e%hqO z`>C#m^Tfr-=uC>?Af;zN9{CnC7$V3sgoQbzzhb={77JvzZsx$fgtLDo80oCKywri1 z{G4tAYJU>_gut?Y@q%hQM}*UOO$U{->=BS8V1ru~O`EqI7$(1{vGa>=1B-lyj%c*j z8VQ0OaBUqg89P`PXR3I8KV@N`=i#8`|z gh~1{Y!#ka3qD`1eX_YEGA~^GYX4j&>{eu5PH#8v;+5i9m literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch b/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch new file mode 100644 index 0000000..c1f3ad5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch @@ -0,0 +1,43 @@ +diff --git a/drivers/gpu/drm/xe/display/xe_display_bo.c b/drivers/gpu/drm/xe/display/xe_display_bo.c +--- a/drivers/gpu/drm/xe/display/xe_display_bo.c ++++ b/drivers/gpu/drm/xe/display/xe_display_bo.c +@@ -147,33 +147,12 @@ static struct drm_gem_object *xe_display_bo_fbdev_create(struct drm_device *drm, + struct xe_device *xe = to_xe_device(drm); + struct xe_bo *obj; + +- obj = ERR_PTR(-ENODEV); +- +- if (xe_display_bo_fbdev_prefer_stolen(xe, size)) { +- obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), +- size, +- ttm_bo_type_kernel, +- XE_BO_FLAG_FORCE_WC | +- XE_BO_FLAG_STOLEN | +- XE_BO_FLAG_GGTT, +- false); +- if (!IS_ERR(obj)) +- drm_info(&xe->drm, "Allocated fbdev into stolen\n"); +- else +- drm_info(&xe->drm, "Allocated fbdev into stolen failed: %li\n", PTR_ERR(obj)); +- } else { +- drm_info(&xe->drm, "Allocating fbdev: Stolen memory not preferred.\n"); +- } +- +- if (IS_ERR(obj)) { +- obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, +- ttm_bo_type_kernel, +- XE_BO_FLAG_FORCE_WC | +- XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | +- XE_BO_FLAG_GGTT, +- false); +- } +- ++ obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, ++ ttm_bo_type_kernel, ++ XE_BO_FLAG_FORCE_WC | ++ XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | ++ XE_BO_FLAG_GGTT, ++ false); + if (IS_ERR(obj)) { + drm_err(&xe->drm, "failed to allocate framebuffer (%pe)\n", obj); + return ERR_PTR(-ENOMEM); diff --git a/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch.sig b/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..5bc668bc7523f7bfdd043f81c3626ed9757c13f8 GIT binary patch literal 594 zcmV-Y0}fM$Cgb7V3v+iNks{pE%6AW;^EU}YTlBiI6aVTo;)zDCGMOd!AV z&e1o$bV@?IP;>6;EFco9!E(e<3iKqt$8KT#9bQ7JOx13o;NLGyHm?bYd9oY#tf?*3 zU$nxucn1M-qg%d*81;lV%aSk;&{jnQKGK5<1Rl9S6?XW}_C+%yKjw^59;N!>S^vNw z^l=xiLplgv6~eS>ng#|!OjD}W+>Xu05-3X#uxvyqH~0WWRUC|OT@{Q3t+vLwjw5QZ z&qgizyh(=KY;B(!|J}B^jN&pK*(MV)Azpvj05^SPi4s%F;G0D;3BL02yHID@t-pf& zpLO-4S*Fax4Q)~I6^iu5xKA*nOw1<)-!kA9 gG&fOgvOnb39jRd0= 30) ++ set_context_latency = max(1, set_context_latency); ++ + return set_context_latency; + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0420-safe-window.patch.sig b/pkgbuilds/linux-omarchy-muqss/0420-safe-window.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..45646383e35d8e1cd02de353afb33a5c7dbc9fe9 GIT binary patch literal 594 zcmV-Y0kVg26h8e6=5cs0asw zDQSe95|&kU7skdmrAr0QyytxdavaU%IAl9|a=r@=pi~vG(4!Oh3kH0bHGM5&|0V&N+6*d_0iQUeG zoEdokN3mN?OFM;xB^C`?+(Q{l0-UV^L^YF}>7f#OcgIDD$uJQy4!(kRRUyf2n@7Qe z)bfwXG&F1boY`LmN%6i<2<+xo-2D%v1#2;0@bpl#fIEAZNG!eO;`26^WW#z0Q6$1gpJncjQ%zvzULczLh+?~%4lJ_q21uGi zx!ekZ7yOk@(;#Lyjez}B7Iv~T4Y^T~q+LtV0WA>LXRE-)Z3_=5GYW$0B&D)P56rTwn2`y$3&@k? g?#=45=K#f~8c9@3v;sS^o`tkQ1y4)o1gPdE&-=#^f&c&j literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch b/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch new file mode 100644 index 0000000..b64db93 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch @@ -0,0 +1,112 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/display/intel_fbc.c +--- a/drivers/gpu/drm/i915/display/intel_fbc.c ++++ b/drivers/gpu/drm/i915/display/intel_fbc.c +@@ -59,6 +59,7 @@ + #include "intel_fbc_regs.h" + #include "intel_frontbuffer.h" + #include "intel_parent.h" ++#include "skl_universal_plane.h" + + #define for_each_fbc_id(__display, __fbc_id) \ + for ((__fbc_id) = INTEL_FBC_A; (__fbc_id) < I915_MAX_FBCS; (__fbc_id)++) \ +@@ -1305,6 +1306,9 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat + struct intel_display *display = to_intel_display(plane_state); + unsigned int effective_w, effective_h, max_w, max_h; + ++ if (DISPLAY_VER(display) >= 20) ++ return true; ++ + intel_fbc_max_surface_size(display, &max_w, &max_h); + + effective_w = plane_state->view.color_plane[0].x + +@@ -1315,10 +1319,18 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat + return effective_w <= max_w && effective_h <= max_h; + } + +-static void intel_fbc_max_plane_size(struct intel_display *display, ++static void intel_fbc_max_plane_size(const struct intel_plane_state *plane_state, + unsigned int *w, unsigned int *h) + { +- if (DISPLAY_VER(display) >= 10) { ++ struct intel_display *display = to_intel_display(plane_state); ++ struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); ++ const struct drm_framebuffer *fb = plane_state->hw.fb; ++ unsigned int rotation = plane_state->hw.rotation; ++ ++ if (DISPLAY_VER(display) >= 20) { ++ *w = intel_plane_max_width(plane, fb, 0, rotation); ++ *h = 4096; ++ } else if (DISPLAY_VER(display) >= 10) { + *w = 5120; + *h = 4096; + } else if (DISPLAY_VER(display) >= 8 || display->platform.haswell) { +@@ -1335,10 +1347,9 @@ static void intel_fbc_max_plane_size(struct intel_display *display, + + static bool intel_fbc_plane_size_valid(const struct intel_plane_state *plane_state) + { +- struct intel_display *display = to_intel_display(plane_state); + unsigned int w, h, max_w, max_h; + +- intel_fbc_max_plane_size(display, &max_w, &max_h); ++ intel_fbc_max_plane_size(plane_state, &max_w, &max_h); + + w = drm_rect_width(&plane_state->uapi.src) >> 16; + h = drm_rect_height(&plane_state->uapi.src) >> 16; +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.c ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c +@@ -1932,10 +1932,10 @@ static int intel_plane_min_height(struct intel_plane *plane, + return 1; + } + +-static int intel_plane_max_width(struct intel_plane *plane, +- const struct drm_framebuffer *fb, +- int color_plane, +- unsigned int rotation) ++int intel_plane_max_width(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation) + { + if (plane->max_width) + return plane->max_width(fb, color_plane, rotation); +@@ -1943,10 +1943,10 @@ static int intel_plane_max_width(struct intel_plane *plane, + return INT_MAX; + } + +-static int intel_plane_max_height(struct intel_plane *plane, +- const struct drm_framebuffer *fb, +- int color_plane, +- unsigned int rotation) ++int intel_plane_max_height(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation) + { + if (plane->max_height) + return plane->max_height(fb, color_plane, rotation); +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.h b/drivers/gpu/drm/i915/display/skl_universal_plane.h +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.h ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.h +@@ -8,6 +8,7 @@ + + #include + ++struct drm_framebuffer; + struct intel_crtc; + struct intel_display; + struct intel_initial_plane_config; +@@ -42,5 +43,13 @@ bool icl_is_hdr_plane(struct intel_display *display, enum plane_id plane_id); + + u32 skl_plane_aux_dist(const struct intel_plane_state *plane_state, + int color_plane); ++int intel_plane_max_width(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation); ++int intel_plane_max_height(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation); + + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch.sig b/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..41dee2a4f48e14916bd60740d327369cd1f58e66 GIT binary patch literal 594 zcmV-Y0Ikh8{9UUo;wVj_c4_fYH}4+Vt{ z3HTvnG0;)fyirWq$4zdB%R0pd228t2gnZf&*6K48*3VO~M-HsAzE5F!CGE;67XmO)0@}lqAy4X=^yxh$pM+UFpmZ~1ETfb{eoo+im-gi z_@ebxf1JRL?g!-fhvZ~sgOkW_fCwE9J>M)P@MfI8XJ2Ngs1 zUnpohYeweh*9k-!b6Mp-$+74xnIl8$4g?*2gxU9Ft5EQA!fJqfl6(cjOrc;7)UkL) z)jYmi-N1Z)w{sU@Y3l79D)wg|1vwk(+yz9s^q=5iF&5+^>47HEo-;YSm`CX@UdDzf zRYM^9zkUtX53~z~cVcJ6D!`@(>JisXt&tkU*c_J`Yth#Zcgu+2?pvY;iDDoHC+UEI gL)9v2yCOSHW1T9y;VI~a|3y-&PAT~Du>;D22d4ZIn*aa+ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch b/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch new file mode 100644 index 0000000..a13b165 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch @@ -0,0 +1,67 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c +--- a/drivers/gpu/drm/i915/display/intel_bw.c ++++ b/drivers/gpu/drm/i915/display/intel_bw.c +@@ -52,6 +52,8 @@ + + #define DEPROGBWPCLIMIT 60 + ++#define PEAK_BW_THRESHOLD 20000 ++ + struct intel_psf_gv_point { + u8 clk; /* clock in multiples of 16.6666 MHz */ + }; +@@ -587,6 +589,33 @@ + return num_channels * (channel_width / 8) * dclk; + } + ++static void xe3_add_peakbw_threshold(struct intel_display *display) ++{ ++ u8 qgv_points = display->bw.max[0].num_qgv_points; ++ ++ if (!HAS_PEAK_BW_THRESHOLD(display)) ++ return; ++ ++ if (qgv_points >= I915_NUM_QGV_POINTS) { ++ drm_dbg_kms(display->drm, "QGV points maxed out; skipping peak bandwidth threshold.\n"); ++ return; ++ } ++ ++ if (qgv_points <= 1) ++ return; ++ ++ for (int i = 0; i < ARRAY_SIZE(display->bw.max); i++) { ++ struct intel_bw_info *bi = &display->bw.max[i]; ++ ++ bi->num_qgv_points++; ++ bi->peakbw[qgv_points] = PEAK_BW_THRESHOLD; ++ bi->deratedbw[qgv_points] = PEAK_BW_THRESHOLD; ++ } ++ ++ drm_dbg_kms(display->drm, "An extra QGV point %d added for Peak bw threshod of %d\n", ++ qgv_points, PEAK_BW_THRESHOLD); ++} ++ + static int tgl_get_bw_info(struct intel_display *display, + const struct dram_info *dram_info, + const struct intel_soc_bw_params *soc_bw_params, +@@ -684,6 +713,9 @@ + } + } + ++ /* For xe3 cases add an extra qgv point for Peak bw threshold */ ++ xe3_add_peakbw_threshold(display); ++ + /* + * In case if SAGV is disabled in BIOS, we always get 1 + * SAGV point, but we can't send PCode commands to restrict it +diff --git a/drivers/gpu/drm/i915/display/intel_display_device.h b/drivers/gpu/drm/i915/display/intel_display_device.h +--- a/drivers/gpu/drm/i915/display/intel_display_device.h ++++ b/drivers/gpu/drm/i915/display/intel_display_device.h +@@ -191,6 +191,7 @@ + #define HAS_MBUS_JOINING(__display) ((__display)->platform.alderlake_p || DISPLAY_VER(__display) >= 14) + #define HAS_MSO(__display) (DISPLAY_VER(__display) >= 12) + #define HAS_OVERLAY(__display) (DISPLAY_INFO(__display)->has_overlay) ++#define HAS_PEAK_BW_THRESHOLD(__display) (DISPLAY_VER(__display) >= 30) + #define HAS_PIPEDMC(__display) (DISPLAY_VER(__display) >= 12) + #define HAS_PIXEL_NORMALIZER(__display) (DISPLAY_VER(__display) >= 35) + #define HAS_PSR(__display) (DISPLAY_INFO(__display)->has_psr) diff --git a/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch.sig b/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..711523eb789b06af886c523aabd77bdfc6d22fd5 GIT binary patch literal 594 zcmV-Y0v!_#VQ;?H!~Ur+s=eVm<)?~{ za0HuQGfb+Cb^BB*V{(c4&&Ss9esJ?`%4x$Aa+<;GjppK-yTJp}izTWZT6zjr++m>e z;Z~L`)IvOpmE(oN-Oo!RiT%$ex?C@o%}CTj^qA>g zmiWGtiwy=Z5E`8@i;~>1x3dADJ=*UgmqL0m@#FJ7{QFNb%p(CXXzyFsK$!pc{uq)l zJg)eoSIRMUq<8HzoAF1;(SUhVM?39TmgF_af1RNLuO~(oD(7KGhpi;uE>xs??(Axh z>T#Y}QnXpU1DO*P)|vHKc?j%kxe|l+@^36#S4~~h#FoLRTiPx@EV#1IU_0gzS>Qd4 zrh&L?C_(mm^Ni8-chKU8!?>Y>5v{PEoO@@sk(h|?Y40PrpeoXGpHwx_r4z2beRd1_ g_giCHw&9KXWR*^QNlAcKF*O0xp-=vDJO7ipipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->clear_psr2_sel_fetch) + return; + + intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); +@@ -569,8 +570,10 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch) { ++ i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); + return; ++ } + + if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) { + if (crtc_state->enable_psr2_su_region_et) { +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -1177,6 +1177,8 @@ struct intel_crtc_state { + bool has_sel_update; + bool enable_psr2_sel_fetch; + bool enable_psr2_su_region_et; ++ /* Drop the stale selective fetch enable bits as selective fetch is turned off */ ++ bool clear_psr2_sel_fetch; + bool req_psr2_sdp_prior_scanline; + bool has_panel_replay; + bool link_off_after_as_sdp_when_pr_active; +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2941,6 +2941,8 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) + { + struct intel_display *display = to_intel_display(state); ++ const struct intel_crtc_state *old_crtc_state = ++ intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + struct intel_plane_state *new_plane_state, *old_plane_state; + struct intel_plane *plane; +@@ -2953,6 +2955,19 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, + bool full_update = false, su_area_changed; + int i, ret; + ++ /* ++ * Selective fetch is not always usable, for instance it is dropped ++ * while pipe CRC is active. The planes keep their selective fetch ++ * enable bit set in hardware over that, and a plane disabled while ++ * selective fetch is off never gets the bit cleared. Once selective ++ * fetch comes back the hardware would resume fetching for a plane that ++ * is no longer enabled and keep its DDB range reserved, so have the ++ * plane update drop the bit for every plane of the pipe as selective ++ * fetch is turned off. ++ */ ++ crtc_state->clear_psr2_sel_fetch = old_crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->enable_psr2_sel_fetch; ++ + if (!crtc_state->enable_psr2_sel_fetch) + return 0; + +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.c ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c +@@ -885,7 +885,8 @@ static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->clear_psr2_sel_fetch) + return; + + intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); +@@ -1634,10 +1635,8 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) +- return; +- +- if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) ++ if (crtc_state->enable_psr2_sel_fetch && ++ drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) + intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), + SEL_FETCH_PLANE_CTL_ENABLE); + else diff --git a/pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig b/pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..fa39ab522991323e07a168e21b9858b59b9a92bd GIT binary patch literal 594 zcmV-Y0-?DCR^kTc_BZrz|Tzfv={24gg9`uyDGqJMs%SNoHJRXHHq#aQdO#}QCD;o;qlH? zovnO>{Aljwc&2c3pdRFQPQSy5^Hhg1oh43V-#6b|K)ZtbSO#lD@ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch b/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch new file mode 100644 index 0000000..820d377 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_dsb.c b/drivers/gpu/drm/i915/display/intel_dsb.c +--- a/drivers/gpu/drm/i915/display/intel_dsb.c ++++ b/drivers/gpu/drm/i915/display/intel_dsb.c +@@ -902,9 +902,17 @@ void intel_dsb_wait_for_delayed_vblank(struct intel_atomic_state *state, + * the hardware itself guarantees that we're SCL lines + * away from the delayed vblank, and we won't be inside + * the vmin safe window so this extra wait does nothing. ++ * ++ * Experimentally, DSB may observe a slightly stale ++ * PIPEDSL value. When the actual scanline has just reached ++ * safe_window_start, WAIT_DSL_OUT may complete immediately ++ * due to the stale value. ++ * ++ * Shift the start back by one scanline to ensure the wait ++ * window is entered reliably. + */ + intel_dsb_wait_scanline_out(state, dsb, +- intel_vrr_safe_window_start(crtc_state), ++ intel_vrr_safe_window_start(crtc_state) - 1, + intel_vrr_vmin_safe_window_end(crtc_state)); + /* + * When the push is sent during vblank it will trigger diff --git a/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig b/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..cd6b14b622fb327fc4b22e90d0b30855fac2d22e GIT binary patch literal 594 zcmV-Y0m400000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=ZCBh{0PPZW~)vh_|knOk)qje z2GLHIkF^hDRR9-o6Q$+Huredc8rYJ-7VA6-S`{uMT}?g(*JBpfIk4cvt~-K-Els}f z5Rv%gG2hH+0km_%Kf1Jx)0;$TTM3mbowcd@@~NM;3- z-hg_a=rmfa5RB5A=g&WrO3vBGa!yO-(yWUq$l>WLHlIjI*(X4Quu=sE&y&rO1Gva; z0xkm>P1y{mh2m_M`y3mlRhU4e;C0nkEh6VAB;ifOAP^X^fXuQlDjhw.adjusted_mode; + +- if (!intel_vrr_dc_balance_possible(crtc_state) || !crtc_state->vrr.enable) ++ if (!intel_vrr_dc_balance_possible(crtc_state) || ++ !crtc_state->vrr.enable || !display->params.enable_dc_balance) + return; + + crtc_state->vrr.dc_balance.vmax = crtc_state->vrr.vmax; diff --git a/pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig b/pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..c8a67fa7ff9cec379470e6048a1f059fc34ad67a GIT binary patch literal 594 zcmV-Y0nlh3o%&~<+-b9h`h z5pV}P1_0j$-#DNiL<{hW;$%@ba^|-ev?d8jeIoZ|1m7h~b$p-Ns_@EtM;t@yH>d z&tYz&UqS(3!-439sM+#-ieo6}=BhqyQ$%pYBW+95XB!S@hOm=E?q?bpVV^qaZn=LZO@&2J=SNcD~3gZ2uY z1xx44%rGtX16IZtJ=%PPfj9>+^+rhBi;$WZ;(cJk>)4Jw>%O^ z3-~XD^zA0OMyy%}1#->5WwKz5WqG53LyO2sP#OZSI gY1YI6r+9OWQGgo2oen-@vHmTmmrGK7z;P_28yKJ*Jpcdz literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch b/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch new file mode 100644 index 0000000..02991bc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch @@ -0,0 +1,456 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -104,6 +104,7 @@ + #include "ivsrcid/dcn/irqsrcs_dcn_1_0.h" + + #include "modules/inc/mod_freesync.h" ++#include "modules/inc/mod_info_packet.h" + #include "modules/inc/mod_power.h" + #include "modules/power/power_helpers.h" + +@@ -10088,6 +10089,9 @@ static void update_freesync_state_on_stream( + &vrr_infopacket, + pack_sdp_v1_3); + ++ if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) ++ mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); ++ + new_crtc_state->freesync_vrr_info_changed |= + (memcmp(&new_crtc_state->vrr_infopacket, + &vrr_infopacket, +@@ -10099,6 +10103,36 @@ static void update_freesync_state_on_stream( + new_stream->vrr_infopacket = vrr_infopacket; + new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); + ++ /* ++ * HDMI ALLM: when Gaming-VRR is active (VRR_EN=1) and the sink ++ * advertises ALLM in the SCDS, the Source shall transmit the HF-VSIF ++ * with ALLM_Mode=1 (HDMI 2.1 Section 7.6.6). ++ */ ++ if (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ new_stream->signal == SIGNAL_TYPE_HDMI_FRL) { ++ struct dc_info_packet hf_vsif = {0}; ++ bool sink_allm = aconn && aconn->base.display_info.hdmi.allm; ++ bool allm = sink_allm && ++ (vrr_params.state == VRR_STATE_ACTIVE_VARIABLE || ++ vrr_params.state == VRR_STATE_ACTIVE_FIXED); ++ bool allm_changed; ++ ++ if (allm) ++ mod_build_hf_vsif_infopacket(new_stream, &hf_vsif, allm, allm); ++ ++ allm_changed = memcmp(&new_stream->hfvsif_infopacket, &hf_vsif, ++ sizeof(hf_vsif)) != 0; ++ new_crtc_state->freesync_vrr_info_changed |= allm_changed; ++ new_stream->hfvsif_infopacket = hf_vsif; ++ ++ if (allm_changed) ++ drm_dbg_driver(adev_to_drm(adev), ++ "ALLM: flip on crtc=%u: sink_allm=%d vrr_state=%d -> ALLM_Mode=%d\n", ++ new_crtc_state->base.crtc->base.id, ++ sink_allm, ++ vrr_params.state, allm); ++ } ++ + if (new_crtc_state->freesync_vrr_info_changed) + drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d", + new_crtc_state->base.crtc->base.id, +@@ -10665,9 +10699,12 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, + } + + if (acrtc_state->stream) { +- if (acrtc_state->freesync_vrr_info_changed) ++ if (acrtc_state->freesync_vrr_info_changed) { + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; ++ bundle->stream_update.hfvsif_infopacket = ++ &acrtc_state->stream->hfvsif_infopacket; ++ } + } + } + +@@ -12069,6 +12106,14 @@ static void get_freesync_config_for_crtc( + } + out: + new_crtc_state->freesync_config = config; ++ ++ drm_dbg_driver(new_con_state->base.connector->dev, ++ "VRR: cfg vrr_enabled=%d vrr_supported=%d fs_capable=%d vrefresh=%d min=%d max=%d state=%d\n", ++ new_crtc_state->base.vrr_enabled, ++ new_crtc_state->vrr_supported, ++ new_con_state->freesync_capable, vrefresh, ++ aconnector->min_vfreq, aconnector->max_vfreq, ++ config.state); + } + + static void reset_freesync_config_for_crtc( +@@ -14031,6 +14076,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) + goto update; + ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: enter signal=%d hdmi_vrr=%d mrange[%d-%d] hdmi.vrr_cap[sup=%d min=%d max=%d]\n", ++ sink->sink_signal, connector->display_info.hdmi.vrr_cap.supported, ++ connector->display_info.monitor_range.min_vfreq, ++ connector->display_info.monitor_range.max_vfreq, ++ connector->display_info.hdmi.vrr_cap.supported, ++ connector->display_info.hdmi.vrr_cap.vrr_min, ++ connector->display_info.hdmi.vrr_cap.vrr_max); ++ + edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() + + /* Some eDP panels only have the refresh rate range info in DisplayID */ +@@ -14056,7 +14110,9 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; + } + +- } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { ++ } else if (drm_edid && ++ (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ sink->sink_signal == SIGNAL_TYPE_HDMI_FRL)) { + i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + if (i >= 0) { + amdgpu_dm_connector->vsdb_info = vsdb_info; +@@ -14072,6 +14128,59 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; + } + } ++ ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: amd_vsdb i=%d fs_sup=%d min=%d max=%d fs_capable=%d\n", ++ i, vsdb_info.freesync_supported, ++ vsdb_info.min_refresh_rate_hz, ++ vsdb_info.max_refresh_rate_hz, freesync_capable); ++ ++ /* ++ * If AMD VSDB didn't provide a valid FreeSync range, fall back to ++ * the HDMI 2.1 VRR capability parsed from the HF-VSDB. ++ */ ++ if (!freesync_capable && connector->display_info.hdmi.vrr_cap.supported) { ++ struct drm_hdmi_vrr_cap *vrr_cap = ++ &connector->display_info.hdmi.vrr_cap; ++ ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: HF-VSDB fallback: hdmi_vrr=1 vrr_cap[sup=%d min=%d max=%d] mrange_max=%d\n", ++ vrr_cap->supported, vrr_cap->vrr_min, vrr_cap->vrr_max, ++ connector->display_info.monitor_range.max_vfreq); ++ ++ if (vrr_cap->supported && vrr_cap->vrr_min > 0) { ++ amdgpu_dm_connector->min_vfreq = vrr_cap->vrr_min; ++ amdgpu_dm_connector->max_vfreq = vrr_cap->vrr_max ? ++ vrr_cap->vrr_max : ++ connector->display_info.monitor_range.max_vfreq; ++ ++ /* ++ * VRRMAX = 0 in the HF-VSDB means "up to the Base ++ * Refresh Rate". If the EDID also did not provide a ++ * monitor range max, fall back to the Base Refresh ++ * Rate (the highest refresh rate of the preferred ++ * timing) so a valid VRR range is still reported to ++ * userspace. ++ */ ++ if (!amdgpu_dm_connector->max_vfreq) { ++ struct drm_display_mode *brr_mode = ++ get_highest_refresh_rate_mode(amdgpu_dm_connector, true); ++ ++ if (brr_mode) ++ amdgpu_dm_connector->max_vfreq = ++ drm_mode_vrefresh(brr_mode); ++ } ++ ++ if (amdgpu_dm_connector->max_vfreq - ++ amdgpu_dm_connector->min_vfreq > 10) ++ freesync_capable = true; ++ ++ connector->display_info.monitor_range.min_vfreq = ++ amdgpu_dm_connector->min_vfreq; ++ connector->display_info.monitor_range.max_vfreq = ++ amdgpu_dm_connector->max_vfreq; ++ } ++ } + } + + if (amdgpu_dm_connector->dc_link) +@@ -14113,6 +14222,11 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + if (dm_con_state) + dm_con_state->freesync_capable = freesync_capable; + ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: caps result: freesync_capable=%d min_vfreq=%d max_vfreq=%d\n", ++ freesync_capable, amdgpu_dm_connector->min_vfreq, ++ amdgpu_dm_connector->max_vfreq); ++ + if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable && + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) { + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false; +diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +--- a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h ++++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +@@ -67,6 +67,10 @@ struct AS_Df_params { + struct frame_duration_op decrease; + }; + ++void mod_build_infopacket_vtem(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket); ++ + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, + enum adaptive_sync_type asType, const struct AS_Df_params *param, + struct dc_info_packet *info_packet); +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -291,6 +291,21 @@ void set_vsc_packet_colorimetry_data( + info_packet->sb[18] = 0; + } + ++static void set_field_with_mask(unsigned char *dest, unsigned int mask, unsigned int value) ++{ ++ unsigned int shift = 0; ++ ++ if (!mask || !dest) ++ return; ++ ++ while (!((mask >> shift) & 1)) ++ shift++; ++ ++ *dest = *dest & ~mask; ++ value = value & (mask >> shift); ++ *dest = *dest | (value << shift); ++} ++ + void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet, + enum dc_color_space cs, +@@ -644,6 +659,100 @@ void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + info_packet->valid = true; + } + ++static void build_vtem_infopacket_data(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket) ++{ ++ unsigned int field_rate_in_hz; ++ ++ /* FVA Factor setting */ ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__FVA_FACTOR_M1, ++ (fva_factor > 0) ? (fva_factor - 1) : 0); ++ /* VRR Parameters */ ++ if (vrr->state == VRR_STATE_ACTIVE_VARIABLE || ++ vrr->state == VRR_STATE_ACTIVE_FIXED) { ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__VRR_EN, 1); ++ } else { ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__VRR_EN, 0); ++ } ++ ++ if (vrr->state == VRR_STATE_ACTIVE_FIXED) ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__M_CONST, vrr->m_const); ++ ++ if (!stream->timing.vic) { ++ set_field_with_mask(&infopacket->sb[VTEM_MD1], MASK_VTEM_MD1__BASE_VFRONT, ++ stream->timing.v_front_porch); ++ ++ ++ /* TODO: In dal2, we check mode flags for a reduced blanking timing. ++ * Need a way to relay that information to this function. ++ * if("ReducedBlanking") ++ * { ++ * set_field_with_mask(&infopacket->sb[VRR_VTEM_MD2], MASK__VRR_VTEM_MD2__RB, 1; ++ * } ++ */ ++ ++ field_rate_in_hz = stream->timing.pix_clk_100hz * 100; ++ field_rate_in_hz /= stream->timing.h_total; ++ field_rate_in_hz = (field_rate_in_hz + stream->timing.v_total / 2) ++ / stream->timing.v_total; ++ ++ set_field_with_mask(&infopacket->sb[VTEM_MD2], MASK_VTEM_MD2__BASE_REFRESH_RATE_98, ++ field_rate_in_hz >> 8); ++ set_field_with_mask(&infopacket->sb[VTEM_MD3], MASK_VTEM_MD3__BASE_REFRESH_RATE_07, ++ field_rate_in_hz); ++ ++ } ++ ++ /* ++ * When no VTEM feature is enabled (neither VRR nor FVA), signal a ++ * zero-length data set (MLDS) by clearing Data_Set_Length. HDMI 2.1 ++ * 10.10.2.4 requires the Source to either stop transmitting the VTEM ++ * or set Data_Set_Length = 0 when no feature is enabled; keeping the ++ * VTEM with Data_Set_Length = 0 preserves the every-MTW cadence while ++ * staying compliant (e.g. HDMI GCTS HF1-58 step 6.2). ++ */ ++ if (vrr->state != VRR_STATE_ACTIVE_VARIABLE && ++ vrr->state != VRR_STATE_ACTIVE_FIXED && fva_factor == 0) ++ set_field_with_mask(&infopacket->sb[VTEM_PB6], ++ MASK_VTEM_PB6__DATA_SET_LENGTH_LSB, 0); ++ ++ infopacket->valid = true; ++} ++ ++static void build_infopacket_header_vtem(enum signal_type signal, ++ struct dc_info_packet *infopacket) ++{ ++ /* HEADER */ ++ ++ /* HB0, HB1, HB2 indicates PacketType VTEMPacket */ ++ infopacket->hb0 = 0x7F; ++ infopacket->hb1 = 0xC0; ++ infopacket->hb2 = 0x00; /* sequence_index */ ++ ++ set_field_with_mask(&infopacket->sb[VTEM_PB0], MASK_VTEM_PB0__VFR, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB2], MASK_VTEM_PB2__ORGANIZATION_ID, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB3], MASK_VTEM_PB3__DATA_SET_TAG_MSB, 0); ++ set_field_with_mask(&infopacket->sb[VTEM_PB4], MASK_VTEM_PB4__DATA_SET_TAG_LSB, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB5], MASK_VTEM_PB5__DATA_SET_LENGTH_MSB, 0); ++ set_field_with_mask(&infopacket->sb[VTEM_PB6], MASK_VTEM_PB6__DATA_SET_LENGTH_LSB, 4); ++} ++ ++void mod_build_infopacket_vtem(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket) ++{ ++ /* VTEM info packet for HdmiVrr */ ++ ++ memset(infopacket, 0, sizeof(struct dc_info_packet)); ++ ++ /* VTEM Packet is structured differently */ ++ build_infopacket_header_vtem(stream->signal, infopacket); ++ build_vtem_infopacket_data(stream, vrr, fva_factor, infopacket); ++ ++ infopacket->valid = true; ++} ++ + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, + enum adaptive_sync_type asType, + const struct AS_Df_params *param, +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -6182,6 +6182,33 @@ static void drm_parse_ycbcr420_deep_color_info(struct drm_connector *connector, + hdmi->y420_dc_modes = dc_mask; + } + ++static void drm_parse_hdmi_gaming_info(struct drm_hdmi_info *hdmi, const u8 *db) ++{ ++ struct drm_hdmi_vrr_cap *vrr = &hdmi->vrr_cap; ++ ++ if (cea_db_payload_len(db) < 8) ++ return; ++ ++ hdmi->fapa_start_location = db[8] & DRM_EDID_FAPA_START_LOCATION; ++ hdmi->allm = db[8] & DRM_EDID_ALLM; ++ vrr->fva = db[8] & DRM_EDID_FVA; ++ vrr->cnmvrr = db[8] & DRM_EDID_CNMVRR; ++ vrr->cinema_vrr = db[8] & DRM_EDID_CINEMA_VRR; ++ vrr->mdelta = db[8] & DRM_EDID_MDELTA; ++ ++ if (cea_db_payload_len(db) < 9) ++ return; ++ ++ vrr->vrr_min = db[9] & DRM_EDID_VRR_MIN_MASK; ++ vrr->supported = (vrr->vrr_min > 0 && vrr->vrr_min <= 48); ++ ++ if (cea_db_payload_len(db) < 10) ++ return; ++ ++ vrr->vrr_max = (db[9] & DRM_EDID_VRR_MAX_UPPER_MASK) << 2 | db[10]; ++ vrr->supported &= (vrr->vrr_max == 0 || vrr->vrr_max >= 100); ++} ++ + static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc, + const u8 *hf_scds) + { +@@ -6308,6 +6335,8 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, + + drm_parse_ycbcr420_deep_color_info(connector, hf_scds); + ++ drm_parse_hdmi_gaming_info(&connector->display_info.hdmi, hf_scds); ++ + if (cea_db_payload_len(hf_scds) >= 11 && hf_scds[11]) { + drm_parse_dsc_info(hdmi_dsc, hf_scds); + dsc_support = true; +@@ -6317,6 +6346,19 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, + "[CONNECTOR:%d:%s] HF-VSDB: max TMDS clock: %d KHz, HDMI 2.1 support: %s, DSC 1.2 support: %s\n", + connector->base.id, connector->name, + max_tmds_clock, str_yes_no(max_frl_rate), str_yes_no(dsc_support)); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] FAPA in blanking: %s, ALLM support: %s, Fast Vactive support: %s\n", ++ connector->base.id, connector->name, str_yes_no(hdmi->fapa_start_location), ++ str_yes_no(hdmi->allm), str_yes_no(hdmi->vrr_cap.fva)); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] Negative M VRR support: %s, CinemaVRR support: %s, Mdelta: %d\n", ++ connector->base.id, connector->name, str_yes_no(hdmi->vrr_cap.cnmvrr), ++ str_yes_no(hdmi->vrr_cap.cinema_vrr), hdmi->vrr_cap.mdelta); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] VRRmin: %u, VRRmax: %u, VRR supported: %s\n", ++ connector->base.id, connector->name, hdmi->vrr_cap.vrr_min, ++ hdmi->vrr_cap.vrr_max, str_yes_no(hdmi->vrr_cap.supported)); ++ + } + + static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, +diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h +--- a/include/drm/drm_connector.h ++++ b/include/drm/drm_connector.h +@@ -254,6 +254,44 @@ struct drm_scdc { + struct drm_scrambling scrambling; + }; + ++/** ++ * struct drm_hdmi_vrr_cap - Information about VRR capabilities of a HDMI sink ++ * ++ * Describes the VRR support provided by HDMI 2.1 sink. The information is ++ * fetched fom additional HFVSDB blocks defined for HDMI 2.1. ++ */ ++struct drm_hdmi_vrr_cap { ++ /** @fva: flag for Fast VActive (Quick Frame Transport) support */ ++ bool fva; ++ ++ /** @mcnmvrr: flag for Negative M VRR support */ ++ bool cnmvrr; ++ ++ /** @mcinema_vrr: flag for Cinema VRR support */ ++ bool cinema_vrr; ++ ++ /** @mdelta: flag for limited frame-to-frame compensation support */ ++ bool mdelta; ++ ++ /** ++ * @vrr_min : minimum supported variable refresh rate in Hz. ++ * Valid values only inide 1 - 48 range ++ */ ++ u16 vrr_min; ++ ++ /** ++ * @vrr_max : maximum supported variable refresh rate in Hz (optional). ++ * Valid values are either 0 (max based on video mode) or >= 100 ++ */ ++ u16 vrr_max; ++ ++ /** ++ * @supported: flag for vrr support based on checking for VRRmin and ++ * VRRmax values having correct values. ++ */ ++ bool supported; ++}; ++ + /** + * struct drm_hdmi_dsc_cap - DSC capabilities of HDMI sink + * +@@ -330,6 +368,15 @@ struct drm_hdmi_info { + /** @max_lanes: supported by sink */ + u8 max_lanes; + ++ /** @fapa_start_location: flag for the FAPA in blanking support */ ++ bool fapa_start_location; ++ ++ /** @allm: flag for Auto Low Latency Mode support by sink */ ++ bool allm; ++ ++ /** @vrr_cap: VRR capabilities of the sink */ ++ struct drm_hdmi_vrr_cap vrr_cap; ++ + /** @dsc_cap: DSC capabilities of the sink */ + struct drm_hdmi_dsc_cap dsc_cap; + }; diff --git a/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch.sig b/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..8e70633dd2c72cc95ddf6ddd06c99870000fdd11 GIT binary patch literal 594 zcmV-Y0=0QY9UyPZNW=O7`UtdVDK7kRb&YmR zD%RZ*e0NdrM|gbd=9rUhc6CoXVT$-K)kHoX;zCmpUe|>9s;!Rj2q(9Ul3T!IR+W*K zkK@4z{s3+B8NI|(k2)lVDzF`yba{#YJLIq0)usa<43wMh2)joLa|X|XMJk^xzjI4a z52|YuD-_JAbp|PYFpl*)VwRg%(WT(^F#HDVh4kyBT!ro`ZntlUvBYOEy?hCk}l+B@ZxKaXhn38ZRE_}CYy({uy|>f;cpxpkdf8N;$Xg8&LP zYwh8y$eusU6O5~=KRKosink->sink_signal == SIGNAL_TYPE_HDMI_FRL) ++ /* Per HDMI 2.1, VTEM is valid on TMDS as well as FRL */ ++ if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL || ++ (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A && ++ aconn && aconn->base.display_info.hdmi.vrr_cap.supported)) + mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); + + new_crtc_state->freesync_vrr_info_changed |= diff --git a/pkgbuilds/linux-omarchy-muqss/0451-amd-vtem-tmds-links.patch.sig b/pkgbuilds/linux-omarchy-muqss/0451-amd-vtem-tmds-links.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..29e7f443a5c2d9250810ce3923349a6a527e3d83 GIT binary patch literal 594 zcmV-Y0yx8nl^g-{W zce;MZ;dQ7p6w2Se3twSs zHyoIm-k~l12BHh!BXR3^)kMsh_y92rVkjKIs%yw66W{p}zd)AB3Jw}27ylF>SI?KN zb+AK@t+$jWMTPp%V==UWxcL1jn0Td$z68-^1z~7tQ@psMTW&yH5myA5%v!_@VT zT7grUI>6b$zEHCE!gZL@qgvLUk+i*0MB{F1>8uOudxne#j`{HHqPpa80{aXv-zryw znI^q7T{5T}`utNf9BH~>szlE!t9O)Qm7U=MglxM5(Xy1TGXR263oydK2(PE+}Wq03QJ89fXY(otw$ONd`1$P1vlVnb1ey|d}t4&q`u!UUih}s z1CTBB8&dTX(kVR!U7~e8rd{*g+Qvh;p)e>@%lWCj++*dS)Ap=ve4)!DIo^U`6%z%2 zn2aEg(A*3h@FS)?vG-DRePO38Yw-kTuWqUC#79VQgTBICIl6hHw*UtJa)Ap|ROpvW zIG`4~UuX0>Kd7vf%Q>(zBsPz0)EI3J11=};xN)uA8X(*hNy2++?c^F`x6&d1I`Q-k zS8eQVVIM~`Ic?Yc#W3`@--#+RhMYr!4Ozh4*FX`ZI4bWq7C&h9{;wu`x|bIZ!CKrY zl>Xv!l7x(k1$$mc#Mh5wA1uC-UNCQXs6~;Pg?joKZsHX631nLG8!%DvN*+`Xh|=ES z3vj=pKeDalLS__QaE)o87K`_ex9Ni43N{9vcG~Ctcz1nJF~Ms}$5hK`pQgC!f8&&R z6MJGX1XI6qsO#SZu$VQ3UHideCMv080@{v7HGm@eD!RE4ex|6Z03(jR&oA^j#*&ASXC^I0CVuNM!DlDb4Rs5_e%!+wb8sink->sink_signal == SIGNAL_TYPE_HDMI_FRL || +- (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A && ++ if (new_stream->signal == SIGNAL_TYPE_HDMI_FRL || ++ (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A && + aconn && aconn->base.display_info.hdmi.vrr_cap.supported)) + mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); + diff --git a/pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch.sig b/pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..795a7708706c667dd5c5dbb5ca8eafa760980758 GIT binary patch literal 594 zcmV-Y000000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=Np9&|4$MZk@mx2@(;xtqd~I) zy^PlMeLzU}Ht9tG4;7qwed)gCt*G!3@pz5Ib&7j8xaxPC+1=}{Bf z90pw^K~s?`N%-EyCD|RqJto|P6;rs7fxMljcnf9E9LdJJTc%%2;jE6NZU_@8OonX% zR~eQauY?RFGcpE_s7(Ec6V&LtDv9i0QiZJi*J zfTg$VR%xXH9z?`dwv(1yEFx)wp?rzU?%DBPguJpxJ3G=?s+`rc9eo;h0`9=@cXBIF7SJX|d}TY;d=p zXc5;Ybcrcz9A%B{Q5KYp g*e(V?YUwbUKu=SAB;j&8;^kH3KVHvAvj)AB*Hh6I#{d8T literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch b/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch new file mode 100644 index 0000000..62ada93 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -2025,7 +2025,7 @@ static void hdmi_frl_status_polling_work(struct work_struct *work) + if (!dc_is_hdmi_signal(dc_link->connector_signal)) + continue; + +- if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) ++ if (dc_link->frl_link_settings.frl_link_rate == 0) + continue; + + link_update = dc_link_frl_poll_status_flag(dc_link); diff --git a/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch.sig b/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..786879ac8bae93cc0482dcbea4a34367c0ecf5b0 GIT binary patch literal 594 zcmV-Y000000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=NYgM{13^|HP=BG7W`~!Bb<=t zIosairC+y_MwK_V9~yUav#Gsrah^H{zomx)35@|vhl+)uRV;C7l&2EhEQ{7Sq8gj3 znu=g3U#(1}sz2m$k0^JoEN;9Ka9to;1b*!#1NSFInCw)cHfWx+c)15X13Dhc5m-+q zOZzmhy4vtgIXOY1{{eZMr(CH-sp|hY{Gih6p{ac$nr5w|sJLG*sPohFCRWpJ+>eRR zHYkU07s~IOeD>w;9km*Nk{Dy(Na4d9l_K&N+J!uEN6mMrnyHHll2SSu6>GoS5+){x za91NC_ct*kh5Qp6b9IT_-My~|l+(G_E_XH%&`90H(4W4Y>e@Wbm4;4BaFL9zCee1^ z&#DTmFZ30A9_NQI{xfFW(rl&i@i|*ZoNa)~#O>OX@L<14i(k1dUDO;l8k{0o2)Y=S zW7ZJ+_pD-Xwu+VcIqMPHEiatwA@L^^5a0uDG+TA;hq=oc1^IREm1X~@0HA(~kFsverified_link_cap = dp_get_max_link_cap(link); + } else if (dc_is_hdmi_signal(link->local_sink->sink_signal)) { +- link->verified_link_cap = link->reported_link_cap; ++ link->frl_verified_link_cap = link->frl_reported_link_cap; + + if (is_hdmi_frl_in_use(link)) { + link->local_sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; diff --git a/pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig b/pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f2255818c677361493049615817afe08229ba05a GIT binary patch literal 594 zcmV-Y0d#esuWYRl-in zMD-GFyGJlcw|=P{ArQ!M9+X=Y#w{q{Hl%B#Kka-x(hyRjEjAGAi$1!QJ8cuKPLOLYuw*l-+Run)u% zM#&DC(|pV6Sn6M-O~~svP%7cg32y~kc7N`P4z0`#yHHoBb%W;oJ9(G~NRk;@%gkoO+l=$>1|*GXt{a(qBl9*^y=*%dIGxqn z&=yG2?g!S43KO#wof*kY9UQoi{^1Zv>$Rw}Yqrivr(8EA<^qjpqHvM}S39A^lEZ^3S2x5NkM3; zMT_hgrsMQp-fK+HhtMtu7Nt20cTP-H`Gqdgl8a+i>!Sk5{Maz&`J?UWSO--lk^#m8 gqO~TFe`W#Ec&8o>dHX+QRBaLYD3=Ztx+IS1@lAvkmjD0& literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch b/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch new file mode 100644 index 0000000..4531604 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch @@ -0,0 +1,26 @@ +diff --git a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +--- a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c ++++ b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +@@ -630,17 +630,17 @@ enum frl_cap_chk_result dml1_frl_cap_chk(struct frl_cap_chk_params *params) + { + struct frl_cap_chk_intermediates inter; + +-#if defined (CONFIG_DRM_AMD_DC_FP) +- if (params->compressed) +- return dml1_frl_cap_chk_compressed(params, &inter); +-#endif +- + return dml1_frl_cap_chk_inter(params, &inter); + } + + enum frl_cap_chk_result dml1_frl_cap_chk_inter(struct frl_cap_chk_params *params, + struct frl_cap_chk_intermediates *inter) + { ++#if defined (CONFIG_DRM_AMD_DC_FP) ++ if (params->compressed) ++ return dml1_frl_cap_chk_compressed(params, inter); ++#endif ++ + return dml1_frl_cap_chk_uncompressed(params, inter); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch.sig b/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..8297ca22c15e32b9dd9e728fb2fecdef5f9c8257 GIT binary patch literal 594 zcmV-Y0?<0_$?zlpXaJd@KGMY)q=)bKR4shEp=e)~|@@-{yjbsjfC zihb0E#;CGJc@jWujlX5}xnaRZ_Yl-~%2wAN$3t&w7=2SHEGe!jPSY@uOCWo`pAo2i zfRspO#Z#2i$lkjg)X1p0!=O(rMpVBLdnWsSQpLt|1-RP6sJZ6bO+oG%S-+@YE4jJ* zp-rV2_3DADA$`i@A72^9!DtCJsd&Y%DZbcSYf$EJ?7+l@C2BXVUY#y{GP${Fs$to) zc)i5a6Ba)jP{maudn4EX;dhNMFa-Dz#pIL?ZG3N&q(XTnNzg^`g_XWssYJudz16HO zC4&Phbs}drke~l@Q&-}Gsrey}FGrinPyAP^tYuE6p4xlB!5RD^UxBrG-H7pjnE=g`EzSk{ZB$2MMge(U;XQsN!vct-04?5< z^Gri~<}^pBN4XV-Q(|(*b}!}1i$LRrlR{E3ELF!&`N3ME<=5vaYcM>tnijtWQt%@y g`8c|;r~R!xIKj2oII|btT+Vyk5nk6dV0V4Tzyy{SzW@LL literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch b/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch new file mode 100644 index 0000000..7597723 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch @@ -0,0 +1,335 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7116,6 +7116,11 @@ static void fill_stream_properties_from_drm_display_mode( + + stream->output_color_space = get_output_color_space(timing_out, connector_state); + stream->content_type = get_output_content_type(connector_state); ++ ++ /* DisplayID Type VII pass-through timings. */ ++ if (mode_in->dsc_passthrough_timings_support && info->dp_dsc_bpp_x16 != 0) { ++ stream->timing.dsc_fixed_bits_per_pixel_x16 = info->dp_dsc_bpp_x16; ++ } + } + + static void fill_audio_info(struct audio_info *audio_info, +@@ -7636,6 +7641,7 @@ create_stream_for_sink(struct drm_connector *connector, + struct drm_display_mode mode; + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; ++ struct drm_display_mode *dsc_passthru_mode = NULL; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; +@@ -7727,6 +7733,16 @@ create_stream_for_sink(struct drm_connector *connector, + } + } + ++ list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { ++ if (dsc_passthru_mode->hdisplay == mode.hdisplay && ++ dsc_passthru_mode->vdisplay == mode.vdisplay && ++ drm_mode_vrefresh(dsc_passthru_mode) == mode_refresh) { ++ mode.dsc_passthrough_timings_support = ++ dsc_passthru_mode->dsc_passthrough_timings_support; ++ break; ++ } ++ } ++ + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + +diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h +--- a/drivers/gpu/drm/drm_displayid_internal.h ++++ b/drivers/gpu/drm/drm_displayid_internal.h +@@ -98,6 +98,7 @@ struct displayid_header { + u8 ext_count; + } __packed; + ++#define DISPLAYID_BLOCK_REV GENMASK(2, 0) + struct displayid_block { + u8 tag; + u8 rev; +@@ -126,6 +127,7 @@ struct displayid_detailed_timings_1 { + __le16 vsw; + } __packed; + ++#define DISPLAYID_BLOCK_PASSTHROUGH_TIMINGS_SUPPORT BIT(3) + struct displayid_detailed_timing_block { + struct displayid_block base; + struct displayid_detailed_timings_1 timings[]; +@@ -138,19 +140,28 @@ struct displayid_formula_timings_9 { + u8 vrefresh; + } __packed; + ++#define DISPLAYID_BLOCK_DESCRIPTOR_PAYLOAD_BYTES GENMASK(6, 4) + struct displayid_formula_timing_block { + struct displayid_block base; + struct displayid_formula_timings_9 timings[]; + } __packed; + ++#define DISPLAYID_VESA_DP_TYPE GENMASK(2, 0) + #define DISPLAYID_VESA_MSO_OVERLAP GENMASK(3, 0) + #define DISPLAYID_VESA_MSO_MODE GENMASK(6, 5) ++#define DISPLAYID_VESA_DSC_BPP_INT GENMASK(5, 0) ++#define DISPLAYID_VESA_DSC_BPP_FRACT GENMASK(3, 0) ++ ++#define DISPLAYID_VESA_DP_TYPE_EDP 0 ++#define DISPLAYID_VESA_DP_TYPE_DP 1 + + struct displayid_vesa_vendor_specific_block { + struct displayid_block base; + u8 oui[3]; + u8 data_structure_type; + u8 mso; ++ u8 dsc_bpp_int; ++ u8 dsc_bpp_fract; + } __packed; + + /* +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -45,6 +45,7 @@ + #include + #include + #include ++#include + #include + + #include "drm_crtc_internal.h" +@@ -6683,12 +6684,13 @@ static bool drm_update_displayid_adaptive_sync_range(struct drm_connector *conne + return false; + } + +-static void drm_parse_vesa_mso_data(struct drm_connector *connector, +- const struct displayid_block *block) ++static void drm_parse_vesa_specific_block(struct drm_connector *connector, ++ const struct displayid_block *block) + { + struct displayid_vesa_vendor_specific_block *vesa = + (struct displayid_vesa_vendor_specific_block *)block; + struct drm_display_info *info = &connector->display_info; ++ int dp_type; + + if (block->num_bytes < 3) { + drm_dbg_kms(connector->dev, +@@ -6700,51 +6702,73 @@ static void drm_parse_vesa_mso_data(struct drm_connector *connector, + if (oui(vesa->oui[0], vesa->oui[1], vesa->oui[2]) != VESA_IEEE_OUI) + return; + +- if (sizeof(*vesa) != sizeof(*block) + block->num_bytes) { ++ if (block->num_bytes < 5) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Unexpected VESA vendor block size\n", + connector->base.id, connector->name); + return; + } + +- switch (FIELD_GET(DISPLAYID_VESA_MSO_MODE, vesa->mso)) { +- default: +- drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved MSO mode value\n", ++ dp_type = FIELD_GET(DISPLAYID_VESA_DP_TYPE, vesa->data_structure_type); ++ if (dp_type > 1) { ++ drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved dp type value\n", + connector->base.id, connector->name); +- fallthrough; +- case 0: +- info->mso_stream_count = 0; +- break; +- case 1: +- info->mso_stream_count = 2; /* 2 or 4 links */ +- break; +- case 2: +- info->mso_stream_count = 4; /* 4 links */ +- break; + } + +- if (!info->mso_stream_count) { ++ /* MSO is only supported for eDP */ ++ if (dp_type == DISPLAYID_VESA_DP_TYPE_EDP) { ++ switch (FIELD_GET(DISPLAYID_VESA_MSO_MODE, vesa->mso)) { ++ default: ++ drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved MSO mode value\n", ++ connector->base.id, connector->name); ++ fallthrough; ++ case 0: ++ info->mso_stream_count = 0; ++ break; ++ case 1: ++ info->mso_stream_count = 2; /* 2 or 4 links */ ++ break; ++ case 2: ++ info->mso_stream_count = 4; /* 4 links */ ++ break; ++ } ++ } ++ ++ if (info->mso_stream_count) { ++ info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); ++ if (info->mso_pixel_overlap > 8) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", ++ connector->base.id, connector->name, ++ info->mso_pixel_overlap); ++ info->mso_pixel_overlap = 8; ++ } ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", ++ connector->base.id, connector->name, ++ info->mso_stream_count, info->mso_pixel_overlap); ++ } else { + info->mso_pixel_overlap = 0; ++ } ++ ++ if (block->num_bytes < 7) { ++ /* DSC bpp is optional */ + return; + } + +- info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); +- if (info->mso_pixel_overlap > 8) { +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", +- connector->base.id, connector->name, +- info->mso_pixel_overlap); +- info->mso_pixel_overlap = 8; +- } ++ info->dp_dsc_bpp_x16 = FIELD_GET(DISPLAYID_VESA_DSC_BPP_INT, vesa->dsc_bpp_int) << 4 | ++ FIELD_GET(DISPLAYID_VESA_DSC_BPP_FRACT, vesa->dsc_bpp_fract); + +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", +- connector->base.id, connector->name, +- info->mso_stream_count, info->mso_pixel_overlap); ++ if (info->dp_dsc_bpp_x16 > 0) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DSC bits per pixel " FXP_Q4_FMT "\n", ++ connector->base.id, connector->name, ++ FXP_Q4_ARGS(info->dp_dsc_bpp_x16)); ++ } + } + +-static void drm_update_mso(struct drm_connector *connector, +- const struct drm_edid *drm_edid) ++static void drm_update_vesa_specific_block(struct drm_connector *connector, ++ const struct drm_edid *drm_edid) + { + const struct displayid_block *block; + struct displayid_iter iter; +@@ -6752,7 +6776,7 @@ static void drm_update_mso(struct drm_connector *connector, + displayid_iter_edid_begin(drm_edid, &iter); + displayid_iter_for_each(block, &iter) { + if (block->tag == DATA_BLOCK_2_VENDOR_SPECIFIC) +- drm_parse_vesa_mso_data(connector, block); ++ drm_parse_vesa_specific_block(connector, block); + } + displayid_iter_end(&iter); + } +@@ -6789,6 +6813,7 @@ static void drm_reset_display_info(struct drm_connector *connector) + info->mso_stream_count = 0; + info->mso_pixel_overlap = 0; + info->max_dsc_bpp = 0; ++ info->dp_dsc_bpp_x16 = 0; + + kfree(info->vics); + info->vics = NULL; +@@ -6930,7 +6955,7 @@ static void update_display_info(struct drm_connector *connector, + if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB422) + info->color_formats |= BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + +- drm_update_mso(connector, drm_edid); ++ drm_update_vesa_specific_block(connector, drm_edid); + + out: + if (drm_edid_has_internal_quirk(connector, EDID_QUIRK_NON_DESKTOP)) { +@@ -6960,8 +6985,8 @@ static void update_display_info(struct drm_connector *connector, + } + + static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *dev, +- const struct displayid_detailed_timings_1 *timings, +- bool type_7) ++ const struct displayid_block *block, ++ const struct displayid_detailed_timings_1 *timings) + { + struct drm_display_mode *mode; + unsigned int pixel_clock = (timings->pixel_clock[0] | +@@ -6977,11 +7002,16 @@ static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *d + unsigned int vsync_width = le16_to_cpu(timings->vsw) + 1; + bool hsync_positive = le16_to_cpu(timings->hsync) & (1 << 15); + bool vsync_positive = le16_to_cpu(timings->vsync) & (1 << 15); ++ bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; + + mode = drm_mode_create(dev); + if (!mode) + return NULL; + ++ if (type_7 && FIELD_GET(DISPLAYID_BLOCK_REV, block->rev) >= 1) ++ mode->dsc_passthrough_timings_support = ++ block->rev & DISPLAYID_BLOCK_PASSTHROUGH_TIMINGS_SUPPORT; ++ + /* resolution is kHz for type VII, and 10 kHz for type I */ + mode->clock = type_7 ? pixel_clock : pixel_clock * 10; + mode->hdisplay = hactive; +@@ -7014,7 +7044,6 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, + int num_timings; + struct drm_display_mode *newmode; + int num_modes = 0; +- bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; + /* blocks must be multiple of 20 bytes length */ + if (block->num_bytes % 20) + return 0; +@@ -7023,7 +7052,7 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, + for (i = 0; i < num_timings; i++) { + struct displayid_detailed_timings_1 *timings = &det->timings[i]; + +- newmode = drm_mode_displayid_detailed(connector->dev, timings, type_7); ++ newmode = drm_mode_displayid_detailed(connector->dev, block, timings); + if (!newmode) + continue; + +@@ -7070,7 +7099,8 @@ static int add_displayid_formula_modes(struct drm_connector *connector, + struct drm_display_mode *newmode; + int num_modes = 0; + bool type_10 = block->tag == DATA_BLOCK_2_TYPE_10_FORMULA_TIMING; +- int timing_size = 6 + ((formula_block->base.rev & 0x70) >> 4); ++ int timing_size = 6 + ++ FIELD_GET(DISPLAYID_BLOCK_DESCRIPTOR_PAYLOAD_BYTES, formula_block->base.rev); + + /* extended blocks are not supported yet */ + if (timing_size != 6) +diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h +--- a/include/drm/drm_connector.h ++++ b/include/drm/drm_connector.h +@@ -939,6 +939,12 @@ struct drm_display_info { + */ + u32 max_dsc_bpp; + ++ /** ++ * @dp_dsc_bpp: DP Display-Stream-Compression (DSC) timing's target ++ * DSC bits per pixel in 6.4 fixed point format. 0 means undefined. ++ */ ++ u16 dp_dsc_bpp_x16; ++ + /** + * @vics: Array of vics_len VICs. Internal to EDID parsing. + */ +diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h +--- a/include/drm/drm_modes.h ++++ b/include/drm/drm_modes.h +@@ -417,6 +417,16 @@ struct drm_display_mode { + */ + enum hdmi_picture_aspect picture_aspect_ratio; + ++ /** ++ * @dsc_passthrough_timing_support: ++ * ++ * Indicates whether this mode timing descriptor is supported ++ * with specific target DSC bits per pixel only. ++ * ++ * VESA vendor-specific data block shall exist with the relevant ++ * DSC bits per pixel declaration when this flag is set to true. ++ */ ++ bool dsc_passthrough_timings_support; + }; + + /** diff --git a/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch.sig b/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..922eab7bc5d2b75d0e0185c3d80241c3fcf557a8 GIT binary patch literal 594 zcmV-Y0c9I4CRf(mM1B>g>?6ecO9-x-eFk`}U23jnhyFB8QKvdX+*4jr zoUpThHc{A%vGqgI{UoFrSvek93Sfu8Ze~EC9K^PQuzJ642z*5!3tEI~Gt=6R#%a+OGhV!Z literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch b/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch new file mode 100644 index 0000000..b826d02 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch @@ -0,0 +1,51 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7642,6 +7642,7 @@ create_stream_for_sink(struct drm_connector *connector, + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; + struct drm_display_mode *dsc_passthru_mode = NULL; ++ unsigned int dsc_passthru_match_flags; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; +@@ -7708,6 +7709,22 @@ create_stream_for_sink(struct drm_connector *connector, + + mode_refresh = drm_mode_vrefresh(&mode); + ++ dsc_passthru_match_flags = DRM_MODE_MATCH_TIMINGS | ++ DRM_MODE_MATCH_CLOCK | ++ DRM_MODE_MATCH_FLAGS | ++ DRM_MODE_MATCH_3D_FLAGS; ++ if (mode.picture_aspect_ratio) ++ dsc_passthru_match_flags |= DRM_MODE_MATCH_ASPECT_RATIO; ++ ++ list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { ++ if (drm_mode_match(dsc_passthru_mode, &mode, ++ dsc_passthru_match_flags)) { ++ mode.dsc_passthrough_timings_support = ++ dsc_passthru_mode->dsc_passthrough_timings_support; ++ break; ++ } ++ } ++ + if (preferred_mode == NULL) { + /* + * This may not be an error, the use case is when we have no +@@ -7733,16 +7750,6 @@ create_stream_for_sink(struct drm_connector *connector, + } + } + +- list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { +- if (dsc_passthru_mode->hdisplay == mode.hdisplay && +- dsc_passthru_mode->vdisplay == mode.vdisplay && +- drm_mode_vrefresh(dsc_passthru_mode) == mode_refresh) { +- mode.dsc_passthrough_timings_support = +- dsc_passthru_mode->dsc_passthrough_timings_support; +- break; +- } +- } +- + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + diff --git a/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch.sig b/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..cb18d66968f36ab8e3f249a6464ebd8b28b04917 GIT binary patch literal 593 zcmV-X02-(jY~i^F@%qKj{xrzh zA3#iQ0L7$WW3D}KsHFcJo}w(pj#njF3S_%60?6T@!erAeZDXk`eAS?pz?#l6_psqB zQq=@wEEt)24%4-UA8pdWv5Q}2aZCY*g*TAWVC+x1c#GCF%jPV)cTt4vh%Cs5;!v$9 zfl=)Wz#OZM3vFxRkl5D7Mv!_SPYBz(`LLQ-bdZ*WXJMmG^ua#9N>zj%{+%62=ZDL# zX9__YH53jtx{Q3!iBz)|8To{$ie=r8 fOEX;he>qHQbGfQw&en^P4J=&=^I&userq_doorbell_xa, queue_id, queue) { + if (queue->state == AMDGPU_USERQ_STATE_HUNG && !vram_lost) { +@@ -1435,11 +1435,12 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) + r = userq_funcs->map(queue); + if (r) { + dev_err(adev->dev, "Failed to remap queue %ld\n", queue_id); ++ ret = r; + continue; + } + queue->state = AMDGPU_USERQ_STATE_MAPPED; + } + } + +- return r; ++ return ret; + } diff --git a/pkgbuilds/linux-omarchy-muqss/0472-amdgpu-userq-post-reset-error.patch.sig b/pkgbuilds/linux-omarchy-muqss/0472-amdgpu-userq-post-reset-error.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..5de4d8567e8ab14fe1694ace87565ec6cfe9af50 GIT binary patch literal 594 zcmV-Y0M76&YkLq}n!-^vUwrP1 z)(Xh=24E#*jEK}JRwz8z%Y-uCDa@&d<>yhi@TomYK}s_N{AEi9mrT+(Q!cpw(4z3= zrPsP|kNIv+m9X^+RpdEW`N>OH+@itZZ}R@i7BZvljz-3A*$8WVPXgmf%K}NWS&b#K zh#krds1|D};?`WZu-?WSx0(iJgt4Y&TwaDHlLb2y5D3ZlD@BZ!vB=F3bu zJJLq`1wN##Y+T3iJwS{Fw$E#UpHG$PGF2Ei`+=Mv8vD1eD>P2E1-$gX8JWozforce_pwm) + caps->aux_support = false; +- if (caps->aux_support) +- aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; ++ if (caps->aux_support) { ++ if (aconnector->dc_link->dpcd_caps.panel_luminance_control) ++ aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_VESA_AUX; ++ else ++ aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; ++ } + + luminance_range = &conn_base->display_info.luminance_range; + diff --git a/pkgbuilds/linux-omarchy-muqss/0474-amd-display-oled-vesa-backlight.patch.sig b/pkgbuilds/linux-omarchy-muqss/0474-amd-display-oled-vesa-backlight.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f64009c9f88211faca0202b22fa5898b6da8d7af GIT binary patch literal 594 zcmV-Y09st+fm`+|U&l6!g^oAsWOQWRLR!-a=n${5Yl{J{U~_2SwwuL_ z!sPF?_bYL(R>b)Js81d9N1m0U?TVbGD+W_P2uqaaU~V3d3*ABbj&eG}rhJt$^~wJP zEo7qsLMyA)AaPF{XW4PQx9}D825E&9@%0&wtqZ~r`4yVJg=cf3`Ao2qW6SL?=X9vx?#L( z+(#%vA;W3!GBGyQxeKqq{4FVTdJbAAV9(aqrYXQ@ guIk810I+jE8M%Mw*q5Inq;&ymyOJ2-d#`S+VYxFGT>t<8 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch b/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch new file mode 100644 index 0000000..a58a962 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c +--- a/drivers/gpu/drm/i915/display/intel_dp.c ++++ b/drivers/gpu/drm/i915/display/intel_dp.c +@@ -3219,11 +3219,7 @@ static bool intel_dp_needs_as_sdp(struct intel_dp *intel_dp, + if (drm_dp_is_branch(intel_dp->dpcd)) + return false; + +- if (intel_psr_needs_alpm_aux_less(intel_dp, crtc_state) && +- !intel_psr_pr_async_video_timing_supported(intel_dp)) +- return true; +- +- return intel_vrr_possible(crtc_state); ++ return crtc_state->vrr.enable; + } + + static void intel_dp_compute_as_sdp(struct intel_dp *intel_dp, diff --git a/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig b/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..3ddfad0a331d357f85eb04d5b5620e507cb27330 GIT binary patch literal 594 zcmV-Y0;S|8XATGaY^P);}-nEx)o| z7vB}P)Jf1W>XF&+(h{HdUAeIs;lLnH`}ujB8U+xlBn4DxJX^}g#^1p#8s$d>iBSA} ze>~As6(u1PDhfLqu@K$@{-*cwiha`E>j&?cbeC~4yH*(ZTz2|7iGAzh4vRhZ`JTPL zb-cD|l!||0M@B$p%sgZJ{`FkF;s9=Y?FMklr9HKD9#L0YfoK~z{7^X%n?q~=Nw>jJ zfpvpEizC@C3f}8B|Fc?^)|#S(&B%(&P-$!7$!pGK4a3$6Q3E!d9VdEFB9`>_G~5xu zzB*-eQxB!Ka89kc;EI(&8FqO&{<2V*#`3BV^7xqJNUX5T#ybc|v-6`|lpE*!;U&<@ zO;mv-8BmSCT2pr_Fa*>fmv#o{8Cf2DKQI4U+3c_z7)Hf@P0VHTgi+Ga6JDp7_mE~E zvDXVNcv(4i3dZ$0!$$AKUL~g+^2u>Sv4A6W%jKb*rZsn?zPG>DoB);7Op((-FcZ_tZasmMgmxB@kgmmfL4K=}*VBE@IR8xiIgd^k)aD_qT(d9jSxh z$tDg1KDasyCg$YuXSNRi6(~B|D-jQq&~Qj2zWYy!j1dP@gk%fo$WTU3o^}_MNe+0j gcVQ)K4ouwvY)699tu&9VcKTp6A(zkI_k*L3>+q%+761SM literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch b/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch new file mode 100644 index 0000000..8d6c8a4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch @@ -0,0 +1,25 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7020,16 +7020,11 @@ static void fill_stream_properties_from_drm_display_mode( + timing_out->v_border_top = 0; + timing_out->v_border_bottom = 0; + /* TODO: un-hardcode */ +- if (drm_mode_is_420_only(info, mode_in) +- && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || +- stream->signal == SIGNAL_TYPE_HDMI_FRL) +- && aconnector +- && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) +- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; +- else if (drm_mode_is_420_also(info, mode_in) +- && aconnector +- && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 +- || aconnector->force_yuv420_output)) ++ if (drm_mode_is_420_only(info, mode_in) || ++ (aconnector && ++ (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 || ++ aconnector->force_yuv420_output) && ++ drm_mode_is_420_also(info, mode_in))) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) + && aconnector diff --git a/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..2b5da948b29f1b052c263c39db804b81abed822c GIT binary patch literal 594 zcmV-Y0KjySPB}pR+ZBKT z+n@sx=MIW(Qog;K`cZj-*!B^uy=Wt&I`QS*7eobbfi*Vc`3Mm!d^eK g$25d?8~_VfkCJDumNIf3kG}@r(X80xRnf!f(h^A*2><{9 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch b/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch new file mode 100644 index 0000000..1b0eb75 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch @@ -0,0 +1,303 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -3345,7 +3345,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, + if (acrtc && state->stream_status[i].plane_count != 0 && + amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { + irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; +- rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; ++ rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; + if (rc) + drm_warn(adev_to_drm(adev), "Failed to %s pflip interrupts\n", + enable ? "enable" : "disable"); +@@ -3369,7 +3369,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, + /* During gpu-reset we disable and then enable vblank irq, so + * don't use amdgpu_irq_get/put() to avoid refcount change. + */ +- if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) ++ if (!amdgpu_dm_irq_set(adev, irq_source, enable)) + drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); + + } else if (acrtc && state->stream_status[i].plane_count != 0) { +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +@@ -550,6 +550,18 @@ struct amdgpu_display_manager { + struct common_irq_params + vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; + ++ /** ++ * @irq_reg_lock: ++ * ++ * Serializes the read-modify-writes of the HW interrupt control ++ * registers. Several interrupt sources share one register - e.g. the ++ * enable and clear bits of both VSTARTUP (vblank) and VUPDATE_NO_LOCK ++ * live in OTG_GLOBAL_SYNC_STATUS. Therefore, enabling one source must ++ * not race with acking another. Held only across amdgpu_dm_irq_set() ++ * and amdgpu_dm_irq_ack(). ++ */ ++ spinlock_t irq_reg_lock; ++ + /** + * @dmub_trace_params: + * +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +@@ -31,6 +31,7 @@ + #include "amdgpu_dm_psr.h" + #include "amdgpu_dm_replay.h" + #include "amdgpu_dm_crtc.h" ++#include "amdgpu_dm_irq.h" + #include "amdgpu_dm_plane.h" + #include "amdgpu_dm_trace.h" + #include "amdgpu_dm_debugfs.h" +@@ -87,7 +88,7 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) + + irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst; + +- rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; ++ rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; + + DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n", + acrtc->crtc_id, enable ? "en" : "dis", rc); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +@@ -1378,12 +1378,13 @@ void dm_helpers_free_gpu_mem( + + bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable) + { ++ struct amdgpu_device *adev = ctx->driver_context; + enum dc_irq_source irq_source; + bool ret; + + irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + +- ret = dc_interrupt_set(ctx->dc, irq_source, enable); ++ ret = amdgpu_dm_irq_set(adev, irq_source, enable); + + DRM_DEBUG_DRIVER("Dmub trace irq %sabling: r=%d\n", + enable ? "en" : "dis", ret); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +@@ -424,6 +424,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) + DRM_DEBUG_KMS("DM_IRQ\n"); + + spin_lock_init(&adev->dm.irq_handler_list_table_lock); ++ spin_lock_init(&adev->dm.irq_reg_lock); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + /* low context handler list init */ +@@ -496,7 +497,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, false); ++ amdgpu_dm_irq_set(adev, src, false); + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + +@@ -533,7 +534,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_dm_irq_set(adev, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +@@ -558,7 +559,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_dm_irq_set(adev, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +@@ -645,6 +646,21 @@ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + } + ++bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, ++ bool enable) ++{ ++ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); ++ ++ return dc_interrupt_set(adev->dm.dc, src, enable); ++} ++ ++void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src) ++{ ++ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); ++ ++ dc_interrupt_ack(adev->dm.dc, src); ++} ++ + /** + * amdgpu_dm_irq_handler - Generic DM IRQ handler + * @adev: amdgpu base driver device containing the DM device +@@ -665,7 +681,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, + entry->src_id, + entry->src_data[0]); + +- dc_interrupt_ack(adev->dm.dc, src); ++ amdgpu_dm_irq_ack(adev, src); + + /* Call high irq work immediately */ + amdgpu_dm_irq_immediate_work(adev, src); +@@ -703,7 +719,7 @@ static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, + enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, src, st); ++ amdgpu_dm_irq_set(adev, src, st); + return 0; + } + +@@ -737,7 +753,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, + if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) + dc_allow_idle_optimizations(dc, false); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -791,7 +807,7 @@ static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -817,7 +833,7 @@ static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -881,9 +897,7 @@ void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) + } + void amdgpu_dm_outbox_init(struct amdgpu_device *adev) + { +- dc_interrupt_set(adev->dm.dc, +- DC_IRQ_SOURCE_DMCUB_OUTBOX, +- true); ++ amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); + } + + /** +@@ -905,7 +919,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + + /* First, clear all hpd and hpdrx interrupts */ + for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) { +- if (!dc_interrupt_set(adev->dm.dc, i, false)) ++ if (!amdgpu_dm_irq_set(adev, i, false)) + drm_err(dev, "Failed to clear hpd(rx) source=%d on init\n", + i); + } +@@ -934,7 +948,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + * of dm. Note that only hpd interrupt types are registered with + * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on + * hpd_rx isn't available. DM currently controls hpd_rx +- * explicitly with dc_interrupt_set() ++ * explicitly with amdgpu_dm_irq_set() + */ + if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { + irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; +@@ -943,23 +957,21 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + * and what bios reports as the # of connectors with hpd + * sources. Since the # of hpd source types registered + * with base driver == mode_info.num_hpd, we have to +- * fallback to dc_interrupt_set for the remaining types. ++ * fallback to amdgpu_dm_irq_set for the remaining types. + */ + if (irq_type < adev->mode_info.num_hpd) { + if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) + drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", + dc_link->irq_source_hpd); + } else { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd, +- true); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, ++ true); + } + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd_rx, +- true); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, ++ true); + } + } + drm_connector_list_iter_end(&iter); +@@ -1003,16 +1015,14 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) + drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", + dc_link->irq_source_hpd); + } else { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd, +- false); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, ++ false); + } + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd_rx, +- false); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, ++ false); + } + } + drm_connector_list_iter_end(&iter); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +@@ -81,6 +81,34 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, + enum dc_irq_source irq_source, + void *ih_index); + ++/** ++ * amdgpu_dm_irq_set - enable or disable a DC interrupt source. ++ * ++ * @adev: AMD DRM device ++ * @src: DC interrupt source to toggle ++ * @enable: true to enable the source, false to disable it ++ * ++ * DM-wide replacement for dc_interrupt_set(). As locking is DM's ++ * responsibility, this is a thin wrapper serializes the underlying ++ * read-modify-write against the other interrupt sources sharing HW control ++ * registers with @src, so DM must never call dc_interrupt_set() directly. ++ * ++ * Returns: true if the source was toggled. ++ */ ++bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, ++ bool enable); ++ ++/** ++ * amdgpu_dm_irq_ack - acknowledge a DC interrupt source. ++ * ++ * @adev: AMD DRM device ++ * @src: DC interrupt source to acknowledge ++ * ++ * DM-wide replacement for dc_interrupt_ack(), serialized the same way as ++ * amdgpu_dm_irq_set(). ++ */ ++void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src); ++ + void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); + + void amdgpu_dm_outbox_init(struct amdgpu_device *adev); diff --git a/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch.sig b/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..c0f963ebde83d406496b6a635203caa41618b797 GIT binary patch literal 594 zcmV-Y000000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=VWpZ|5hB(6TIfZUp)P9)@qW) zvJ#eXdgJ2?RtFcu)2GEyO3qjif7L=rCiSmkMT&|-POq;)&0Z~Pfl}B>U!9u*^H9N{ znO81o8@-KG*dlBSJTUQ(2GXNd*Ybdbx^W-U2Dg!w{gm4+FUvCg@8v1{nE_tT>F%QJ z?kVxLX40$MM(pl*@zm55r2C|)Lb zO=Q*+I_cM0flqCX)K+CFSr!SbjN&1jNtdy*?DE1{XMFAZnrhyYC?XhMzH@B-m}P5) zT)76B#Wmhrvdp^|i~-iZ-uEJo)gsw~#7v#$?r@8D1mbBz(f!lsirmZxzPn}Mc~0OG gTxbbXK-WqWn{I*amPzzohq|2L-W7zXe}G23!!&9j8UO$Q literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch b/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch new file mode 100644 index 0000000..dcdb282 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -14232,6 +14232,12 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + } + } + ++ /* HDMI/PCON without an EDID FreeSync VCP code is not FreeSync-capable. */ ++ if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && ++ !sink->edid_caps.freesync_vcp_code) ++ freesync_capable = false; ++ + /* Handle MCCS */ + if (do_mccs) { + dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); diff --git a/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig b/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..eed1a60e6676c3447e5c80b11459a3cacaf6dc27 GIT binary patch literal 594 zcmV-Y0A0IE^hK(Ig8TN#Bji?8F( zjqPz$#WVh1EebRnD&(GJ$<`VgN!GUEiiy>I&W|{c?;os zI7!VCE=QUUi^v&+4H4xXK%%xU6Wbc zDtFwL4smfh^oLZ=f`)m0yaOuH^gIk0{S6z#T-0FdJL`$1_PX3m-I@6!%ixlU0gBRKq zqDhr+CU#7uytfKV4h9pKITuF#<@x93*0-kU>YEO`3)o!^8Uzg|i?(_NtFO gKHq+xHrwaLu_NyI@6pF2V)%hXF6(y?Uw-OOrzfNmcmMzZ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch b/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch new file mode 100644 index 0000000..13b9043 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch @@ -0,0 +1,40 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -10135,20 +10135,19 @@ static void update_freesync_state_on_stream( + */ + if (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + new_stream->signal == SIGNAL_TYPE_HDMI_FRL) { +- struct dc_info_packet hf_vsif = {0}; ++ struct dc_info_packet vsp_infopacket = {0}; + bool sink_allm = aconn && aconn->base.display_info.hdmi.allm; + bool allm = sink_allm && + (vrr_params.state == VRR_STATE_ACTIVE_VARIABLE || + vrr_params.state == VRR_STATE_ACTIVE_FIXED); + bool allm_changed; + +- if (allm) +- mod_build_hf_vsif_infopacket(new_stream, &hf_vsif, allm, allm); ++ mod_build_hf_vsif_infopacket(new_stream, &vsp_infopacket, allm, allm); + +- allm_changed = memcmp(&new_stream->hfvsif_infopacket, &hf_vsif, +- sizeof(hf_vsif)) != 0; ++ allm_changed = memcmp(&new_stream->vsp_infopacket, &vsp_infopacket, ++ sizeof(vsp_infopacket)) != 0; + new_crtc_state->freesync_vrr_info_changed |= allm_changed; +- new_stream->hfvsif_infopacket = hf_vsif; ++ new_stream->vsp_infopacket = vsp_infopacket; + + if (allm_changed) + drm_dbg_driver(adev_to_drm(adev), +@@ -10727,8 +10726,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, + if (acrtc_state->freesync_vrr_info_changed) { + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; +- bundle->stream_update.hfvsif_infopacket = +- &acrtc_state->stream->hfvsif_infopacket; ++ bundle->stream_update.vsp_infopacket = ++ &acrtc_state->stream->vsp_infopacket; + } + } + } diff --git a/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig b/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..37a9ec9f88abf04f8dce66f62e53d79cfb67529e GIT binary patch literal 594 zcmV-Y0qOTd<%UbUy(XGHGXV$QK!U*C%Z*?YrPj+_q_kj<;cQ znp*SLsKq4se8Wzbl{R41?fjUx(9_&Sr;=FYX=sKLPGWmF_CoY(qYHjOkg?u6;vgFo zuWWwitO|da@Hl3HM3;09T2UEW*R2X`LjCivW71$G(?Q+vk?1G_YT+h0-^8t`j9nrF zIAC@IDfOLRALy->Uz;m=Cy;@e0y2Qh1%NZD!t z#C8mxM6nl59msS_clCiSOt#X>oDp{TPCXCStD$n*r(@&mEH;BLeH*?B=NL@6tRfhf zo%Q9U)Y9yeEYw~2Q&d1@9q;2Gf)w2=v?SIcMKWyILxu|!)DoGkElZ%mx1**F?juw$ zfRZ;ZZiHre{B)^&@DOG@hdQ`#9?`V70O`C?-J1BZ@iBTIyJUPA%K!iX literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch b/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch new file mode 100644 index 0000000..1368f53 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch @@ -0,0 +1,12141 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -1757,6 +1757,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) + * by the drivers/platform/x86/serial-multi-instantiate.c driver, which + * knows which client device id to use for each resource. + */ ++ {"AWDZ8399", }, + {"BSG1160", }, + {"BSG2150", }, + {"CSC3551", }, +diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c +--- a/drivers/platform/x86/serial-multi-instantiate.c ++++ b/drivers/platform/x86/serial-multi-instantiate.c +@@ -307,6 +307,15 @@ static void smi_remove(struct platform_device *pdev) + smi_devs_unregister(smi); + } + ++static const struct smi_node aw88399_hda = { ++ .instances = { ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ {} ++ }, ++ .bus_type = SMI_AUTO_DETECT, ++}; ++ + static const struct smi_node bsg1160_data = { + .instances = { + { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, +@@ -405,6 +414,7 @@ static const struct smi_node tas2781_hda = { + * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). + */ + static const struct acpi_device_id smi_acpi_ids[] = { ++ { "AWDZ8399", (unsigned long)&aw88399_hda }, + { "BSG1160", (unsigned long)&bsg1160_data }, + { "BSG2150", (unsigned long)&bsg2150_data }, + { "CSC3551", (unsigned long)&cs35l41_hda }, +diff --git a/include/sound/ac97/codec.h b/include/sound/ac97/codec.h +--- a/include/sound/ac97/codec.h ++++ b/include/sound/ac97/codec.h +@@ -33,7 +33,7 @@ struct ac97_id { + }; + + /** +- * ac97_codec_device - a ac97 codec ++ * struct ac97_codec_device - a ac97 codec + * @dev: the core device + * @vendor_id: the vendor_id of the codec, as sensed on the AC-link + * @num: the codec number, 0 is primary, 1 is first slave, etc ... +@@ -53,7 +53,7 @@ struct ac97_codec_device { + }; + + /** +- * ac97_codec_driver - a ac97 codec driver ++ * struct ac97_codec_driver - a ac97 codec driver + * @driver: the device driver structure + * @probe: the function called when a ac97_codec_device is matched + * @remove: the function called when the device is unbound/removed +diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h +--- a/include/sound/asequencer.h ++++ b/include/sound/asequencer.h +@@ -11,9 +11,6 @@ + #include + #include + +-/* helper macro */ +-#define snd_seq_event_bounce_ext_data(ev) ((void*)((char *)(ev)->data.ext.ptr + sizeof(struct snd_seq_event_bounce))) +- + /* + * type check macros + */ +diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h +new file mode 100644 +--- /dev/null ++++ b/include/sound/aw88399.h +@@ -0,0 +1,621 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * linux/sound/aw88399.h -- Platform data for AW88399 ++ * ++ * Copyright (c) 2023 AWINIC Technology CO., LTD ++ * ++ * Author: Weidong Wang ++ */ ++ ++#ifndef __SOUND_AW88399_H ++#define __SOUND_AW88399_H ++ ++#include ++#include ++ ++/* registers list */ ++#define AW88399_ID_REG (0x00) ++#define AW88399_SYSST_REG (0x01) ++#define AW88399_SYSINT_REG (0x02) ++#define AW88399_SYSINTM_REG (0x03) ++#define AW88399_SYSCTRL_REG (0x04) ++#define AW88399_SYSCTRL2_REG (0x05) ++#define AW88399_I2SCTRL1_REG (0x06) ++#define AW88399_I2SCTRL2_REG (0x07) ++#define AW88399_I2SCTRL3_REG (0x08) ++#define AW88399_DACCFG1_REG (0x09) ++#define AW88399_DACCFG2_REG (0x0A) ++#define AW88399_DACCFG3_REG (0x0B) ++#define AW88399_DACCFG4_REG (0x0C) ++#define AW88399_DACCFG5_REG (0x0D) ++#define AW88399_DACCFG6_REG (0x0E) ++#define AW88399_DACCFG7_REG (0x0F) ++#define AW88399_MPDCFG1_REG (0x10) ++#define AW88399_MPDCFG2_REG (0x11) ++#define AW88399_MPDCFG3_REG (0x12) ++#define AW88399_MPDCFG4_REG (0x13) ++#define AW88399_PWMCTRL1_REG (0x14) ++#define AW88399_PWMCTRL2_REG (0x15) ++#define AW88399_PWMCTRL3_REG (0x16) ++#define AW88399_I2SCFG1_REG (0x17) ++#define AW88399_DBGCTRL_REG (0x18) ++#define AW88399_HAGCST_REG (0x20) ++#define AW88399_VBAT_REG (0x21) ++#define AW88399_TEMP_REG (0x22) ++#define AW88399_PVDD_REG (0x23) ++#define AW88399_ISNDAT_REG (0x24) ++#define AW88399_VSNDAT_REG (0x25) ++#define AW88399_I2SINT_REG (0x26) ++#define AW88399_I2SCAPCNT_REG (0x27) ++#define AW88399_ANASTA1_REG (0x28) ++#define AW88399_ANASTA2_REG (0x29) ++#define AW88399_ANASTA3_REG (0x2A) ++#define AW88399_TESTDET_REG (0x2B) ++#define AW88399_DSMCFG1_REG (0x30) ++#define AW88399_DSMCFG2_REG (0x31) ++#define AW88399_DSMCFG3_REG (0x32) ++#define AW88399_DSMCFG4_REG (0x33) ++#define AW88399_DSMCFG5_REG (0x34) ++#define AW88399_DSMCFG6_REG (0x35) ++#define AW88399_DSMCFG7_REG (0x36) ++#define AW88399_DSMCFG8_REG (0x37) ++#define AW88399_TESTIN_REG (0x38) ++#define AW88399_TESTOUT_REG (0x39) ++#define AW88399_MEMTEST_REG (0x3A) ++#define AW88399_VSNCTRL1_REG (0x3B) ++#define AW88399_ISNCTRL1_REG (0x3C) ++#define AW88399_ISNCTRL2_REG (0x3D) ++#define AW88399_DSPMADD_REG (0x40) ++#define AW88399_DSPMDAT_REG (0x41) ++#define AW88399_WDT_REG (0x42) ++#define AW88399_ACR1_REG (0x43) ++#define AW88399_ACR2_REG (0x44) ++#define AW88399_ASR1_REG (0x45) ++#define AW88399_ASR2_REG (0x46) ++#define AW88399_DSPCFG_REG (0x47) ++#define AW88399_ASR3_REG (0x48) ++#define AW88399_ASR4_REG (0x49) ++#define AW88399_DSPVCALB_REG (0x4A) ++#define AW88399_CRCCTRL_REG (0x4B) ++#define AW88399_DSPDBG1_REG (0x4C) ++#define AW88399_DSPDBG2_REG (0x4D) ++#define AW88399_DSPDBG3_REG (0x4E) ++#define AW88399_PLLCTRL1_REG (0x50) ++#define AW88399_PLLCTRL2_REG (0x51) ++#define AW88399_PLLCTRL3_REG (0x52) ++#define AW88399_CDACTRL1_REG (0x53) ++#define AW88399_CDACTRL2_REG (0x54) ++#define AW88399_CDACTRL3_REG (0x55) ++#define AW88399_SADCCTRL1_REG (0x56) ++#define AW88399_SADCCTRL2_REG (0x57) ++#define AW88399_BOPCTRL1_REG (0x58) ++#define AW88399_BOPCTRL2_REG (0x5A) ++#define AW88399_BOPCTRL3_REG (0x5B) ++#define AW88399_BOPCTRL4_REG (0x5C) ++#define AW88399_BOPCTRL5_REG (0x5D) ++#define AW88399_BOPCTRL6_REG (0x5E) ++#define AW88399_BOPCTRL7_REG (0x5F) ++#define AW88399_BSTCTRL1_REG (0x60) ++#define AW88399_BSTCTRL2_REG (0x61) ++#define AW88399_BSTCTRL3_REG (0x62) ++#define AW88399_BSTCTRL4_REG (0x63) ++#define AW88399_BSTCTRL5_REG (0x64) ++#define AW88399_BSTCTRL6_REG (0x65) ++#define AW88399_BSTCTRL7_REG (0x66) ++#define AW88399_BSTCTRL8_REG (0x67) ++#define AW88399_BSTCTRL9_REG (0x68) ++#define AW88399_BSTCTRL10_REG (0x69) ++#define AW88399_CPCTRL_REG (0x6A) ++#define AW88399_EFWH_REG (0x6C) ++#define AW88399_EFWM2_REG (0x6D) ++#define AW88399_EFWM1_REG (0x6E) ++#define AW88399_EFWL_REG (0x6F) ++#define AW88399_TESTCTRL1_REG (0x70) ++#define AW88399_TESTCTRL2_REG (0x71) ++#define AW88399_EFCTRL1_REG (0x72) ++#define AW88399_EFCTRL2_REG (0x73) ++#define AW88399_EFRH4_REG (0x74) ++#define AW88399_EFRH3_REG (0x75) ++#define AW88399_EFRH2_REG (0x76) ++#define AW88399_EFRH1_REG (0x77) ++#define AW88399_EFRL4_REG (0x78) ++#define AW88399_EFRL3_REG (0x79) ++#define AW88399_EFRL2_REG (0x7A) ++#define AW88399_EFRL1_REG (0x7B) ++#define AW88399_TM_REG (0x7C) ++#define AW88399_TM2_REG (0x7D) ++ ++#define AW88399_REG_MAX (0x7E) ++#define AW88399_MUTE_VOL (1023) ++ ++#define AW88399_DSP_CFG_ADDR (0x9B00) ++#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) ++#define AW88399_DSP_FW_ADDR (0x8980) ++#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) ++#define AW88399_DSP_ROM_CHECK_DATA (0x4638) ++ ++#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) ++#define AW88399_CALI_RE_HBITS_SHIFT (16) ++ ++#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) ++#define AW88399_CALI_RE_LBITS_SHIFT (0) ++ ++#define AW88399_I2STXEN_START_BIT (9) ++#define AW88399_I2STXEN_BITS_LEN (1) ++#define AW88399_I2STXEN_MASK \ ++ (~(((1<> (shift)) ++#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) ++#define AW88399_CRC_CHECK_PASS_VAL (0x4) ++ ++#define AW88399_CRC_CFG_BASE_ADDR (0xD80) ++#define AW88399_CRC_FW_BASE_ADDR (0x4C0) ++#define AW88399_ACF_FILE "aw88399_acf.bin" ++#define AW88399_DEV_SYSST_CHECK_MAX (10) ++#define AW88399_CHIP_ID 0x2183 ++ ++#define AW88399_START_RETRIES (5) ++#define AW88399_START_WORK_DELAY_MS (0) ++ ++enum { ++ AW_EF_AND_CHECK = 0, ++ AW_EF_OR_CHECK, ++}; ++ ++enum { ++ AW88399_DEV_VDSEL_DAC = 0, ++ AW88399_DEV_VDSEL_VSENSE = 32, ++}; ++ ++enum { ++ AW88399_DSP_CRC_NA = 0, ++ AW88399_DSP_CRC_OK = 1, ++}; ++ ++enum { ++ AW88399_DSP_FW_UPDATE_OFF = 0, ++ AW88399_DSP_FW_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_FORCE_UPDATE_OFF = 0, ++ AW88399_FORCE_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_1000_US = 1000, ++ AW88399_2000_US = 2000, ++ AW88399_3000_US = 3000, ++ AW88399_4000_US = 4000, ++}; ++ ++enum AW88399_DEV_STATUS { ++ AW88399_DEV_PW_OFF = 0, ++ AW88399_DEV_PW_ON, ++}; ++ ++enum AW88399_DEV_FW_STATUS { ++ AW88399_DEV_FW_FAILED = 0, ++ AW88399_DEV_FW_OK, ++}; ++ ++enum AW88399_DEV_MEMCLK { ++ AW88399_DEV_MEMCLK_OSC = 0, ++ AW88399_DEV_MEMCLK_PLL = 1, ++}; ++ ++enum AW88399_DEV_DSP_CFG { ++ AW88399_DEV_DSP_WORK = 0, ++ AW88399_DEV_DSP_BYPASS = 1, ++}; ++ ++enum { ++ AW88399_NOT_RCV_MODE = 0, ++ AW88399_RCV_MODE = 1, ++}; ++ ++enum { ++ AW88399_SYNC_START = 0, ++ AW88399_ASYNC_START, ++}; ++ ++struct aw_device; ++struct aw_container; ++struct aw_cali_desc; ++struct gpio_desc; ++struct i2c_client; ++struct regmap; ++ ++struct aw88399 { ++ struct aw_device *aw_pa; ++ struct mutex lock; ++ struct gpio_desc *reset_gpio; ++ struct delayed_work start_work; ++ struct regmap *regmap; ++ struct aw_container *aw_cfg; ++ ++ unsigned int check_val; ++ unsigned int crc_init_val; ++ unsigned int vcalb_init_val; ++ unsigned int dither_st; ++ bool bsts_unreliable; ++ bool fw_needs_reload; ++}; ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev); ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable); ++int aw_dev_get_dsp_status(struct aw_device *aw_dev); ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value); ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc); ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name); ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute); ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel); ++void aw88399_hw_reset(struct aw88399 *aw88399); ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap); ++extern const struct regmap_config aw88399_remap_config; ++int aw88399_request_firmware_file(struct aw88399 *aw88399); ++void aw88399_start(struct aw88399 *aw88399, bool sync_start); ++void aw88399_startup_work(struct work_struct *work); ++int aw88399_stop(struct aw_device *aw_dev); ++ ++#endif /* __SOUND_AW88399_H */ +diff --git a/include/sound/control.h b/include/sound/control.h +--- a/include/sound/control.h ++++ b/include/sound/control.h +@@ -7,6 +7,7 @@ + * Copyright (c) by Jaroslav Kysela + */ + ++#include + #include + #include + #include +@@ -167,6 +168,8 @@ snd_ctl_find_id_mixer(struct snd_card *card, const char *name) + + int snd_ctl_create(struct snd_card *card); + ++extern struct rw_semaphore snd_ioctl_rwsem; ++ + int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn); + int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn); + #ifdef CONFIG_COMPAT +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -108,8 +108,8 @@ struct snd_card { + char longname[80]; /* name of this soundcard */ + char irq_descr[32]; /* Interrupt description */ + char mixername[80]; /* mixer name */ +- char components[128]; /* card components delimited with +- space */ ++ char *components; /* card components, space-delimited */ ++ unsigned int components_alloc_size; /* current allocation size of components */ + struct module *module; /* top-level module */ + + void *private_data; /* private data for soundcard */ +diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h +--- a/include/sound/hda_codec.h ++++ b/include/sound/hda_codec.h +@@ -256,6 +256,7 @@ struct hda_codec { + unsigned int link_down_at_suspend:1; /* link down at runtime suspend */ + unsigned int relaxed_resume:1; /* don't resume forcibly for jack */ + unsigned int forced_resume:1; /* forced resume for jack */ ++ unsigned int acomp_requested_resume:1; /* resume requested by acomp */ + unsigned int no_stream_clean_at_suspend:1; /* do not clean streams at suspend */ + unsigned int ctl_dev_id:1; /* old control element id build behaviour */ + unsigned int eld_jack_detect:1; /* Machine jack-detection by ELD */ +diff --git a/include/sound/hda_regmap.h b/include/sound/hda_regmap.h +--- a/include/sound/hda_regmap.h ++++ b/include/sound/hda_regmap.h +@@ -69,11 +69,14 @@ void snd_hdac_regmap_sync(struct hdac_device *codec); + + /** + * snd_hdac_regmap_write - Write a verb with caching ++ * @codec: HD-audio codec base device + * @nid: codec NID +- * @reg: verb to write ++ * @verb: verb to write + * @val: value to write + * + * For writing an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, +@@ -85,13 +88,16 @@ snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_update - Update a verb value with caching ++ * snd_hdac_regmap_update - Update a verb value with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to update + * @mask: bit mask to update + * @val: value to update + * + * For updating an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, +@@ -104,12 +110,15 @@ snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_read - Read a verb with caching ++ * snd_hdac_regmap_read - Read a verb with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to read + * @val: pointer to store the value + * + * For reading an amp value, use snd_hda_regmap_get_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, +@@ -125,12 +134,12 @@ snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -148,13 +157,14 @@ snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the AMP value with a bit mask. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -169,13 +179,12 @@ snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_get_amp_stereo - Read stereo AMP values + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read stereo AMP values. The lower byte is left, the upper byte is right. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +@@ -192,14 +201,15 @@ snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_update_amp_stereo - update the stereo AMP value + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the stereo AMP value with a bit mask. + * The lower byte is left, the upper byte is right. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +diff --git a/include/sound/pcm.h b/include/sound/pcm.h +--- a/include/sound/pcm.h ++++ b/include/sound/pcm.h +@@ -145,61 +145,60 @@ struct snd_pcm_ops { + #define SNDRV_PCM_RATE_8000_768000 (SNDRV_PCM_RATE_8000_384000|\ + SNDRV_PCM_RATE_705600|\ + SNDRV_PCM_RATE_768000) +-#define _SNDRV_PCM_FMTBIT(fmt) (1ULL << (__force int)SNDRV_PCM_FORMAT_##fmt) +-#define SNDRV_PCM_FMTBIT_S8 _SNDRV_PCM_FMTBIT(S8) +-#define SNDRV_PCM_FMTBIT_U8 _SNDRV_PCM_FMTBIT(U8) +-#define SNDRV_PCM_FMTBIT_S16_LE _SNDRV_PCM_FMTBIT(S16_LE) +-#define SNDRV_PCM_FMTBIT_S16_BE _SNDRV_PCM_FMTBIT(S16_BE) +-#define SNDRV_PCM_FMTBIT_U16_LE _SNDRV_PCM_FMTBIT(U16_LE) +-#define SNDRV_PCM_FMTBIT_U16_BE _SNDRV_PCM_FMTBIT(U16_BE) +-#define SNDRV_PCM_FMTBIT_S24_LE _SNDRV_PCM_FMTBIT(S24_LE) +-#define SNDRV_PCM_FMTBIT_S24_BE _SNDRV_PCM_FMTBIT(S24_BE) +-#define SNDRV_PCM_FMTBIT_U24_LE _SNDRV_PCM_FMTBIT(U24_LE) +-#define SNDRV_PCM_FMTBIT_U24_BE _SNDRV_PCM_FMTBIT(U24_BE) ++#define SNDRV_PCM_FMTBIT_S8 (1ULL << SNDRV_PCM_FORMAT_S8) ++#define SNDRV_PCM_FMTBIT_U8 (1ULL << SNDRV_PCM_FORMAT_U8) ++#define SNDRV_PCM_FMTBIT_S16_LE (1ULL << SNDRV_PCM_FORMAT_S16_LE) ++#define SNDRV_PCM_FMTBIT_S16_BE (1ULL << SNDRV_PCM_FORMAT_S16_BE) ++#define SNDRV_PCM_FMTBIT_U16_LE (1ULL << SNDRV_PCM_FORMAT_U16_LE) ++#define SNDRV_PCM_FMTBIT_U16_BE (1ULL << SNDRV_PCM_FORMAT_U16_BE) ++#define SNDRV_PCM_FMTBIT_S24_LE (1ULL << SNDRV_PCM_FORMAT_S24_LE) ++#define SNDRV_PCM_FMTBIT_S24_BE (1ULL << SNDRV_PCM_FORMAT_S24_BE) ++#define SNDRV_PCM_FMTBIT_U24_LE (1ULL << SNDRV_PCM_FORMAT_U24_LE) ++#define SNDRV_PCM_FMTBIT_U24_BE (1ULL << SNDRV_PCM_FORMAT_U24_BE) + // For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + // available bit count in most significant bit. It's for the case of so-called 'left-justified' or + // `right-padding` sample which has less width than 32 bit. +-#define SNDRV_PCM_FMTBIT_S32_LE _SNDRV_PCM_FMTBIT(S32_LE) +-#define SNDRV_PCM_FMTBIT_S32_BE _SNDRV_PCM_FMTBIT(S32_BE) +-#define SNDRV_PCM_FMTBIT_U32_LE _SNDRV_PCM_FMTBIT(U32_LE) +-#define SNDRV_PCM_FMTBIT_U32_BE _SNDRV_PCM_FMTBIT(U32_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT_LE _SNDRV_PCM_FMTBIT(FLOAT_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT_BE _SNDRV_PCM_FMTBIT(FLOAT_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_LE _SNDRV_PCM_FMTBIT(FLOAT64_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_BE _SNDRV_PCM_FMTBIT(FLOAT64_BE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_LE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_BE) +-#define SNDRV_PCM_FMTBIT_MU_LAW _SNDRV_PCM_FMTBIT(MU_LAW) +-#define SNDRV_PCM_FMTBIT_A_LAW _SNDRV_PCM_FMTBIT(A_LAW) +-#define SNDRV_PCM_FMTBIT_IMA_ADPCM _SNDRV_PCM_FMTBIT(IMA_ADPCM) +-#define SNDRV_PCM_FMTBIT_MPEG _SNDRV_PCM_FMTBIT(MPEG) +-#define SNDRV_PCM_FMTBIT_GSM _SNDRV_PCM_FMTBIT(GSM) +-#define SNDRV_PCM_FMTBIT_S20_LE _SNDRV_PCM_FMTBIT(S20_LE) +-#define SNDRV_PCM_FMTBIT_U20_LE _SNDRV_PCM_FMTBIT(U20_LE) +-#define SNDRV_PCM_FMTBIT_S20_BE _SNDRV_PCM_FMTBIT(S20_BE) +-#define SNDRV_PCM_FMTBIT_U20_BE _SNDRV_PCM_FMTBIT(U20_BE) +-#define SNDRV_PCM_FMTBIT_SPECIAL _SNDRV_PCM_FMTBIT(SPECIAL) +-#define SNDRV_PCM_FMTBIT_S24_3LE _SNDRV_PCM_FMTBIT(S24_3LE) +-#define SNDRV_PCM_FMTBIT_U24_3LE _SNDRV_PCM_FMTBIT(U24_3LE) +-#define SNDRV_PCM_FMTBIT_S24_3BE _SNDRV_PCM_FMTBIT(S24_3BE) +-#define SNDRV_PCM_FMTBIT_U24_3BE _SNDRV_PCM_FMTBIT(U24_3BE) +-#define SNDRV_PCM_FMTBIT_S20_3LE _SNDRV_PCM_FMTBIT(S20_3LE) +-#define SNDRV_PCM_FMTBIT_U20_3LE _SNDRV_PCM_FMTBIT(U20_3LE) +-#define SNDRV_PCM_FMTBIT_S20_3BE _SNDRV_PCM_FMTBIT(S20_3BE) +-#define SNDRV_PCM_FMTBIT_U20_3BE _SNDRV_PCM_FMTBIT(U20_3BE) +-#define SNDRV_PCM_FMTBIT_S18_3LE _SNDRV_PCM_FMTBIT(S18_3LE) +-#define SNDRV_PCM_FMTBIT_U18_3LE _SNDRV_PCM_FMTBIT(U18_3LE) +-#define SNDRV_PCM_FMTBIT_S18_3BE _SNDRV_PCM_FMTBIT(S18_3BE) +-#define SNDRV_PCM_FMTBIT_U18_3BE _SNDRV_PCM_FMTBIT(U18_3BE) +-#define SNDRV_PCM_FMTBIT_G723_24 _SNDRV_PCM_FMTBIT(G723_24) +-#define SNDRV_PCM_FMTBIT_G723_24_1B _SNDRV_PCM_FMTBIT(G723_24_1B) +-#define SNDRV_PCM_FMTBIT_G723_40 _SNDRV_PCM_FMTBIT(G723_40) +-#define SNDRV_PCM_FMTBIT_G723_40_1B _SNDRV_PCM_FMTBIT(G723_40_1B) +-#define SNDRV_PCM_FMTBIT_DSD_U8 _SNDRV_PCM_FMTBIT(DSD_U8) +-#define SNDRV_PCM_FMTBIT_DSD_U16_LE _SNDRV_PCM_FMTBIT(DSD_U16_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_LE _SNDRV_PCM_FMTBIT(DSD_U32_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U16_BE _SNDRV_PCM_FMTBIT(DSD_U16_BE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_BE _SNDRV_PCM_FMTBIT(DSD_U32_BE) ++#define SNDRV_PCM_FMTBIT_S32_LE (1ULL << SNDRV_PCM_FORMAT_S32_LE) ++#define SNDRV_PCM_FMTBIT_S32_BE (1ULL << SNDRV_PCM_FORMAT_S32_BE) ++#define SNDRV_PCM_FMTBIT_U32_LE (1ULL << SNDRV_PCM_FORMAT_U32_LE) ++#define SNDRV_PCM_FMTBIT_U32_BE (1ULL << SNDRV_PCM_FORMAT_U32_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_BE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE) ++#define SNDRV_PCM_FMTBIT_MU_LAW (1ULL << SNDRV_PCM_FORMAT_MU_LAW) ++#define SNDRV_PCM_FMTBIT_A_LAW (1ULL << SNDRV_PCM_FORMAT_A_LAW) ++#define SNDRV_PCM_FMTBIT_IMA_ADPCM (1ULL << SNDRV_PCM_FORMAT_IMA_ADPCM) ++#define SNDRV_PCM_FMTBIT_MPEG (1ULL << SNDRV_PCM_FORMAT_MPEG) ++#define SNDRV_PCM_FMTBIT_GSM (1ULL << SNDRV_PCM_FORMAT_GSM) ++#define SNDRV_PCM_FMTBIT_S20_LE (1ULL << SNDRV_PCM_FORMAT_S20_LE) ++#define SNDRV_PCM_FMTBIT_U20_LE (1ULL << SNDRV_PCM_FORMAT_U20_LE) ++#define SNDRV_PCM_FMTBIT_S20_BE (1ULL << SNDRV_PCM_FORMAT_S20_BE) ++#define SNDRV_PCM_FMTBIT_U20_BE (1ULL << SNDRV_PCM_FORMAT_U20_BE) ++#define SNDRV_PCM_FMTBIT_SPECIAL (1ULL << SNDRV_PCM_FORMAT_SPECIAL) ++#define SNDRV_PCM_FMTBIT_S24_3LE (1ULL << SNDRV_PCM_FORMAT_S24_3LE) ++#define SNDRV_PCM_FMTBIT_U24_3LE (1ULL << SNDRV_PCM_FORMAT_U24_3LE) ++#define SNDRV_PCM_FMTBIT_S24_3BE (1ULL << SNDRV_PCM_FORMAT_S24_3BE) ++#define SNDRV_PCM_FMTBIT_U24_3BE (1ULL << SNDRV_PCM_FORMAT_U24_3BE) ++#define SNDRV_PCM_FMTBIT_S20_3LE (1ULL << SNDRV_PCM_FORMAT_S20_3LE) ++#define SNDRV_PCM_FMTBIT_U20_3LE (1ULL << SNDRV_PCM_FORMAT_U20_3LE) ++#define SNDRV_PCM_FMTBIT_S20_3BE (1ULL << SNDRV_PCM_FORMAT_S20_3BE) ++#define SNDRV_PCM_FMTBIT_U20_3BE (1ULL << SNDRV_PCM_FORMAT_U20_3BE) ++#define SNDRV_PCM_FMTBIT_S18_3LE (1ULL << SNDRV_PCM_FORMAT_S18_3LE) ++#define SNDRV_PCM_FMTBIT_U18_3LE (1ULL << SNDRV_PCM_FORMAT_U18_3LE) ++#define SNDRV_PCM_FMTBIT_S18_3BE (1ULL << SNDRV_PCM_FORMAT_S18_3BE) ++#define SNDRV_PCM_FMTBIT_U18_3BE (1ULL << SNDRV_PCM_FORMAT_U18_3BE) ++#define SNDRV_PCM_FMTBIT_G723_24 (1ULL << SNDRV_PCM_FORMAT_G723_24) ++#define SNDRV_PCM_FMTBIT_G723_24_1B (1ULL << SNDRV_PCM_FORMAT_G723_24_1B) ++#define SNDRV_PCM_FMTBIT_G723_40 (1ULL << SNDRV_PCM_FORMAT_G723_40) ++#define SNDRV_PCM_FMTBIT_G723_40_1B (1ULL << SNDRV_PCM_FORMAT_G723_40_1B) ++#define SNDRV_PCM_FMTBIT_DSD_U8 (1ULL << SNDRV_PCM_FORMAT_DSD_U8) ++#define SNDRV_PCM_FMTBIT_DSD_U16_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U16_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_BE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_BE) + + #ifdef SNDRV_LITTLE_ENDIAN + #define SNDRV_PCM_FMTBIT_S16 SNDRV_PCM_FMTBIT_S16_LE +@@ -228,11 +227,10 @@ struct snd_pcm_ops { + #define SNDRV_PCM_FMTBIT_U20 SNDRV_PCM_FMTBIT_U20_BE + #endif + +-#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT((__force int)SNDRV_PCM_SUBFORMAT_##fmt) +-#define SNDRV_PCM_SUBFMTBIT_STD _SNDRV_PCM_SUBFMTBIT(STD) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX _SNDRV_PCM_SUBFMTBIT(MSBITS_MAX) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 _SNDRV_PCM_SUBFMTBIT(MSBITS_20) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 _SNDRV_PCM_SUBFMTBIT(MSBITS_24) ++#define SNDRV_PCM_SUBFMTBIT_STD (1U << SNDRV_PCM_SUBFORMAT_STD) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX (1U << SNDRV_PCM_SUBFORMAT_MSBITS_MAX) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_20) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_24) + + struct snd_pcm_file { + struct snd_pcm_substream *substream; +@@ -1515,7 +1513,7 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, + */ + static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + { +- return 1ULL << (__force int) pcm_format; ++ return 1ULL << pcm_format; + } + + /** +@@ -1523,9 +1521,7 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + * @f: the iterator variable in snd_pcm_format_t type + */ + #define pcm_for_each_format(f) \ +- for ((f) = SNDRV_PCM_FORMAT_FIRST; \ +- (__force int)(f) <= (__force int)SNDRV_PCM_FORMAT_LAST; \ +- (f) = (__force snd_pcm_format_t)((__force int)(f) + 1)) ++ for ((f) = SNDRV_PCM_FORMAT_FIRST; (f) <= SNDRV_PCM_FORMAT_LAST; (f)++) + + /* printk helpers */ + #define pcm_err(pcm, fmt, args...) \ +diff --git a/include/sound/pcm_params.h b/include/sound/pcm_params.h +--- a/include/sound/pcm_params.h ++++ b/include/sound/pcm_params.h +@@ -71,7 +71,7 @@ static inline void snd_mask_set(struct snd_mask *mask, unsigned int val) + static inline void snd_mask_set_format(struct snd_mask *mask, + snd_pcm_format_t format) + { +- snd_mask_set(mask, (__force unsigned int)format); ++ snd_mask_set(mask, format); + } + + static inline void snd_mask_reset(struct snd_mask *mask, unsigned int val) +@@ -132,7 +132,7 @@ static inline int snd_mask_test(const struct snd_mask *mask, unsigned int val) + static inline int snd_mask_test_format(const struct snd_mask *mask, + snd_pcm_format_t format) + { +- return snd_mask_test(mask, (__force unsigned int)format); ++ return snd_mask_test(mask, format); + } + + static inline int snd_mask_single(const struct snd_mask *mask) +@@ -302,8 +302,7 @@ static inline int snd_interval_eq(const struct snd_interval *i1, const struct sn + */ + static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_access_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_ACCESS)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_ACCESS)); + } + + /** +@@ -312,8 +311,7 @@ static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + */ + static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_format_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_FORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_FORMAT)); + } + + /** +@@ -323,8 +321,7 @@ static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + static inline snd_pcm_subformat_t + params_subformat(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_subformat_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_SUBFORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_SUBFORMAT)); + } + + /** +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -176,8 +176,9 @@ int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream); + /* main midi functions */ + + int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info); +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); +@@ -191,6 +192,15 @@ long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream, + long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream, + const unsigned char *buf, long count); + ++/* non-nested version */ ++static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, ++ int subdevice, ++ int mode, ++ struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/include/sound/seq_virmidi.h b/include/sound/seq_virmidi.h +--- a/include/sound/seq_virmidi.h ++++ b/include/sound/seq_virmidi.h +@@ -46,7 +46,6 @@ struct snd_virmidi_dev { + int client; /* created/attached client */ + int port; /* created/attached port */ + unsigned int flags; /* SNDRV_VIRMIDI_* */ +- rwlock_t filelist_lock; + struct rw_semaphore filelist_sem; + struct list_head filelist; + }; +diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h +--- a/include/uapi/sound/asequencer.h ++++ b/include/uapi/sound/asequencer.h +@@ -308,16 +308,6 @@ struct snd_seq_ump_event { + }; + }; + +-/* +- * bounce event - stored as variable size data +- */ +-struct snd_seq_event_bounce { +- int err; +- struct snd_seq_event event; +- /* external data follows here. */ +-}; +- +- + /* system information */ + struct snd_seq_system_info { + int queues; /* maximum queues count */ +@@ -348,10 +338,10 @@ struct snd_seq_running_info { + + + /* client types */ +-typedef int __bitwise snd_seq_client_type_t; +-#define NO_CLIENT ((__force snd_seq_client_type_t) 0) +-#define USER_CLIENT ((__force snd_seq_client_type_t) 1) +-#define KERNEL_CLIENT ((__force snd_seq_client_type_t) 2) ++typedef int snd_seq_client_type_t; ++#define NO_CLIENT 0 ++#define USER_CLIENT 1 ++#define KERNEL_CLIENT 2 + + /* event filter flags */ + #define SNDRV_SEQ_FILTER_BROADCAST (1U<<0) /* accept broadcast messages */ +diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h +--- a/include/uapi/sound/asound.h ++++ b/include/uapi/sound/asound.h +@@ -169,72 +169,72 @@ enum { + SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, + }; + +-typedef int __bitwise snd_pcm_access_t; +-#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */ +-#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */ +-#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */ ++typedef int snd_pcm_access_t; ++#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED 0 /* interleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED 1 /* noninterleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_COMPLEX 2 /* complex mmap */ ++#define SNDRV_PCM_ACCESS_RW_INTERLEAVED 3 /* readi/writei */ ++#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED 4 /* readn/writen */ + #define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED + +-typedef int __bitwise snd_pcm_format_t; +-#define SNDRV_PCM_FORMAT_S8 ((__force snd_pcm_format_t) 0) +-#define SNDRV_PCM_FORMAT_U8 ((__force snd_pcm_format_t) 1) +-#define SNDRV_PCM_FORMAT_S16_LE ((__force snd_pcm_format_t) 2) +-#define SNDRV_PCM_FORMAT_S16_BE ((__force snd_pcm_format_t) 3) +-#define SNDRV_PCM_FORMAT_U16_LE ((__force snd_pcm_format_t) 4) +-#define SNDRV_PCM_FORMAT_U16_BE ((__force snd_pcm_format_t) 5) +-#define SNDRV_PCM_FORMAT_S24_LE ((__force snd_pcm_format_t) 6) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_S24_BE ((__force snd_pcm_format_t) 7) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_LE ((__force snd_pcm_format_t) 8) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_BE ((__force snd_pcm_format_t) 9) /* low three bytes */ ++typedef int snd_pcm_format_t; ++#define SNDRV_PCM_FORMAT_S8 0 ++#define SNDRV_PCM_FORMAT_U8 1 ++#define SNDRV_PCM_FORMAT_S16_LE 2 ++#define SNDRV_PCM_FORMAT_S16_BE 3 ++#define SNDRV_PCM_FORMAT_U16_LE 4 ++#define SNDRV_PCM_FORMAT_U16_BE 5 ++#define SNDRV_PCM_FORMAT_S24_LE 6 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_S24_BE 7 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_LE 8 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_BE 9 /* low three bytes */ + /* + * For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + * available bit count in most significant bit. It's for the case of so-called 'left-justified' or + * `right-padding` sample which has less width than 32 bit. + */ +-#define SNDRV_PCM_FORMAT_S32_LE ((__force snd_pcm_format_t) 10) +-#define SNDRV_PCM_FORMAT_S32_BE ((__force snd_pcm_format_t) 11) +-#define SNDRV_PCM_FORMAT_U32_LE ((__force snd_pcm_format_t) 12) +-#define SNDRV_PCM_FORMAT_U32_BE ((__force snd_pcm_format_t) 13) +-#define SNDRV_PCM_FORMAT_FLOAT_LE ((__force snd_pcm_format_t) 14) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT_BE ((__force snd_pcm_format_t) 15) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_LE ((__force snd_pcm_format_t) 16) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_BE ((__force snd_pcm_format_t) 17) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((__force snd_pcm_format_t) 18) /* IEC-958 subframe, Little Endian */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((__force snd_pcm_format_t) 19) /* IEC-958 subframe, Big Endian */ +-#define SNDRV_PCM_FORMAT_MU_LAW ((__force snd_pcm_format_t) 20) +-#define SNDRV_PCM_FORMAT_A_LAW ((__force snd_pcm_format_t) 21) +-#define SNDRV_PCM_FORMAT_IMA_ADPCM ((__force snd_pcm_format_t) 22) +-#define SNDRV_PCM_FORMAT_MPEG ((__force snd_pcm_format_t) 23) +-#define SNDRV_PCM_FORMAT_GSM ((__force snd_pcm_format_t) 24) +-#define SNDRV_PCM_FORMAT_S20_LE ((__force snd_pcm_format_t) 25) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_S20_BE ((__force snd_pcm_format_t) 26) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_LE ((__force snd_pcm_format_t) 27) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_BE ((__force snd_pcm_format_t) 28) /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S32_LE 10 ++#define SNDRV_PCM_FORMAT_S32_BE 11 ++#define SNDRV_PCM_FORMAT_U32_LE 12 ++#define SNDRV_PCM_FORMAT_U32_BE 13 ++#define SNDRV_PCM_FORMAT_FLOAT_LE 14 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT_BE 15 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_LE 16 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_BE 17 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE 18 /* IEC-958 subframe, Little Endian */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE 19 /* IEC-958 subframe, Big Endian */ ++#define SNDRV_PCM_FORMAT_MU_LAW 20 ++#define SNDRV_PCM_FORMAT_A_LAW 21 ++#define SNDRV_PCM_FORMAT_IMA_ADPCM 22 ++#define SNDRV_PCM_FORMAT_MPEG 23 ++#define SNDRV_PCM_FORMAT_GSM 24 ++#define SNDRV_PCM_FORMAT_S20_LE 25 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S20_BE 26 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_LE 27 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_BE 28 /* in four bytes, LSB justified */ + /* gap in the numbering for a future standard linear format */ +-#define SNDRV_PCM_FORMAT_SPECIAL ((__force snd_pcm_format_t) 31) +-#define SNDRV_PCM_FORMAT_S24_3LE ((__force snd_pcm_format_t) 32) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S24_3BE ((__force snd_pcm_format_t) 33) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3LE ((__force snd_pcm_format_t) 34) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3BE ((__force snd_pcm_format_t) 35) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3LE ((__force snd_pcm_format_t) 36) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3BE ((__force snd_pcm_format_t) 37) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3LE ((__force snd_pcm_format_t) 38) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3BE ((__force snd_pcm_format_t) 39) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3LE ((__force snd_pcm_format_t) 40) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3BE ((__force snd_pcm_format_t) 41) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3LE ((__force snd_pcm_format_t) 42) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3BE ((__force snd_pcm_format_t) 43) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_G723_24 ((__force snd_pcm_format_t) 44) /* 8 samples in 3 bytes */ +-#define SNDRV_PCM_FORMAT_G723_24_1B ((__force snd_pcm_format_t) 45) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_G723_40 ((__force snd_pcm_format_t) 46) /* 8 Samples in 5 bytes */ +-#define SNDRV_PCM_FORMAT_G723_40_1B ((__force snd_pcm_format_t) 47) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_DSD_U8 ((__force snd_pcm_format_t) 48) /* DSD, 1-byte samples DSD (x8) */ +-#define SNDRV_PCM_FORMAT_DSD_U16_LE ((__force snd_pcm_format_t) 49) /* DSD, 2-byte samples DSD (x16), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_LE ((__force snd_pcm_format_t) 50) /* DSD, 4-byte samples DSD (x32), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U16_BE ((__force snd_pcm_format_t) 51) /* DSD, 2-byte samples DSD (x16), big endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_BE ((__force snd_pcm_format_t) 52) /* DSD, 4-byte samples DSD (x32), big endian */ ++#define SNDRV_PCM_FORMAT_SPECIAL 31 ++#define SNDRV_PCM_FORMAT_S24_3LE 32 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S24_3BE 33 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3LE 34 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3BE 35 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3LE 36 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3BE 37 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3LE 38 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3BE 39 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3LE 40 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3BE 41 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3LE 42 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3BE 43 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_G723_24 44 /* 8 samples in 3 bytes */ ++#define SNDRV_PCM_FORMAT_G723_24_1B 45 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_G723_40 46 /* 8 Samples in 5 bytes */ ++#define SNDRV_PCM_FORMAT_G723_40_1B 47 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_DSD_U8 48 /* DSD, 1-byte samples DSD (x8) */ ++#define SNDRV_PCM_FORMAT_DSD_U16_LE 49 /* DSD, 2-byte samples DSD (x16), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_LE 50 /* DSD, 4-byte samples DSD (x32), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U16_BE 51 /* DSD, 2-byte samples DSD (x16), big endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_BE 52 /* DSD, 4-byte samples DSD (x32), big endian */ + #define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE + #define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 + +@@ -265,11 +265,11 @@ typedef int __bitwise snd_pcm_format_t; + #define SNDRV_PCM_FORMAT_U20 SNDRV_PCM_FORMAT_U20_BE + #endif + +-typedef int __bitwise snd_pcm_subformat_t; +-#define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) ++typedef int snd_pcm_subformat_t; ++#define SNDRV_PCM_SUBFORMAT_STD 0 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX 1 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_20 2 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_24 3 + #define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 + + #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ +@@ -303,16 +303,16 @@ typedef int __bitwise snd_pcm_subformat_t; + #define __SND_STRUCT_TIME64 + #endif + +-typedef int __bitwise snd_pcm_state_t; +-#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */ +-#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */ +-#define SNDRV_PCM_STATE_PREPARED ((__force snd_pcm_state_t) 2) /* stream is ready to start */ +-#define SNDRV_PCM_STATE_RUNNING ((__force snd_pcm_state_t) 3) /* stream is running */ +-#define SNDRV_PCM_STATE_XRUN ((__force snd_pcm_state_t) 4) /* stream reached an xrun */ +-#define SNDRV_PCM_STATE_DRAINING ((__force snd_pcm_state_t) 5) /* stream is draining */ +-#define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ +-#define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ +-#define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ ++typedef int snd_pcm_state_t; ++#define SNDRV_PCM_STATE_OPEN 0 /* stream is open */ ++#define SNDRV_PCM_STATE_SETUP 1 /* stream has a setup */ ++#define SNDRV_PCM_STATE_PREPARED 2 /* stream is ready to start */ ++#define SNDRV_PCM_STATE_RUNNING 3 /* stream is running */ ++#define SNDRV_PCM_STATE_XRUN 4 /* stream reached an xrun */ ++#define SNDRV_PCM_STATE_DRAINING 5 /* stream is draining */ ++#define SNDRV_PCM_STATE_PAUSED 6 /* stream is paused */ ++#define SNDRV_PCM_STATE_SUSPENDED 7 /* hardware is suspended */ ++#define SNDRV_PCM_STATE_DISCONNECTED 8 /* hardware is disconnected */ + #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED + + enum { +@@ -1058,7 +1058,7 @@ struct snd_timer_tread { + * * + ****************************************************************************/ + +-#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) ++#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) + + struct snd_ctl_card_info { + int card; /* card number */ +@@ -1072,24 +1072,43 @@ struct snd_ctl_card_info { + unsigned char components[128]; /* card components / fine identification, delimited with one space (AC97 etc..) */ + }; + +-typedef int __bitwise snd_ctl_elem_type_t; +-#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */ +-#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */ +-#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */ +-#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */ +-#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */ ++/* ++ * Card components can exceed the fixed 128 bytes in snd_ctl_card_info. ++ * Use SNDRV_CTL_IOCTL_CARD_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS ++ * to retrieve the full string. ++ */ ++ ++/* Type values for struct snd_ctl_card_bytes::type */ ++enum { ++ SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */ ++}; ++ ++struct snd_ctl_card_bytes { ++ __u32 type; /* SND_CTL_CARD_BTYPE_* */ ++ __u32 data_allocated; /* size of @data buffer in bytes */ ++ __u32 data_len; /* in/out: actual data length in bytes */ ++ __u32 reserved; /* explicit pad */ ++ __u64 data; /* user buffer (pointer stored as __u64) */ ++}; ++ ++typedef int snd_ctl_elem_type_t; ++#define SNDRV_CTL_ELEM_TYPE_NONE 0 /* invalid */ ++#define SNDRV_CTL_ELEM_TYPE_BOOLEAN 1 /* boolean type */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER 2 /* integer type */ ++#define SNDRV_CTL_ELEM_TYPE_ENUMERATED 3 /* enumerated type */ ++#define SNDRV_CTL_ELEM_TYPE_BYTES 4 /* byte array */ ++#define SNDRV_CTL_ELEM_TYPE_IEC958 5 /* IEC958 (S/PDIF) setup */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER64 6 /* 64-bit integer type */ + #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64 + +-typedef int __bitwise snd_ctl_elem_iface_t; +-#define SNDRV_CTL_ELEM_IFACE_CARD ((__force snd_ctl_elem_iface_t) 0) /* global control */ +-#define SNDRV_CTL_ELEM_IFACE_HWDEP ((__force snd_ctl_elem_iface_t) 1) /* hardware dependent device */ +-#define SNDRV_CTL_ELEM_IFACE_MIXER ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */ +-#define SNDRV_CTL_ELEM_IFACE_PCM ((__force snd_ctl_elem_iface_t) 3) /* PCM device */ +-#define SNDRV_CTL_ELEM_IFACE_RAWMIDI ((__force snd_ctl_elem_iface_t) 4) /* RawMidi device */ +-#define SNDRV_CTL_ELEM_IFACE_TIMER ((__force snd_ctl_elem_iface_t) 5) /* timer device */ +-#define SNDRV_CTL_ELEM_IFACE_SEQUENCER ((__force snd_ctl_elem_iface_t) 6) /* sequencer client */ ++typedef int snd_ctl_elem_iface_t; ++#define SNDRV_CTL_ELEM_IFACE_CARD 0 /* global control */ ++#define SNDRV_CTL_ELEM_IFACE_HWDEP 1 /* hardware dependent device */ ++#define SNDRV_CTL_ELEM_IFACE_MIXER 2 /* virtual mixer device */ ++#define SNDRV_CTL_ELEM_IFACE_PCM 3 /* PCM device */ ++#define SNDRV_CTL_ELEM_IFACE_RAWMIDI 4 /* RawMidi device */ ++#define SNDRV_CTL_ELEM_IFACE_TIMER 5 /* timer device */ ++#define SNDRV_CTL_ELEM_IFACE_SEQUENCER 6 /* sequencer client */ + #define SNDRV_CTL_ELEM_IFACE_LAST SNDRV_CTL_ELEM_IFACE_SEQUENCER + + #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) +@@ -1198,6 +1217,7 @@ struct snd_ctl_tlv { + + #define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) + #define SNDRV_CTL_IOCTL_CARD_INFO _IOR('U', 0x01, struct snd_ctl_card_info) ++#define SNDRV_CTL_IOCTL_CARD_BYTES _IOWR('U', 0x02, struct snd_ctl_card_bytes) + #define SNDRV_CTL_IOCTL_ELEM_LIST _IOWR('U', 0x10, struct snd_ctl_elem_list) + #define SNDRV_CTL_IOCTL_ELEM_INFO _IOWR('U', 0x11, struct snd_ctl_elem_info) + #define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value) +diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h +--- a/include/uapi/sound/firewire.h ++++ b/include/uapi/sound/firewire.h +@@ -79,11 +79,12 @@ struct snd_firewire_event_motu_register_dsp_change { + * + * @type: Fixed to SNDRV_FIREWIRE_EVENT_FF400_MESSAGE. + * @message_count: The number of messages. ++ * @messages: Array of @message_count messages + * @messages.message: The messages expressing hardware knob operation. + * @messages.tstamp: The isochronous cycle at which the request subaction of asynchronous +- * transaction was sent to deliver the message. It has 16 bit unsigned integer ++ * transaction was sent to deliver the message. It has 16-bit unsigned integer + * value. The higher 3 bits of value expresses the lower three bits of second +- * field in the format of CYCLE_TIME, up to 7. The rest 13 bits expresses cycle ++ * field in the format of CYCLE_TIME, up to 7. The remaining 13 bits express cycle + * field up to 7999. + * + * The structure expresses message transmitted by Fireface 400 when operating hardware knob. +@@ -191,8 +192,9 @@ struct snd_firewire_motu_register_dsp_meter { + #define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT (SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT + 2) + + /** +- * snd_firewire_motu_register_dsp_parameter - the container for parameters of DSP controlled +- * by register access. ++ * struct snd_firewire_motu_register_dsp_parameter - the container for parameters ++ * of DSP controlled by register access. ++ * @mixer: aggregate of @mixer.source and @mixer.output + * @mixer.source.gain: The gain of source to mixer. + * @mixer.source.pan: The L/R balance of source to mixer. + * @mixer.source.flag: The flag of source to mixer, including mute, solo. +@@ -200,21 +202,25 @@ struct snd_firewire_motu_register_dsp_meter { + * Audio Express. + * @mixer.source.paired_width: The width of paired source to mixer, only for 4 pre and + * Audio Express. ++ * @mixer.output: FIXME + * @mixer.output.paired_volume: The volume of paired output from mixer. + * @mixer.output.paired_flag: The flag of paired output from mixer. ++ * @output: output parameters + * @output.main_paired_volume: The volume of paired main output. + * @output.hp_paired_volume: The volume of paired hp output. + * @output.hp_paired_assignment: The source assigned to paired hp output. +- * @output.reserved: Padding for 32 bit alignment for future extension. ++ * @output.reserved: Padding for 32-bit alignment for future extension. ++ * @line_input: line input parameters + * @line_input.boost_flag: The flags of boost for line inputs, only for 828mk2 and Traveler. + * @line_input.nominal_level_flag: The flags of nominal level for line inputs, only for 828mk2 and + * Traveler. +- * @line_input.reserved: Padding for 32 bit alignment for future extension. ++ * @line_input.reserved: Padding for 32-bit alignment for future extension. ++ * @input: input parameters + * @input.gain_and_invert: The value including gain and invert for input, only for Ultralite, 4 pre + * and Audio Express. + * @input.flag: The flag of input; e.g. jack detection, phantom power, and pad, only for Ultralite, + * 4 pre and Audio express. +- * @reserved: Padding so that the size of structure is kept to 512 byte, but for future extension. ++ * @reserved: Padding so that the size of structure is kept to 512 bytes, but for future extension. + * + * The structure expresses the set of parameters for DSP controlled by register access. + */ +@@ -272,8 +278,8 @@ struct snd_firewire_motu_register_dsp_parameter { + * controlled by command + * @data: Signal level meters. The mapping between position and signal channel is model-dependent. + * +- * The structure expresses the part of DSP status for hardware meter. The 32 bit storage is +- * estimated to include IEEE 764 32 bit single precision floating point (binary32) value. It is ++ * The structure expresses the part of DSP status for hardware meter. The 32-bit storage is ++ * estimated to include IEEE 764 32-bit single precision floating point (binary32) value. It is + * expected to be linear value (not logarithm) for audio signal level between 0.0 and +1.0. + */ + struct snd_firewire_motu_command_dsp_meter { +diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c +--- a/sound/atmel/ac97c.c ++++ b/sound/atmel/ac97c.c +@@ -693,7 +693,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + struct device *dev = &pdev->dev; + struct snd_card *card; + struct atmel_ac97c *chip; +- struct resource *regs; ++ void __iomem *regs; + struct clk *pclk; + static const struct snd_ac97_bus_ops ops = { + .write = atmel_ac97c_write, +@@ -702,42 +702,34 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + int retval; + int irq; + +- regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- if (!regs) { +- dev_dbg(&pdev->dev, "no memory resource\n"); +- return -ENXIO; +- } ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- dev_dbg(&pdev->dev, "could not get irq: %d\n", irq); ++ if (irq < 0) + return irq; +- } + +- pclk = clk_get(&pdev->dev, "ac97_clk"); ++ pclk = devm_clk_get_enabled(&pdev->dev, "ac97_clk"); + if (IS_ERR(pclk)) { + dev_dbg(&pdev->dev, "no peripheral clock\n"); + return PTR_ERR(pclk); + } +- retval = clk_prepare_enable(pclk); +- if (retval) +- goto err_prepare_enable; + +- retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, ++ retval = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct atmel_ac97c), &card); + if (retval) { + dev_dbg(&pdev->dev, "could not create sound card device\n"); +- goto err_snd_card_new; ++ return retval; + } + + chip = get_chip(card); + +- retval = request_irq(irq, atmel_ac97c_interrupt, 0, "AC97C", chip); +- if (retval) { +- dev_dbg(&pdev->dev, "unable to request irq %d\n", irq); +- goto err_request_irq; +- } ++ retval = devm_request_irq(&pdev->dev, irq, atmel_ac97c_interrupt, 0, "AC97C", chip); ++ if (retval) ++ return retval; ++ + chip->irq = irq; + + spin_lock_init(&chip->lock); +@@ -749,13 +741,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->card = card; + chip->pclk = pclk; + chip->pdev = pdev; +- chip->regs = ioremap(regs->start, resource_size(regs)); +- +- if (!chip->regs) { +- dev_dbg(&pdev->dev, "could not remap register memory\n"); +- retval = -ENOMEM; +- goto err_ioremap; +- } ++ chip->regs = regs; + + chip->reset_pin = devm_gpiod_get_index(dev, "ac97", 2, GPIOD_OUT_HIGH); + if (IS_ERR(chip->reset_pin)) +@@ -770,25 +756,25 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + retval = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (retval) { + dev_dbg(&pdev->dev, "could not register on ac97 bus\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_mixer_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 mixer\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_pcm_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 pcm device\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = snd_card_register(card); + if (retval) { + dev_dbg(&pdev->dev, "could not register sound card\n"); +- goto err_ac97_bus; ++ return retval; + } + + platform_set_drvdata(pdev, card); +@@ -797,18 +783,6 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->regs, irq); + + return 0; +- +-err_ac97_bus: +- iounmap(chip->regs); +-err_ioremap: +- free_irq(irq, chip); +-err_request_irq: +- snd_card_free(card); +-err_snd_card_new: +- clk_disable_unprepare(pclk); +-err_prepare_enable: +- clk_put(pclk); +- return retval; + } + + static int atmel_ac97c_suspend(struct device *pdev) +@@ -839,13 +813,6 @@ static void atmel_ac97c_remove(struct platform_device *pdev) + ac97c_writel(chip, CAMR, 0); + ac97c_writel(chip, COMR, 0); + ac97c_writel(chip, MR, 0); +- +- clk_disable_unprepare(chip->pclk); +- clk_put(chip->pclk); +- iounmap(chip->regs); +- free_irq(chip->irq, chip); +- +- snd_card_free(card); + } + + static struct platform_driver atmel_ac97c_driver = { +diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c +--- a/sound/core/compress_offload.c ++++ b/sound/core/compress_offload.c +@@ -1393,17 +1393,17 @@ static long snd_compr_ioctl_compat(struct file *file, unsigned int cmd, + #endif + + static const struct file_operations snd_compr_file_ops = { +- .owner = THIS_MODULE, +- .open = snd_compr_open, +- .release = snd_compr_free, +- .write = snd_compr_write, +- .read = snd_compr_read, +- .unlocked_ioctl = snd_compr_ioctl, ++ .owner = THIS_MODULE, ++ .open = snd_compr_open, ++ .release = snd_compr_free, ++ .write = snd_compr_write, ++ .read = snd_compr_read, ++ .unlocked_ioctl = snd_compr_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_compr_ioctl_compat, ++ .compat_ioctl = snd_compr_ioctl_compat, + #endif +- .mmap = snd_compr_mmap, +- .poll = snd_compr_poll, ++ .mmap = snd_compr_mmap, ++ .poll = snd_compr_poll, + }; + + static int snd_compress_dev_register(struct snd_device *device) +diff --git a/sound/core/control.c b/sound/core/control.c +--- a/sound/core/control.c ++++ b/sound/core/control.c +@@ -38,7 +38,7 @@ struct snd_kctl_ioctl { + snd_kctl_ioctl_func_t fioctl; + }; + +-static DECLARE_RWSEM(snd_ioctl_rwsem); ++DECLARE_RWSEM(snd_ioctl_rwsem); + static DECLARE_RWSEM(snd_ctl_layer_rwsem); + static LIST_HEAD(snd_control_ioctls); + #ifdef CONFIG_COMPAT +@@ -55,9 +55,7 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + struct snd_ctl_file *ctl; + int i, err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); + if (!card) { +@@ -65,12 +63,10 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + goto __error1; + } + err = snd_card_file_add(card, file); +- if (err < 0) { +- err = -ENODEV; ++ if (err < 0) + goto __error1; +- } + if (!try_module_get(card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); +@@ -876,23 +872,81 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, + { + struct snd_ctl_card_info *info __free(kfree) = + kzalloc(sizeof(*info), GFP_KERNEL); ++ ssize_t n; + + if (! info) + return -ENOMEM; ++ ++ static_assert(sizeof(info->components) >= 2); ++ + scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ + info->card = card->number; + strscpy(info->id, card->id, sizeof(info->id)); + strscpy(info->driver, card->driver, sizeof(info->driver)); + strscpy(info->name, card->shortname, sizeof(info->name)); + strscpy(info->longname, card->longname, sizeof(info->longname)); + strscpy(info->mixername, card->mixername, sizeof(info->mixername)); +- strscpy(info->components, card->components, sizeof(info->components)); ++ n = strscpy(info->components, components, sizeof(info->components)); ++ if (n < 0) // mark the truncation with '>' before NULL terminator ++ info->components[sizeof(info->components) - 2] = '>'; + } + if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) + return -EFAULT; + return 0; + } + ++static int snd_ctl_card_bytes(struct snd_card *card, ++ struct snd_ctl_card_bytes *info, ++ unsigned int __user *data_len_out) ++{ ++ unsigned int data_len; ++ ++ switch (info->type) { ++ case SND_CTL_CARD_BTYPE_COMPONENTS: ++ scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ ++ data_len = strlen(components) + 1; ++ ++ if (!info->data || info->data_allocated == 0) ++ break; ++ ++ if (info->data_allocated < data_len) ++ return -ENOMEM; ++ ++ if (copy_to_user(u64_to_user_ptr(info->data), components, data_len)) ++ return -EFAULT; ++ } ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (put_user(data_len, data_len_out)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int snd_ctl_card_bytes_user(struct snd_card *card, ++ struct snd_ctl_card_bytes __user *_info) ++{ ++ struct snd_ctl_card_bytes info; ++ ++ if (copy_from_user(&info, _info, sizeof(info))) ++ return -EFAULT; ++ ++ return snd_ctl_card_bytes(card, &info, &_info->data_len); ++} ++ + static int snd_ctl_elem_list(struct snd_card *card, + struct snd_ctl_elem_list *list) + { +@@ -1990,6 +2044,8 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg + return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; + case SNDRV_CTL_IOCTL_CARD_INFO: + return snd_ctl_card_info(card, ctl, cmd, argp); ++ case SNDRV_CTL_IOCTL_CARD_BYTES: ++ return snd_ctl_card_bytes_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_LIST: + return snd_ctl_elem_list_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO: +@@ -2339,16 +2395,15 @@ EXPORT_SYMBOL_GPL(snd_ctl_disconnect_layer); + * INIT PART + */ + +-static const struct file_operations snd_ctl_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_ctl_read, +- .open = snd_ctl_open, +- .release = snd_ctl_release, +- .poll = snd_ctl_poll, +- .unlocked_ioctl = snd_ctl_ioctl, +- .compat_ioctl = snd_ctl_ioctl_compat, +- .fasync = snd_ctl_fasync, ++static const struct file_operations snd_ctl_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_ctl_read, ++ .open = snd_ctl_open, ++ .release = snd_ctl_release, ++ .poll = snd_ctl_poll, ++ .unlocked_ioctl = snd_ctl_ioctl, ++ .compat_ioctl = snd_ctl_ioctl_compat, ++ .fasync = snd_ctl_fasync, + }; + + /* call lops under rwsems; called from snd_ctl_dev_*() below() */ +diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c +--- a/sound/core/control_compat.c ++++ b/sound/core/control_compat.c +@@ -226,8 +226,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, + if (type < 0) + return type; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -236,7 +236,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, + data->value.integer.value[i] = val; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (size < 0) { + dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); + return -EINVAL; +@@ -259,8 +259,8 @@ static int copy_ctl_value_to_user(void __user *userdata, + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, size; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -269,7 +269,7 @@ static int copy_ctl_value_to_user(void __user *userdata, + return -EFAULT; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (copy_to_user(valuep, data->value.bytes.data, size)) + return -EFAULT; + } +@@ -417,7 +417,7 @@ static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, + break; + } + return snd_ctl_elem_add(file, data, replace); +-} ++} + + enum { + SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), +@@ -446,6 +446,7 @@ static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, uns + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + case SNDRV_CTL_IOCTL_CARD_INFO: ++ case SNDRV_CTL_IOCTL_CARD_BYTES: + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + case SNDRV_CTL_IOCTL_POWER: + case SNDRV_CTL_IOCTL_POWER_STATE: +diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c +--- a/sound/core/hwdep.c ++++ b/sound/core/hwdep.c +@@ -87,7 +87,7 @@ static int snd_hwdep_open(struct inode *inode, struct file * file) + + if (!try_module_get(hw->card->module)) { + snd_card_unref(hw->card); +- return -EFAULT; ++ return -ENODEV; + } + + init_waitqueue_entry(&wait, current); +@@ -323,18 +323,17 @@ static int snd_hwdep_control_ioctl(struct snd_card *card, + + */ + +-static const struct file_operations snd_hwdep_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_hwdep_llseek, +- .read = snd_hwdep_read, +- .write = snd_hwdep_write, +- .open = snd_hwdep_open, +- .release = snd_hwdep_release, +- .poll = snd_hwdep_poll, +- .unlocked_ioctl = snd_hwdep_ioctl, +- .compat_ioctl = snd_hwdep_ioctl_compat, +- .mmap = snd_hwdep_mmap, ++static const struct file_operations snd_hwdep_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_hwdep_llseek, ++ .read = snd_hwdep_read, ++ .write = snd_hwdep_write, ++ .open = snd_hwdep_open, ++ .release = snd_hwdep_release, ++ .poll = snd_hwdep_poll, ++ .unlocked_ioctl = snd_hwdep_ioctl, ++ .compat_ioctl = snd_hwdep_ioctl_compat, ++ .mmap = snd_hwdep_mmap, + }; + + static void snd_hwdep_free(struct snd_hwdep *hwdep) +diff --git a/sound/core/info.c b/sound/core/info.c +--- a/sound/core/info.c ++++ b/sound/core/info.c +@@ -78,7 +78,7 @@ static int alloc_info_private(struct snd_info_entry *entry, + if (!entry || !entry->p) + return -ENODEV; + if (!try_module_get(entry->module)) +- return -EFAULT; ++ return -ENODEV; + data = kzalloc_obj(*data); + if (!data) { + module_put(entry->module); +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -466,20 +466,19 @@ static int snd_disconnect_fasync(int fd, struct file *file, int on) + return -ENODEV; + } + +-static const struct file_operations snd_shutdown_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_disconnect_llseek, +- .read = snd_disconnect_read, +- .write = snd_disconnect_write, +- .release = snd_disconnect_release, +- .poll = snd_disconnect_poll, +- .unlocked_ioctl = snd_disconnect_ioctl, ++static const struct file_operations snd_shutdown_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_disconnect_llseek, ++ .read = snd_disconnect_read, ++ .write = snd_disconnect_write, ++ .release = snd_disconnect_release, ++ .poll = snd_disconnect_poll, ++ .unlocked_ioctl = snd_disconnect_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_disconnect_ioctl, ++ .compat_ioctl = snd_disconnect_ioctl, + #endif +- .mmap = snd_disconnect_mmap, +- .fasync = snd_disconnect_fasync ++ .mmap = snd_disconnect_mmap, ++ .fasync = snd_disconnect_fasync + }; + + /** +@@ -592,6 +591,9 @@ static int snd_card_do_free(struct snd_card *card) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); + #endif + snd_device_free_all(card); ++ kfree(card->components); ++ card->components = NULL; ++ card->components_alloc_size = 0; + if (card->private_free) + card->private_free(card); + #ifdef CONFIG_SND_CTL_DEBUG +@@ -725,7 +727,7 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, + int len, loops; + bool is_default = false; + char *id; +- ++ + copy_valid_id_string(card, src, nid); + id = card->id; + +@@ -1034,21 +1036,44 @@ int __init snd_card_info_init(void) + * + * Return: Zero otherwise a negative error code. + */ +- ++ + int snd_component_add(struct snd_card *card, const char *component) + { + char *ptr; + int len = strlen(component); ++ unsigned int cur_len, need_len; + +- ptr = strstr(card->components, component); +- if (ptr != NULL) { +- if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ +- return 1; ++ guard(rwsem_write)(&snd_ioctl_rwsem); ++ ++ if (card->components) { ++ ptr = strstr(card->components, component); ++ if (ptr) { ++ if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ ++ return 1; ++ } ++ cur_len = strlen(card->components) + 1; ++ } else { ++ cur_len = 0; + } +- if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { ++ ++ need_len = cur_len + len + 1; ++ if (need_len > 512) { + snd_BUG(); + return -ENOMEM; + } ++ ++ if (need_len > card->components_alloc_size) { ++ unsigned int new_alloc = roundup(need_len, 32); ++ ++ ptr = krealloc(card->components, new_alloc, GFP_KERNEL); ++ if (!ptr) ++ return -ENOMEM; ++ if (!card->components) ++ ptr[0] = '\0'; ++ card->components = ptr; ++ card->components_alloc_size = new_alloc; ++ } ++ + if (card->components[0] != '\0') + strcat(card->components, " "); + strcat(card->components, component); +diff --git a/sound/core/jack.c b/sound/core/jack.c +--- a/sound/core/jack.c ++++ b/sound/core/jack.c +@@ -303,41 +303,41 @@ static ssize_t jack_type_read(struct file *file, + } + + static const struct file_operations jack_type_fops = { +- .open = simple_open, +- .read = jack_type_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_type_read, ++ .llseek = default_llseek, + }; + #endif + + static const struct file_operations sw_inject_enable_fops = { +- .open = simple_open, +- .read = sw_inject_enable_read, +- .write = sw_inject_enable_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = sw_inject_enable_read, ++ .write = sw_inject_enable_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jackin_inject_fops = { +- .open = simple_open, +- .write = jackin_inject_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .write = jackin_inject_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_id_fops = { +- .open = simple_open, +- .read = jack_kctl_id_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_id_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_mask_bits_fops = { +- .open = simple_open, +- .read = jack_kctl_mask_bits_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_mask_bits_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_status_fops = { +- .open = simple_open, +- .read = jack_kctl_status_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_status_read, ++ .llseek = default_llseek, + }; + + static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, +diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c +--- a/sound/core/oss/mixer_oss.c ++++ b/sound/core/oss/mixer_oss.c +@@ -30,9 +30,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + struct snd_mixer_oss_file *fmixer; + int err; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + card = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIXER); +@@ -60,7 +58,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + kfree(fmixer); + snd_card_file_remove(card, file); + snd_card_unref(card); +- return -EFAULT; ++ return -ENODEV; + } + snd_card_unref(card); + return 0; +@@ -407,13 +405,12 @@ static long snd_mixer_oss_ioctl_compat(struct file *file, unsigned int cmd, + * REGISTRATION PART + */ + +-static const struct file_operations snd_mixer_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .open = snd_mixer_oss_open, +- .release = snd_mixer_oss_release, +- .unlocked_ioctl = snd_mixer_oss_ioctl, +- .compat_ioctl = snd_mixer_oss_ioctl_compat, ++static const struct file_operations snd_mixer_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .open = snd_mixer_oss_open, ++ .release = snd_mixer_oss_release, ++ .unlocked_ioctl = snd_mixer_oss_ioctl, ++ .compat_ioctl = snd_mixer_oss_ioctl_compat, + }; + + /* +diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c +--- a/sound/core/oss/pcm_oss.c ++++ b/sound/core/oss/pcm_oss.c +@@ -879,11 +879,11 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); + snd_mask_none(&mask); + if (atomic_read(&substream->mmap_count)) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else { +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (!direct) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); + if (err < 0) { +@@ -909,7 +909,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + else + sformat = snd_pcm_plug_slave_format(format, sformat_mask); + +- if ((__force int)sformat < 0 || ++ if (sformat < 0 || + !snd_mask_test_format(sformat_mask, sformat)) { + pcm_for_each_format(sformat) { + if (snd_mask_test_format(sformat_mask, sformat) && +@@ -921,7 +921,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + goto failure; + } + format_found: +- err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, (__force int)sformat, 0); ++ err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0); + if (err < 0) + goto failure; + +@@ -930,9 +930,9 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + } else { + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, +- (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); ++ SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, +- (__force int)snd_pcm_oss_format_from(runtime->oss.format), 0); ++ snd_pcm_oss_format_from(runtime->oss.format), 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, +@@ -1875,7 +1875,7 @@ static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) + format_mask = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + for (fmt = 0; fmt < 32; ++fmt) { + if (snd_mask_test(format_mask, fmt)) { +- int f = snd_pcm_oss_format_to((__force snd_pcm_format_t)fmt); ++ int f = snd_pcm_oss_format_to(fmt); + if (f >= 0) + formats |= f; + } +@@ -2495,9 +2495,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + int nonblock; + wait_queue_entry_t wait; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + pcm = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM); +@@ -2509,7 +2507,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { +@@ -3129,17 +3127,16 @@ static inline void snd_pcm_oss_proc_done(struct snd_pcm *pcm) + * ENTRY functions + */ + +-static const struct file_operations snd_pcm_oss_f_reg = +-{ +- .owner = THIS_MODULE, +- .read = snd_pcm_oss_read, +- .write = snd_pcm_oss_write, +- .open = snd_pcm_oss_open, +- .release = snd_pcm_oss_release, +- .poll = snd_pcm_oss_poll, +- .unlocked_ioctl = snd_pcm_oss_ioctl, +- .compat_ioctl = snd_pcm_oss_ioctl_compat, +- .mmap = snd_pcm_oss_mmap, ++static const struct file_operations snd_pcm_oss_f_reg = { ++ .owner = THIS_MODULE, ++ .read = snd_pcm_oss_read, ++ .write = snd_pcm_oss_write, ++ .open = snd_pcm_oss_open, ++ .release = snd_pcm_oss_release, ++ .poll = snd_pcm_oss_poll, ++ .unlocked_ioctl = snd_pcm_oss_ioctl, ++ .compat_ioctl = snd_pcm_oss_ioctl_compat, ++ .mmap = snd_pcm_oss_mmap, + }; + + static void register_oss_dsp(struct snd_pcm *pcm, int index) +diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c +--- a/sound/core/oss/pcm_plugin.c ++++ b/sound/core/oss/pcm_plugin.c +@@ -272,13 +272,13 @@ static int snd_pcm_plug_formats(const struct snd_mask *mask, + SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); +- snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW); ++ snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW); + + if (formats.bits[0] & lower_32_bits(linfmts)) + formats.bits[0] |= lower_32_bits(linfmts); + if (formats.bits[1] & upper_32_bits(linfmts)) + formats.bits[1] |= upper_32_bits(linfmts); +- return snd_mask_test(&formats, (__force int)format); ++ return snd_mask_test(&formats, format); + } + + static const snd_pcm_format_t preferred_formats[] = { +@@ -307,20 +307,20 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + { + int i; + +- if (snd_mask_test(format_mask, (__force int)format)) ++ if (snd_mask_test(format_mask, format)) + return format; + if (!snd_pcm_plug_formats(format_mask, format)) +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + if (snd_pcm_format_linear(format)) { + unsigned int width = snd_pcm_format_width(format); + int unsignd = snd_pcm_format_unsigned(format) > 0; + int big = snd_pcm_format_big_endian(format) > 0; + unsigned int badness, best = -1; +- snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1; ++ snd_pcm_format_t best_format = -1; + for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) { + snd_pcm_format_t f = preferred_formats[i]; + unsigned int w; +- if (!snd_mask_test(format_mask, (__force int)f)) ++ if (!snd_mask_test(format_mask, f)) + continue; + w = snd_pcm_format_width(f); + if (w >= width) +@@ -334,21 +334,21 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + best = badness; + } + } +- if ((__force int)best_format >= 0) ++ if (best_format >= 0) + return best_format; + else +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } else { + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { + snd_pcm_format_t format1 = preferred_formats[i]; +- if (snd_mask_test(format_mask, (__force int)format1)) ++ if (snd_mask_test(format_mask, format1)) + return format1; + } + fallthrough; + default: +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } + } + } +diff --git a/sound/core/pcm.c b/sound/core/pcm.c +--- a/sound/core/pcm.c ++++ b/sound/core/pcm.c +@@ -211,11 +211,9 @@ static const char * const snd_pcm_format_names[] = { + */ + const char *snd_pcm_format_name(snd_pcm_format_t format) + { +- unsigned int format_num = (__force unsigned int)format; +- +- if (format_num >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format_num]) ++ if (format >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format]) + return "Unknown"; +- return snd_pcm_format_names[format_num]; ++ return snd_pcm_format_names[format]; + } + EXPORT_SYMBOL_GPL(snd_pcm_format_name); + +@@ -275,12 +273,12 @@ static const char *snd_pcm_stream_name(int stream) + + static const char *snd_pcm_access_name(snd_pcm_access_t access) + { +- return snd_pcm_access_names[(__force int)access]; ++ return snd_pcm_access_names[access]; + } + + static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) + { +- return snd_pcm_subformat_names[(__force int)subformat]; ++ return snd_pcm_subformat_names[subformat]; + } + + static const char *snd_pcm_tstamp_mode_name(int mode) +@@ -290,7 +288,7 @@ static const char *snd_pcm_tstamp_mode_name(int mode) + + static const char *snd_pcm_state_name(snd_pcm_state_t state) + { +- return snd_pcm_state_names[(__force int)state]; ++ return snd_pcm_state_names[state]; + } + + #if IS_ENABLED(CONFIG_SND_PCM_OSS) +diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c +--- a/sound/core/pcm_misc.c ++++ b/sound/core/pcm_misc.c +@@ -24,15 +24,12 @@ struct pcm_format_data { + unsigned char silence[8]; /* silence data to fill */ + }; + +-/* we do lots of calculations on snd_pcm_format_t; shut up sparse */ +-#define INT __force int +- + static bool valid_format(snd_pcm_format_t format) + { +- return (INT)format >= 0 && (INT)format <= (INT)SNDRV_PCM_FORMAT_LAST; ++ return format >= 0 && format <= SNDRV_PCM_FORMAT_LAST; + } + +-static const struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { ++static const struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = { + [SNDRV_PCM_FORMAT_S8] = { + .width = 8, .phys = 8, .le = -1, .signd = 1, + .silence = {}, +@@ -251,7 +248,7 @@ int snd_pcm_format_signed(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].signd; ++ val = pcm_formats[format].signd; + if (val < 0) + return -EINVAL; + return val; +@@ -300,7 +297,7 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].le; ++ val = pcm_formats[format].le; + if (val < 0) + return -EINVAL; + return val; +@@ -337,7 +334,7 @@ int snd_pcm_format_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].width; ++ val = pcm_formats[format].width; + if (!val) + return -EINVAL; + return val; +@@ -356,7 +353,7 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].phys; ++ val = pcm_formats[format].phys; + if (!val) + return -EINVAL; + return val; +@@ -390,9 +387,9 @@ const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) + { + if (!valid_format(format)) + return NULL; +- if (! pcm_formats[(INT)format].phys) ++ if (! pcm_formats[format].phys) + return NULL; +- return pcm_formats[(INT)format].silence; ++ return pcm_formats[format].silence; + } + EXPORT_SYMBOL(snd_pcm_format_silence_64); + +@@ -416,12 +413,12 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int + return -EINVAL; + if (samples == 0) + return 0; +- width = pcm_formats[(INT)format].phys; /* physical width */ ++ width = pcm_formats[format].phys; /* physical width */ + if (!width) + return -EINVAL; +- pat = pcm_formats[(INT)format].silence; ++ pat = pcm_formats[format].silence; + /* signed or 1 byte data */ +- if (pcm_formats[(INT)format].signd == 1 || width <= 8) { ++ if (pcm_formats[format].signd == 1 || width <= 8) { + unsigned int bytes = samples * width / 8; + memset(data, *pat, bytes); + return 0; +diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c +--- a/sound/core/pcm_native.c ++++ b/sound/core/pcm_native.c +@@ -257,7 +257,7 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream, + } + + /* macro for simplified cast */ +-#define PARAM_MASK_BIT(b) (1U << (__force int)(b)) ++#define PARAM_MASK_BIT(b) (1U << (b)) + + static bool hw_support_mmap(struct snd_pcm_substream *substream) + { +@@ -489,7 +489,7 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + params->msbits = snd_interval_value(i); + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + params->msbits = snd_pcm_format_width(format); + } + } +@@ -497,13 +497,13 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + if (params->msbits) { + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + + if (snd_pcm_format_linear(format) && + snd_pcm_format_width(format) != params->msbits) { + m_rw = hw_param_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT); + snd_mask_reset(m_rw, +- (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + if (snd_mask_empty(m_rw)) + return -EINVAL; + } +@@ -1252,7 +1252,7 @@ static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream) + runtime->trigger_master = NULL; + } + +-#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0 ++#define ACTION_ARG_IGNORE 0 + + struct action_ops { + int (*pre_action)(struct snd_pcm_substream *substream, +@@ -1637,7 +1637,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun); + /* + * pause callbacks: pass boolean (to start pause or resume) as state argument + */ +-#define pause_pushed(state) (__force bool)(state) ++#define pause_pushed(state) (bool)(state) + + static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +@@ -1709,14 +1709,14 @@ static const struct action_ops snd_pcm_action_pause = { + static int snd_pcm_pause(struct snd_pcm_substream *substream, bool push) + { + return snd_pcm_action(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream, + bool push) + { + return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + #ifdef CONFIG_PM +@@ -1984,7 +1984,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state) + { + snd_pcm_state_t cur_state = snd_pcm_get_state(substream); +- int f_flags = (__force int)state; ++ int f_flags = state; + + if (cur_state == SNDRV_PCM_STATE_OPEN || + cur_state == SNDRV_PCM_STATE_DISCONNECTED) +@@ -2052,7 +2052,7 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, + + return snd_pcm_action_nonatomic(&snd_pcm_action_prepare, + substream, +- (__force snd_pcm_state_t)f_flags); ++ (snd_pcm_state_t)f_flags); + } + + /* +@@ -2463,7 +2463,7 @@ static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params, + if (bits <= 0) + continue; /* ignore invalid formats */ + if ((unsigned)bits < i->min || (unsigned)bits > i->max) +- snd_mask_reset(&m, (__force unsigned)k); ++ snd_mask_reset(&m, k); + } + return snd_mask_refine(mask, &m); + } +@@ -2545,16 +2545,16 @@ static int snd_pcm_hw_rule_subformats(struct snd_pcm_hw_params *params, + + snd_mask_none(&m); + /* All PCMs support at least the default STD subformat. */ +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_STD); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_STD); + + pcm_for_each_format(f) { +- if (!snd_mask_test(fmask, (__force unsigned)f)) ++ if (!snd_mask_test(fmask, f)) + continue; + + if (f == SNDRV_PCM_FORMAT_S32_LE && *subformats) + m.bits[0] |= *subformats; + else if (snd_pcm_format_linear(f)) +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + } + + return snd_mask_refine(sfmask, &m); +@@ -2882,9 +2882,10 @@ static int snd_pcm_open_file(struct file *file, + static int snd_pcm_playback_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); +@@ -2896,9 +2897,10 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file) + static int snd_pcm_capture_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); +@@ -2920,7 +2922,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + init_waitqueue_entry(&wait, current); +@@ -3916,6 +3918,8 @@ static vm_fault_t snd_pcm_mmap_data_fault(struct vm_fault *vmf) + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; ++ if (runtime->state == SNDRV_PCM_STATE_DISCONNECTED) ++ return VM_FAULT_SIGBUS; + offset = vmf->pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) +@@ -4256,29 +4260,29 @@ static unsigned long snd_pcm_get_unmapped_area(struct file *file, + + const struct file_operations snd_pcm_f_ops[2] = { + { +- .owner = THIS_MODULE, +- .write = snd_pcm_write, +- .write_iter = snd_pcm_writev, +- .open = snd_pcm_playback_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .write = snd_pcm_write, ++ .write_iter = snd_pcm_writev, ++ .open = snd_pcm_playback_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + }, + { +- .owner = THIS_MODULE, +- .read = snd_pcm_read, +- .read_iter = snd_pcm_readv, +- .open = snd_pcm_capture_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .read = snd_pcm_read, ++ .read_iter = snd_pcm_readv, ++ .open = snd_pcm_capture_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + } + }; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -408,9 +408,10 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, + return 0; + } + +-/* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile) ++/* called from sound/core/seq/seq_midi.c and sound/core/ump.c */ ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth) + { + int err; + +@@ -419,13 +420,14 @@ int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, + if (!try_module_get(rmidi->card->module)) + return -ENXIO; + +- guard(mutex)(&rmidi->open_mutex); ++ mutex_lock_nested(&rmidi->open_mutex, depth); + err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); + if (err < 0) + module_put(rmidi->card->module); ++ mutex_unlock(&rmidi->open_mutex); + return err; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_open); ++EXPORT_SYMBOL(snd_rawmidi_kernel_open_nested); + + static int snd_rawmidi_open(struct inode *inode, struct file *file) + { +@@ -441,9 +443,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file) + if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) + return -EINVAL; /* invalid combination */ + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + if (maj == snd_major) { + rmidi = snd_lookup_minor_data(iminor(inode), +@@ -1784,14 +1784,14 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, + */ + + static const struct file_operations snd_rawmidi_f_ops = { +- .owner = THIS_MODULE, +- .read = snd_rawmidi_read, +- .write = snd_rawmidi_write, +- .open = snd_rawmidi_open, +- .release = snd_rawmidi_release, +- .poll = snd_rawmidi_poll, +- .unlocked_ioctl = snd_rawmidi_ioctl, +- .compat_ioctl = snd_rawmidi_ioctl_compat, ++ .owner = THIS_MODULE, ++ .read = snd_rawmidi_read, ++ .write = snd_rawmidi_write, ++ .open = snd_rawmidi_open, ++ .release = snd_rawmidi_release, ++ .poll = snd_rawmidi_poll, ++ .unlocked_ioctl = snd_rawmidi_ioctl, ++ .compat_ioctl = snd_rawmidi_ioctl_compat, + }; + + static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi, +diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c +--- a/sound/core/seq/oss/seq_oss.c ++++ b/sound/core/seq/oss/seq_oss.c +@@ -206,17 +206,16 @@ odev_poll(struct file *file, poll_table * wait) + * registration of sequencer minor device + */ + +-static const struct file_operations seq_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = odev_read, +- .write = odev_write, +- .open = odev_open, +- .release = odev_release, +- .poll = odev_poll, +- .unlocked_ioctl = odev_ioctl, +- .compat_ioctl = odev_ioctl_compat, +- .llseek = noop_llseek, ++static const struct file_operations seq_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .read = odev_read, ++ .write = odev_write, ++ .open = odev_open, ++ .release = odev_release, ++ .poll = odev_poll, ++ .unlocked_ioctl = odev_ioctl, ++ .compat_ioctl = odev_ioctl_compat, ++ .llseek = noop_llseek, + }; + + static int __init +diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c +--- a/sound/core/seq/seq_clientmgr.c ++++ b/sound/core/seq/seq_clientmgr.c +@@ -59,7 +59,7 @@ static DEFINE_MUTEX(register_mutex); + * client table + */ + static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; +-static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS]; ++static struct snd_seq_client __rcu *clienttab[SNDRV_SEQ_MAX_CLIENTS]; + static struct snd_seq_usage client_usage; + + /* +@@ -95,15 +95,23 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) + return snd_seq_total_cells(client->pool) > 0; + } + +-/* return pointer to client structure for specified id */ +-static struct snd_seq_client *clientptr(int clientid) ++/* return pointer to client structure for specified id; call under RCU read-lock */ ++static struct snd_seq_client *__clientptr(int clientid) + { + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } +- return clienttab[clientid]; ++ return rcu_dereference_check(clienttab[clientid], ++ lockdep_is_held(&clients_lock)); ++} ++ ++/* return pointer to client structure for specified id */ ++static struct snd_seq_client *clientptr(int clientid) ++{ ++ guard(rcu)(); ++ return __clientptr(clientid); + } + + static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) +@@ -115,8 +123,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + clientid); + return NULL; + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + if (clienttablock[clientid]) +@@ -150,8 +158,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + snd_seq_device_load_drivers(); + } + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + } +@@ -212,7 +220,6 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + } + client->type = NO_CLIENT; + snd_use_lock_init(&client->use_lock); +- rwlock_init(&client->ports_lock); + mutex_init(&client->ports_mutex); + INIT_LIST_HEAD(&client->ports_list_head); + mutex_init(&client->ioctl_mutex); +@@ -224,14 +231,17 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; + c < SNDRV_SEQ_MAX_CLIENTS; + c++) { +- if (clienttab[c] || clienttablock[c]) ++ if (rcu_access_pointer(clienttab[c]) || clienttablock[c]) + continue; +- clienttab[client->number = c] = client; ++ client->number = c; ++ rcu_assign_pointer(clienttab[c], client); + return client; + } + } else { +- if (clienttab[client_index] == NULL && !clienttablock[client_index]) { +- clienttab[client->number = client_index] = client; ++ if (rcu_access_pointer(clienttab[client_index]) == NULL && ++ !clienttablock[client_index]) { ++ client->number = client_index; ++ rcu_assign_pointer(clienttab[client_index], client); + return client; + } + } +@@ -249,10 +259,16 @@ static int seq_free_client1(struct snd_seq_client *client) + return 0; + scoped_guard(spinlock_irq, &clients_lock) { + clienttablock[client->number] = 1; +- clienttab[client->number] = NULL; ++ rcu_assign_pointer(clienttab[client->number], NULL); + } + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); ++ /* the client has been unpublished from the table; wait for a grace ++ * period so that lockless readers (snd_seq_client_use_ptr()) that ++ * observed the old pointer can no longer take a new use_lock ++ * reference, then drain the outstanding references before freeing ++ */ ++ synchronize_rcu(); + snd_use_lock_sync(&client->use_lock); + if (client->pool) + snd_seq_pool_delete(&client->pool); +@@ -296,11 +312,8 @@ static int snd_seq_open(struct inode *inode, struct file *file) + int c, mode; /* client id */ + struct snd_seq_client *client; + struct snd_seq_user_client *user; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + scoped_guard(mutex, ®ister_mutex) { + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); +@@ -517,9 +530,8 @@ static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event) + * Return the error event. + * + * If the receiver client is a user client, the original event is +- * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If +- * the original event is also variable length, the external data is +- * copied after the event record. ++ * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. The ++ * external data of a variable length event is not copied along. + * If the receiver client is a kernel client, the original event is + * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra + * kmalloc. +@@ -724,10 +736,11 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + + /* lock list */ + if (atomic) +- read_lock(&grp->list_lock); ++ rcu_read_lock(); + else + down_read_nested(&grp->list_mutex, hop); +- list_for_each_entry(subs, &grp->list_head, src_list) { ++ hlist_for_each_entry_rcu(subs, &grp->list_head, src_list, ++ lockdep_is_held(&grp->list_mutex)) { + /* both ports ready? */ + if (atomic_read(&subs->ref_count) != 2) + continue; +@@ -748,7 +761,7 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + memcpy(event, &event_saved, saved_size); + } + if (atomic) +- read_unlock(&grp->list_lock); ++ rcu_read_unlock(); + else + up_read(&grp->list_mutex); + memcpy(event, &event_saved, saved_size); +@@ -1945,7 +1958,7 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + { + struct snd_seq_query_subs *subs = arg; + struct snd_seq_port_subs_info *group; +- struct list_head *p; ++ struct hlist_node *p; + int i; + + struct snd_seq_client *cptr __free(snd_seq_client) = +@@ -1972,15 +1985,15 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + /* search for the subscriber */ + subs->num_subs = group->count; + i = 0; +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (i++ == subs->index) { + /* found! */ + struct snd_seq_subscribers *s; + if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + subs->addr = s->info.dest; + } else { +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + subs->addr = s->info.sender; + } + subs->flags = s->info.flags; +@@ -2536,19 +2549,19 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, + struct snd_seq_port_subs_info *group, + int is_src, char *msg) + { +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int count = 0; + + guard(rwsem_read)(&group->list_mutex); +- if (list_empty(&group->list_head)) ++ if (hlist_empty(&group->list_head)) + return; + snd_iprintf(buffer, msg); +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (is_src) +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", +@@ -2670,16 +2683,15 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, + * REGISTRATION PART + */ + +-static const struct file_operations snd_seq_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_seq_read, +- .write = snd_seq_write, +- .open = snd_seq_open, +- .release = snd_seq_release, +- .poll = snd_seq_poll, +- .unlocked_ioctl = snd_seq_ioctl, +- .compat_ioctl = snd_seq_ioctl_compat, ++static const struct file_operations snd_seq_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_seq_read, ++ .write = snd_seq_write, ++ .open = snd_seq_open, ++ .release = snd_seq_release, ++ .poll = snd_seq_poll, ++ .unlocked_ioctl = snd_seq_ioctl, ++ .compat_ioctl = snd_seq_ioctl_compat, + }; + + static struct device *seq_dev; +diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h +--- a/sound/core/seq/seq_clientmgr.h ++++ b/sound/core/seq/seq_clientmgr.h +@@ -49,7 +49,6 @@ struct snd_seq_client { + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; +- rwlock_t ports_lock; + struct mutex ports_mutex; + struct mutex ioctl_mutex; + int convert32; /* convert 32->64bit */ +diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c +--- a/sound/core/seq/seq_ports.c ++++ b/sound/core/seq/seq_ports.c +@@ -48,8 +48,8 @@ struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, + + if (client == NULL) + return NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ +@@ -71,8 +71,8 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + + num = pinfo->addr.port; + found = NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && + !check_inactive) + continue; /* skip inactive ports */ +@@ -98,10 +98,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + /* initialize snd_seq_port_subs_info */ + static void port_subs_info_init(struct snd_seq_port_subs_info *grp) + { +- INIT_LIST_HEAD(&grp->list_head); ++ INIT_HLIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; +- rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +@@ -154,7 +153,6 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + + num = max(port, 0); + guard(mutex)(&client->ports_mutex); +- guard(write_lock_irq)(&client->ports_lock); + struct list_head *insert_before = &client->ports_list_head; + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) +@@ -166,12 +164,13 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } +- /* insert the new port */ +- list_add_tail(&new_port->list, insert_before); +- client->num_ports++; ++ /* finish initializing the port before publishing it to RCU readers */ + new_port->addr.port = num; /* store the port number in the port */ + if (!new_port->name[0]) + sprintf(new_port->name, "port-%d", num); ++ /* insert the new port */ ++ list_add_tail_rcu(&new_port->list, insert_before); ++ client->num_ports++; + + return num; + } +@@ -202,12 +201,12 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack); + + static inline struct snd_seq_subscribers * +-get_subscriber(struct list_head *p, bool is_src) ++get_subscriber(struct hlist_node *p, bool is_src) + { + if (is_src) +- return list_entry(p, struct snd_seq_subscribers, src_list); ++ return hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- return list_entry(p, struct snd_seq_subscribers, dest_list); ++ return hlist_entry(p, struct snd_seq_subscribers, dest_list); + } + + /* +@@ -219,9 +218,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, + struct snd_seq_port_subs_info *grp, + int is_src) + { +- struct list_head *p, *n; ++ struct hlist_node *p, *n; + +- list_for_each_safe(p, n, &grp->list_head) { ++ hlist_for_each_safe(p, n, &grp->list_head) { + struct snd_seq_subscribers *subs; + + subs = get_subscriber(p, is_src); +@@ -238,13 +237,13 @@ static void clear_subscriber_list(struct snd_seq_client *client, + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) +- kfree(subs); ++ kfree_rcu(subs, rcu); + continue; + } + + /* ok we got the connected port */ + delete_and_unsubscribe_port(c, aport, subs, !is_src, true); +- kfree(subs); ++ kfree_rcu(subs, rcu); + } + } + +@@ -254,7 +253,13 @@ static int port_delete(struct snd_seq_client *client, + { + /* set closing flag and wait for all port access are gone */ + port->closing = 1; +- snd_use_lock_sync(&port->use_lock); ++ /* the port has already been unlinked from the client's port list; ++ * wait for a grace period so that RCU readers still traversing the ++ * list can no longer take a new use_lock reference, then drain the ++ * outstanding references before freeing ++ */ ++ synchronize_rcu(); ++ snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, true); +@@ -277,11 +282,10 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + struct snd_seq_client_port *found = NULL, *p; + + scoped_guard(mutex, &client->ports_mutex) { +- guard(write_lock_irq)(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + /* ok found. delete from the list at first */ +- list_del(&p->list); ++ list_del_rcu(&p->list); + client->num_ports--; + found = p; + break; +@@ -297,26 +301,16 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + /* delete the all ports belonging to the given client */ + int snd_seq_delete_all_ports(struct snd_seq_client *client) + { +- struct list_head deleted_list; + struct snd_seq_client_port *port, *tmp; +- +- /* move the port list to deleted_list, and +- * clear the port list in the client data. ++ ++ /* unlink and delete each port; port_delete() waits for an RCU grace ++ * period before draining the port, so concurrent lockless readers can ++ * no longer take a new use_lock reference on it + */ + guard(mutex)(&client->ports_mutex); +- scoped_guard(write_lock_irq, &client->ports_lock) { +- if (!list_empty(&client->ports_list_head)) { +- list_add(&deleted_list, &client->ports_list_head); +- list_del_init(&client->ports_list_head); +- } else { +- INIT_LIST_HEAD(&deleted_list); +- } +- client->num_ports = 0; +- } +- +- /* remove each port in deleted_list */ +- list_for_each_entry_safe(port, tmp, &deleted_list, list) { +- list_del(&port->list); ++ list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { ++ list_del_rcu(&port->list); ++ client->num_ports--; + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } +@@ -499,20 +493,20 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + bool is_src, bool exclusive, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int err; + + grp = is_src ? &port->c_src : &port->c_dest; + guard(rwsem_write)(&grp->list_mutex); + if (exclusive) { +- if (!list_empty(&grp->list_head)) ++ if (!hlist_empty(&grp->list_head)) + return -EBUSY; + } else { + if (grp->exclusive) + return -EBUSY; + /* check whether already exists */ +- list_for_each(p, &grp->list_head) { ++ hlist_for_each(p, &grp->list_head) { + s = get_subscriber(p, is_src); + if (match_subs_info(&subs->info, &s->info)) + return -EBUSY; +@@ -526,11 +520,10 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + } + + /* add to list */ +- guard(write_lock_irq)(&grp->list_lock); + if (is_src) +- list_add_tail(&subs->src_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->src_list, &grp->list_head); + else +- list_add_tail(&subs->dest_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->dest_list, &grp->list_head); + grp->exclusive = exclusive; + atomic_inc(&subs->ref_count); + +@@ -544,17 +537,15 @@ static void __delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *list; ++ struct hlist_node *list; + bool empty; + + grp = is_src ? &port->c_src : &port->c_dest; + list = is_src ? &subs->src_list : &subs->dest_list; +- scoped_guard(write_lock_irq, &grp->list_lock) { +- empty = list_empty(list); +- if (!empty) +- list_del_init(list); +- grp->exclusive = 0; +- } ++ empty = hlist_unhashed(list); ++ if (!empty) ++ hlist_del_init_rcu(list); ++ grp->exclusive = 0; + + if (!empty) + unsubscribe_port(client, port, grp, &subs->info, ack); +@@ -590,8 +581,8 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + + subs->info = *info; + atomic_set(&subs->ref_count, 0); +- INIT_LIST_HEAD(&subs->src_list); +- INIT_LIST_HEAD(&subs->dest_list); ++ INIT_HLIST_NODE(&subs->src_list); ++ INIT_HLIST_NODE(&subs->dest_list); + + exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); + +@@ -612,7 +603,7 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + error: +- kfree(subs); ++ kfree_rcu(subs, rcu); + return err; + } + +@@ -633,7 +624,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + */ + scoped_guard(rwsem_write, &dest->list_mutex) { + /* look for the connection */ +- list_for_each_entry(subs, &dest->list_head, dest_list) { ++ hlist_for_each_entry(subs, &dest->list_head, dest_list) { + if (match_subs_info(info, &subs->info)) { + __delete_and_unsubscribe_port(dest_client, dest_port, + subs, false, +@@ -648,7 +639,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); +- kfree(subs); ++ kfree_rcu(subs, rcu); + return 0; + } + +@@ -662,7 +653,7 @@ int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + int err = -ENOENT; + + guard(rwsem_read)(&src_grp->list_mutex); +- list_for_each_entry(s, &src_grp->list_head, src_list) { ++ hlist_for_each_entry(s, &src_grp->list_head, src_list) { + if (addr_match(dest_addr, &s->info.dest)) { + *subs = s->info; + err = 0; +diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h +--- a/sound/core/seq/seq_ports.h ++++ b/sound/core/seq/seq_ports.h +@@ -28,17 +28,17 @@ + + struct snd_seq_subscribers { + struct snd_seq_port_subscribe info; /* additional info */ +- struct list_head src_list; /* link of sources */ +- struct list_head dest_list; /* link of destinations */ ++ struct hlist_node src_list; /* link of sources */ ++ struct hlist_node dest_list; /* link of destinations */ + atomic_t ref_count; ++ struct rcu_head rcu; /* for deferred free */ + }; + + struct snd_seq_port_subs_info { +- struct list_head list_head; /* list of subscribed ports */ ++ struct hlist_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; +- rwlock_t list_lock; + int (*open)(void *private_data, struct snd_seq_port_subscribe *info); + int (*close)(void *private_data, struct snd_seq_port_subscribe *info); + }; +diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c +--- a/sound/core/seq/seq_ump_client.c ++++ b/sound/core/seq/seq_ump_client.c +@@ -37,8 +37,11 @@ struct seq_ump_client { + struct snd_ump_endpoint *ump; /* assigned endpoint */ + int seq_client; /* sequencer client id */ + int opened[2]; /* current opens for each direction */ +- rwlock_t output_lock; /* protects out_rfile output access */ + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ ++ /* RCU-protected shadow of out_rfile.output for the delivery hot path; ++ * out_rfile itself is only touched by open/close under open_mutex ++ */ ++ struct snd_rawmidi_substream __rcu *out_substream; + struct seq_ump_input_buffer input; /* input parser context */ + void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ + struct work_struct group_notify_work; /* FB change notification */ +@@ -89,8 +92,8 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + unsigned char type; + int len; + +- guard(read_lock_irqsave)(&client->output_lock); +- substream = client->out_rfile.output; ++ guard(rcu)(); ++ substream = rcu_dereference(client->out_substream); + if (!substream) + return -ENODEV; + if (!snd_seq_ev_is_ump(ev)) +@@ -108,19 +111,22 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + static int seq_ump_client_open(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + int err; + + guard(mutex)(&ump->open_mutex); + if (dir == STR_OUT && !client->opened[dir]) { ++ /* out_rfile is only accessed under open_mutex; the delivery ++ * path reads out_substream via RCU, so open into out_rfile ++ * directly and publish the substream afterwards ++ */ + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, +- &rfile); ++ &client->out_rfile); + if (err < 0) + return err; +- scoped_guard(write_lock_irqsave, &client->output_lock) +- client->out_rfile = rfile; ++ rcu_assign_pointer(client->out_substream, ++ client->out_rfile.output); + } + client->opened[dir]++; + return 0; +@@ -130,17 +136,18 @@ static int seq_ump_client_open(struct seq_ump_client *client, int dir) + static int seq_ump_client_close(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + + guard(mutex)(&ump->open_mutex); + if (!--client->opened[dir]) { +- if (dir == STR_OUT) { +- scoped_guard(write_lock_irqsave, &client->output_lock) { +- rfile = client->out_rfile; +- client->out_rfile = (struct snd_rawmidi_file){}; +- } +- if (rfile.rmidi) +- snd_rawmidi_kernel_release(&rfile); ++ if (dir == STR_OUT && client->out_rfile.rmidi) { ++ rcu_assign_pointer(client->out_substream, NULL); ++ /* wait for a grace period so that no reader in the ++ * delivery path is still writing to the substream ++ * before it is released ++ */ ++ synchronize_rcu(); ++ snd_rawmidi_kernel_release(&client->out_rfile); ++ client->out_rfile = (struct snd_rawmidi_file){}; + } + } + return 0; +@@ -480,7 +487,6 @@ static int snd_seq_ump_probe(struct snd_seq_device *dev) + + INIT_WORK(&client->group_notify_work, handle_group_notify); + client->ump = ump; +- rwlock_init(&client->output_lock); + + client->seq_client = + snd_seq_create_kernel_client(card, ump->core.device, +diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c +--- a/sound/core/seq/seq_virmidi.c ++++ b/sound/core/seq/seq_virmidi.c +@@ -78,10 +78,11 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + int len; + + if (atomic) +- read_lock(&rdev->filelist_lock); ++ rcu_read_lock(); + else + down_read(&rdev->filelist_sem); +- list_for_each_entry(vmidi, &rdev->filelist, list) { ++ list_for_each_entry_rcu(vmidi, &rdev->filelist, list, ++ lockdep_is_held(&rdev->filelist_sem)) { + if (!READ_ONCE(vmidi->trigger)) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { +@@ -96,7 +97,7 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + } + } + if (atomic) +- read_unlock(&rdev->filelist_lock); ++ rcu_read_unlock(); + else + up_read(&rdev->filelist_sem); + +@@ -200,8 +201,7 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) + vmidi->port = rdev->port; + runtime->private_data = vmidi; + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_add_tail(&vmidi->list, &rdev->filelist); ++ list_add_tail_rcu(&vmidi->list, &rdev->filelist); + } + vmidi->rdev = rdev; + return 0; +@@ -243,9 +243,13 @@ static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) + struct snd_virmidi *vmidi = substream->runtime->private_data; + + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_del(&vmidi->list); ++ list_del_rcu(&vmidi->list); + } ++ /* wait for a grace period so that lockless readers in the atomic ++ * delivery path (snd_virmidi_dev_receive_event()) are no longer ++ * traversing this entry before its parser and memory are freed ++ */ ++ synchronize_rcu(); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); +@@ -508,7 +512,6 @@ int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmi + rdev->device = device; + rdev->client = -1; + init_rwsem(&rdev->filelist_sem); +- rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; +diff --git a/sound/core/sound.c b/sound/core/sound.c +--- a/sound/core/sound.c ++++ b/sound/core/sound.c +@@ -167,11 +167,10 @@ static int snd_open(struct inode *inode, struct file *file) + return err; + } + +-static const struct file_operations snd_fops = +-{ +- .owner = THIS_MODULE, +- .open = snd_open, +- .llseek = noop_llseek, ++static const struct file_operations snd_fops = { ++ .owner = THIS_MODULE, ++ .open = snd_open, ++ .llseek = noop_llseek, + }; + + #ifdef CONFIG_SND_DYNAMIC_MINORS +@@ -358,7 +357,7 @@ static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_bu + struct snd_minor *mptr; + + guard(mutex)(&sound_mutex); +- for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) { ++ for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + mptr = snd_minors[minor]; + if (!mptr) + continue; +@@ -395,15 +394,21 @@ int __init snd_minor_info_init(void) + + static int __init alsa_sound_init(void) + { ++ int err; ++ + snd_major = major; + snd_ecards_limit = cards_limit; +- if (register_chrdev(major, "alsa", &snd_fops)) { ++ ++ err = register_chrdev(major, "alsa", &snd_fops); ++ if (err < 0) { + pr_err("ALSA core: unable to register native major device number %d\n", major); +- return -EIO; ++ return err; + } +- if (snd_info_init() < 0) { ++ ++ err = snd_info_init(); ++ if (err < 0) { + unregister_chrdev(major, "alsa"); +- return -ENOMEM; ++ return err; + } + + #ifdef CONFIG_SND_DEBUG +diff --git a/sound/core/sound_kunit.c b/sound/core/sound_kunit.c +--- a/sound/core/sound_kunit.c ++++ b/sound/core/sound_kunit.c +@@ -17,8 +17,8 @@ + .name = #fmt, \ + } + +-#define WRONG_FORMAT_1 (__force snd_pcm_format_t)((__force int)SNDRV_PCM_FORMAT_LAST + 1) +-#define WRONG_FORMAT_2 (__force snd_pcm_format_t)-1 ++#define WRONG_FORMAT_1 (SNDRV_PCM_FORMAT_LAST + 1) ++#define WRONG_FORMAT_2 -1 + + #define VALID_NAME "ValidName" + #define NAME_W_SPEC_CHARS "In%v@1id name" +diff --git a/sound/core/timer.c b/sound/core/timer.c +--- a/sound/core/timer.c ++++ b/sound/core/timer.c +@@ -1546,11 +1546,8 @@ static int realloc_user_queue(struct snd_timer_user *tu, int size) + static int snd_timer_user_open(struct inode *inode, struct file *file) + { + struct snd_timer_user *tu; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + tu = kzalloc_obj(*tu); + if (tu == NULL) +@@ -2176,9 +2173,9 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon + } + + static const struct file_operations snd_utimer_fops = { +- .llseek = noop_llseek, +- .release = snd_utimer_release, +- .unlocked_ioctl = snd_utimer_ioctl, ++ .llseek = noop_llseek, ++ .release = snd_utimer_release, ++ .unlocked_ioctl = snd_utimer_ioctl, + }; + + static int snd_utimer_start(struct snd_timer *t) +@@ -2530,16 +2527,15 @@ static __poll_t snd_timer_user_poll(struct file *file, poll_table * wait) + #define snd_timer_user_ioctl_compat NULL + #endif + +-static const struct file_operations snd_timer_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_timer_user_read, +- .open = snd_timer_user_open, +- .release = snd_timer_user_release, +- .poll = snd_timer_user_poll, +- .unlocked_ioctl = snd_timer_user_ioctl, +- .compat_ioctl = snd_timer_user_ioctl_compat, +- .fasync = snd_timer_user_fasync, ++static const struct file_operations snd_timer_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_timer_user_read, ++ .open = snd_timer_user_open, ++ .release = snd_timer_user_release, ++ .poll = snd_timer_user_poll, ++ .unlocked_ioctl = snd_timer_user_ioctl, ++ .compat_ioctl = snd_timer_user_ioctl_compat, ++ .fasync = snd_timer_user_fasync, + }; + + /* unregister the system timer */ +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1157,10 +1157,11 @@ static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) + return -ENODEV; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { +- err = snd_rawmidi_kernel_open(&ump->core, 0, +- SNDRV_RAWMIDI_LFLG_OUTPUT | +- SNDRV_RAWMIDI_LFLG_APPEND, +- &ump->legacy_out_rfile); ++ err = snd_rawmidi_kernel_open_nested(&ump->core, 0, ++ SNDRV_RAWMIDI_LFLG_OUTPUT | ++ SNDRV_RAWMIDI_LFLG_APPEND, ++ &ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + if (err < 0) + return err; + } +diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c +--- a/sound/drivers/aloop.c ++++ b/sound/drivers/aloop.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -55,7 +56,7 @@ MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver."); + module_param_array(pcm_notify, int, NULL, 0444); + MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes."); + module_param_array(timer_source, charp, NULL, 0444); +-MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default]."); ++MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default], 'hrtimer' for high-resolution timer."); + + #define NO_PITCH 100000 + +@@ -161,8 +162,11 @@ struct loopback_pcm { + unsigned int period_size_frac; /* period size in jiffies ticks */ + unsigned int last_drift; + unsigned long last_jiffies; +- /* If jiffies timer is used */ ++ /* If jiffies / hrtimer is used */ + struct timer_list timer; ++#ifdef CONFIG_HIGH_RES_TIMERS ++ struct hrtimer hrtimer; ++#endif + + /* size of per channel buffer in case of non-interleaved access */ + unsigned int channel_buf_n; +@@ -232,6 +236,31 @@ static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static int loopback_hrtimer_start(struct loopback_pcm *dpcm) ++{ ++ unsigned long tick; ++ unsigned int rate_shift = get_rate_shift(dpcm); ++ ++ if (rate_shift != dpcm->pcm_rate_shift) { ++ dpcm->pcm_rate_shift = rate_shift; ++ dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size); ++ } ++ if (dpcm->period_size_frac <= dpcm->irq_pos) { ++ dpcm->irq_pos %= dpcm->period_size_frac; ++ dpcm->period_update_pending = 1; ++ } ++ tick = dpcm->period_size_frac - dpcm->irq_pos; ++ tick = DIV_ROUND_UP(tick, dpcm->pcm_bps); ++ hrtimer_start(&dpcm->hrtimer, ++ ns_to_ktime(div_u64((u64)tick * NSEC_PER_SEC, HZ)), ++ HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_start(struct loopback_pcm *dpcm) + { +@@ -270,6 +299,16 @@ static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static inline int loopback_hrtimer_stop(struct loopback_pcm *dpcm) ++{ ++ hrtimer_try_to_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_stop(struct loopback_pcm *dpcm) + { +@@ -300,6 +339,15 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static inline int loopback_hrtimer_stop_sync(struct loopback_pcm *dpcm) ++{ ++ hrtimer_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in loopback->cable_lock */ + static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) + { +@@ -734,6 +782,30 @@ static void loopback_jiffies_timer_function(struct timer_list *t) + snd_pcm_period_elapsed(dpcm->substream); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static enum hrtimer_restart loopback_hrtimer_function(struct hrtimer *t) ++{ ++ struct loopback_pcm *dpcm = container_of(t, struct loopback_pcm, hrtimer); ++ bool period_elapsed = false; ++ ++ scoped_guard(spinlock_irqsave, &dpcm->cable->lock) { ++ if (loopback_jiffies_timer_pos_update(dpcm->cable) & ++ (1 << dpcm->substream->stream)) { ++ loopback_hrtimer_start(dpcm); ++ if (dpcm->period_update_pending) { ++ dpcm->period_update_pending = 0; ++ period_elapsed = true; ++ } ++ } ++ } ++ ++ if (period_elapsed) ++ snd_pcm_period_elapsed(dpcm->substream); ++ ++ return HRTIMER_NORESTART; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime, + unsigned long resolution) +@@ -907,6 +979,21 @@ static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm, + snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static void loopback_hrtimer_dpcm_info(struct loopback_pcm *dpcm, ++ struct snd_info_buffer *buffer) ++{ ++ snd_iprintf(buffer, " update_pending:\t%u\n", ++ dpcm->period_update_pending); ++ snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos); ++ snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac); ++ snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n", ++ dpcm->last_jiffies, jiffies); ++ snd_iprintf(buffer, " timer_expires:\t%llu\n", ++ ktime_to_ns(hrtimer_get_expires(&dpcm->hrtimer))); ++} ++#endif ++ + static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm, + struct snd_info_buffer *buffer) + { +@@ -1097,6 +1184,26 @@ static const struct loopback_ops loopback_jiffies_timer_ops = { + .dpcm_info = loopback_jiffies_timer_dpcm_info, + }; + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static int loopback_hrtimer_open(struct loopback_pcm *dpcm) ++{ ++ hrtimer_setup(&dpcm->hrtimer, loopback_hrtimer_function, ++ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++ ++static const struct loopback_ops loopback_hrtimer_ops = { ++ .open = loopback_hrtimer_open, ++ .start = loopback_hrtimer_start, ++ .stop = loopback_hrtimer_stop, ++ .stop_sync = loopback_hrtimer_stop_sync, ++ .close_substream = loopback_hrtimer_stop_sync, ++ .pos_update = loopback_jiffies_timer_pos_update, ++ .dpcm_info = loopback_hrtimer_dpcm_info, ++}; ++#endif ++ + static int loopback_parse_timer_id(const char *str, + struct snd_timer_id *tid) + { +@@ -1274,7 +1381,12 @@ static int loopback_open(struct snd_pcm_substream *substream) + spin_lock_init(&cable->lock); + snd_refcount_init(&cable->stop_count); + cable->hw = loopback_pcm_hardware; +- if (loopback->timer_source) ++#ifdef CONFIG_HIGH_RES_TIMERS ++ if (loopback->timer_source && !strcmp(loopback->timer_source, "hrtimer")) ++ cable->ops = &loopback_hrtimer_ops; ++ else ++#endif ++ if (loopback->timer_source && loopback->timer_source[0]) + cable->ops = &loopback_snd_timer_ops; + else + cable->ops = &loopback_jiffies_timer_ops; +@@ -1491,7 +1603,7 @@ static int loopback_format_info(struct snd_kcontrol *kcontrol, + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; +- uinfo->value.integer.max = (__force int)SNDRV_PCM_FORMAT_LAST; ++ uinfo->value.integer.max = SNDRV_PCM_FORMAT_LAST; + uinfo->value.integer.step = 1; + return 0; + } +@@ -1502,7 +1614,7 @@ static int loopback_format_get(struct snd_kcontrol *kcontrol, + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = +- (__force int)loopback->setup[kcontrol->id.subdevice] ++ loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].format; + return 0; + } +@@ -1556,7 +1668,7 @@ static int loopback_channels_get(struct snd_kcontrol *kcontrol, + static int loopback_access_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +- const char * const texts[] = {"Interleaved", "Non-interleaved"}; ++ static const char * const texts[] = {"Interleaved", "Non-interleaved"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -100,6 +100,7 @@ struct dummy_timer_ops { + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); ++ int (*sync_stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); + }; + +@@ -285,6 +286,14 @@ static int dummy_systimer_stop(struct snd_pcm_substream *substream) + return 0; + } + ++static int dummy_systimer_sync_stop(struct snd_pcm_substream *substream) ++{ ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_delete_sync(&dpcm->timer); ++ return 0; ++} ++ + static int dummy_systimer_prepare(struct snd_pcm_substream *substream) + { + struct snd_pcm_runtime *runtime = substream->runtime; +@@ -341,7 +350,10 @@ static int dummy_systimer_create(struct snd_pcm_substream *substream) + + static void dummy_systimer_free(struct snd_pcm_substream *substream) + { +- kfree(substream->runtime->private_data); ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_shutdown_sync(&dpcm->timer); ++ kfree(dpcm); + } + + static const struct dummy_timer_ops dummy_systimer_ops = { +@@ -350,6 +362,7 @@ static const struct dummy_timer_ops dummy_systimer_ops = { + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, ++ .sync_stop = dummy_systimer_sync_stop, + .pointer = dummy_systimer_pointer, + }; + +@@ -407,9 +420,12 @@ static int dummy_hrtimer_stop(struct snd_pcm_substream *substream) + return 0; + } + +-static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm) ++static int dummy_hrtimer_sync_stop(struct snd_pcm_substream *substream) + { ++ struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; ++ + hrtimer_cancel(&dpcm->timer); ++ return 0; + } + + static snd_pcm_uframes_t +@@ -435,7 +451,6 @@ static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream) + long sec; + unsigned long nsecs; + +- dummy_hrtimer_sync(dpcm); + period = runtime->period_size; + rate = runtime->rate; + sec = period / rate; +@@ -463,7 +478,7 @@ static int dummy_hrtimer_create(struct snd_pcm_substream *substream) + static void dummy_hrtimer_free(struct snd_pcm_substream *substream) + { + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; +- dummy_hrtimer_sync(dpcm); ++ + kfree(dpcm); + } + +@@ -473,6 +488,7 @@ static const struct dummy_timer_ops dummy_hrtimer_ops = { + .prepare = dummy_hrtimer_prepare, + .start = dummy_hrtimer_start, + .stop = dummy_hrtimer_stop, ++ .sync_stop = dummy_hrtimer_sync_stop, + .pointer = dummy_hrtimer_pointer, + }; + +@@ -495,6 +511,13 @@ static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) + return -EINVAL; + } + ++static int dummy_pcm_sync_stop(struct snd_pcm_substream *substream) ++{ ++ if (get_dummy_ops(substream)->sync_stop) ++ return get_dummy_ops(substream)->sync_stop(substream); ++ return 0; ++} ++ + static int dummy_pcm_prepare(struct snd_pcm_substream *substream) + { + return get_dummy_ops(substream)->prepare(substream); +@@ -646,6 +669,7 @@ static const struct snd_pcm_ops dummy_pcm_ops = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + }; + +@@ -655,6 +679,7 @@ static const struct snd_pcm_ops dummy_pcm_ops_no_buf = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + .copy = dummy_pcm_copy, + .fill_silence = dummy_pcm_silence, +diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c +--- a/sound/hda/codecs/ca0132.c ++++ b/sound/hda/codecs/ca0132.c +@@ -5788,7 +5788,8 @@ static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; + if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) + uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; +- sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); ++ snprintf(namestr, sizeof(namestr), "%d %s", ++ (uinfo->value.enumerated.item * 10), sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5840,9 +5841,9 @@ static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX; + if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX) + uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1; +- sprintf(namestr, "%s %s", +- ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, +- sfx); ++ snprintf(namestr, sizeof(namestr), "%s %s", ++ ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, ++ sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5894,8 +5895,8 @@ static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX; + if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX) + uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1; +- sprintf(namestr, "%s", +- ae5_filter_presets[uinfo->value.enumerated.item].name); ++ snprintf(namestr, sizeof(namestr), "%s", ++ ae5_filter_presets[uinfo->value.enumerated.item].name); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -6632,7 +6633,7 @@ static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + +- sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Volume", pfx, dirstr[dir]); + + knew.tlv.c = NULL; + +@@ -6671,9 +6672,9 @@ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, + * prefix to OutFX or InFX enable controls. + */ + if (ca0132_use_alt_controls(spec) && (nid <= IN_EFFECT_END_NID)) +- sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Switch", pfx, dirstr[dir]); + else +- sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "%s %s Switch", pfx, dirstr[dir]); + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + } +@@ -8530,10 +8531,9 @@ static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) + + static bool ca0132_download_dsp_images(struct hda_codec *codec) + { +- bool dsp_loaded = false; + struct ca0132_spec *spec = codec->spec; + const struct dsp_image_seg *dsp_os_image; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + /* + * Alternate firmwares for different variants. The Recon3Di apparently + * can use the default firmware, but I'll leave the option in case +@@ -8573,15 +8573,10 @@ static bool ca0132_download_dsp_images(struct hda_codec *codec) + dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); + if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) { + codec_err(codec, "ca0132 DSP load image failed\n"); +- goto exit_download; ++ return false; + } + +- dsp_loaded = dspload_wait_loaded(codec); +- +-exit_download: +- release_firmware(fw_entry); +- +- return dsp_loaded; ++ return dspload_wait_loaded(codec); + } + + static void ca0132_download_dsp(struct hda_codec *codec) +@@ -9634,6 +9629,7 @@ static void ca0132_free(struct hda_codec *codec) + #endif + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void dbpro_free(struct hda_codec *codec) +@@ -9644,6 +9640,7 @@ static void dbpro_free(struct hda_codec *codec) + + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void ca0132_config(struct hda_codec *codec) +diff --git a/sound/hda/codecs/cm9825.c b/sound/hda/codecs/cm9825.c +--- a/sound/hda/codecs/cm9825.c ++++ b/sound/hda/codecs/cm9825.c +@@ -15,7 +15,8 @@ + + enum { + QUIRK_CM_STD = 0x0, +- QUIRK_GENE_TWL7_SSID = 0x160dc000 ++ QUIRK_GENE_TWL7_SSID = 0x160dc000, ++ QUIRK_IBP_SSID = 0x15bd3275 + }; + + /* CM9825 Offset Definitions */ +@@ -40,13 +41,19 @@ enum { + #define CM9825_VERB_SET_GAD 0x7b4 + #define CM9825_VERB_SET_TMOD 0x7b5 + #define CM9825_VERB_SET_SNR 0x7b6 ++#define CM9825_VERB_SET_OMTP 0x7ef ++#define CM9825_VERB_READ_OMTP 0xfec + + struct cmi_spec { + struct hda_gen_spec gen; + const struct hda_verb *chip_d0_verbs; + const struct hda_verb *chip_d3_verbs; ++ const struct hda_verb *chip_playback_start_verbs; ++ const struct hda_verb *chip_playback_stop_verbs; + const struct hda_verb *chip_hp_present_verbs; + const struct hda_verb *chip_hp_remove_verbs; ++ const struct hda_verb *chip_lineout_present_verbs; ++ const struct hda_verb *chip_lineout_remove_verbs; + struct hda_codec *codec; + struct delayed_work unsol_inputs_work; + struct delayed_work unsol_lineout_work; +@@ -193,6 +200,94 @@ static const struct hda_verb cm9825_gene_twl7_playback_stop_verbs[] = { + {} + }; + ++/* ++ * To save power, AD/CLK is turned off. ++ */ ++static const struct hda_verb cm9825_ibp_d3_verbs[] = { ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_PLL, 0x01}, ++ {0x43, CM9825_VERB_SET_NEG, 0xc2}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x50}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x04}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf6}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {} ++}; ++ ++/* ++ * D0 configuration: enable PLL/CLK/ADC/DAC and optimize performance ++ */ ++static const struct hda_verb cm9825_ibp_d0_verbs[] = { ++ {0x34, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {0x43, CM9825_VERB_SET_PLL, 0x00}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x56}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_DACTRL, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x0c}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf4}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {0x43, CM9825_VERB_SET_MTCBA, 0x61}, ++ {0x43, CM9825_VERB_SET_OCP, 0x33}, ++ {0x43, CM9825_VERB_SET_GAD, 0x07}, ++ {0x43, CM9825_VERB_SET_TMOD, 0x26}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0xa0, 0x2d}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0x90, 0x2d}, ++ {0x43, CM9825_VERB_SET_HPF_1, 0x40}, ++ {0x43, CM9825_VERB_SET_HPF_2, 0x40}, ++ {} ++}; ++ ++/* ++ * Enable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_present_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x8c}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x10}, ++ {} ++}; ++ ++/* ++ * Disable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_remove_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {} ++}; ++ ++/* ++ * Turn on the DAC (widget NID 0x43) in the playback path by writing ++ * a sequence of vendor-specific verbs. ++ */ ++static const struct hda_verb cm9825_ibp_playback_start_verbs[] = { ++ {0x43, CM9825_VERB_SET_DACL, 0xaa}, ++ {0x43, CM9825_VERB_SET_D2S, 0xf2}, ++ {0x43, CM9825_VERB_SET_VDO, 0xc4}, ++ {0x43, CM9825_VERB_SET_SNR, 0x30}, ++ {} ++}; ++ ++/* ++ * Shut down the playback path. The order differs slightly from the ++ * enable sequence, likely to avoid audible pop noise when powering ++ * down the output stage. ++ */ ++static const struct hda_verb cm9825_ibp_playback_stop_verbs[] = { ++ {0x43, CM9825_VERB_SET_VDO, 0xc0}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_VDO, 0x80}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {} ++}; ++ + static void cm9825_update_jk_plug_status(struct hda_codec *codec, hda_nid_t nid) + { + struct cmi_spec *spec = codec->spec; +@@ -232,6 +327,23 @@ static void cm9825_unsol_lineout_delayed(struct work_struct *work) + struct cmi_spec *spec = + container_of(to_delayed_work(work), struct cmi_spec, + unsol_lineout_work); ++ hda_nid_t line_out_pin = spec->gen.autocfg.line_out_pins[0]; ++ bool line_out_jack_plugin = false; ++ ++ line_out_jack_plugin = snd_hda_jack_detect(spec->codec, line_out_pin); ++ ++ codec_dbg(spec->codec, "lineout_jack_plugin %d, lineout_pin 0x%X\n", ++ (int)line_out_jack_plugin, line_out_pin); ++ ++ if (!line_out_jack_plugin) { ++ /* Jack plugout */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_remove_verbs); ++ } else { ++ /* Jack plugin */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_present_verbs); ++ } + + cm9825_update_jk_plug_status(spec->codec, + spec->gen.autocfg.line_out_pins[0]); +@@ -362,12 +474,10 @@ static void cm9825_playback_pcm_hook(struct hda_pcm_stream *hinfo, + + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_start_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_start_verbs); + break; + case HDA_GEN_PCM_ACT_CLEANUP: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_stop_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_stop_verbs); + break; + default: + return; +@@ -471,7 +581,8 @@ static int cm9825_resume(struct hda_codec *codec) + + if (codec->core.subsystem_id == QUIRK_CM_STD) + cm9825_cm_std_resume(codec); +- else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID) { ++ else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID || ++ codec->core.subsystem_id == QUIRK_IBP_SSID) { + snd_hda_codec_init(codec); + snd_hda_sequence_write(codec, spec->chip_d0_verbs); + } +@@ -487,6 +598,7 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + struct cmi_spec *spec; + struct auto_pin_cfg *cfg; + int err = 0; ++ unsigned int val; + + spec = kzalloc_obj(*spec); + if (spec == NULL) +@@ -523,9 +635,33 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->chip_d0_verbs = cm9825_gene_twl7_d0_verbs; + spec->chip_d3_verbs = cm9825_gene_twl7_d3_verbs; + spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_playback_start_verbs = ++ cm9825_gene_twl7_playback_start_verbs; ++ spec->chip_playback_stop_verbs = ++ cm9825_gene_twl7_playback_stop_verbs; + /* Internal fixed device, Rear, Mic-in, 3.5mm */ + snd_hda_codec_set_pincfg(codec, 0x37, 0x24A70100); + break; ++ case QUIRK_IBP_SSID: ++ snd_hda_codec_set_name(codec, "CM9825 IBP"); ++ spec->chip_d0_verbs = cm9825_ibp_d0_verbs; ++ spec->chip_d3_verbs = cm9825_ibp_d3_verbs; ++ spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_lineout_present_verbs = ++ cm9825_ibp_lineout_present_verbs; ++ spec->chip_lineout_remove_verbs = ++ cm9825_ibp_lineout_remove_verbs; ++ spec->chip_playback_start_verbs = ++ cm9825_ibp_playback_start_verbs; ++ spec->chip_playback_stop_verbs = cm9825_ibp_playback_stop_verbs; ++ ++ /* OMTP */ ++ val = ++ snd_hda_codec_read(codec, 0x46, 0, CM9825_VERB_READ_OMTP, ++ 0x0); ++ snd_hda_codec_write(codec, 0x46, 0, CM9825_VERB_SET_OMTP, ++ (val >> 24) & 0x7f); ++ break; + default: + err = -ENXIO; + break; +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -292,6 +292,7 @@ enum { + CXT_PINCFG_TOP_SPEAKER, + CXT_FIXUP_HP_A_U, + CXT_FIXUP_ACER_SWIFT_HP, ++ CXT_FIXUP_HUAWEI_MATEBOOK_HP, + }; + + /* for hda_fixup_thinkpad_acpi() */ +@@ -451,7 +452,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + if (!spec->dc_enable) { + /* disable DC bias path and pin for port F */ + update_mic_pin(codec, 0x1e, 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, false, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, false, false); + + /* update port B (ext mic) and C (int mic) */ + /* OLPC defers mic widget control until when capture is +@@ -487,7 +489,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + update_mic_pin(codec, 0x1b, 0); + /* enable DC bias path and pin */ + update_mic_pin(codec, 0x1e, spec->recording ? PIN_IN : 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, true, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, true, false); + } + } + +@@ -1033,6 +1036,13 @@ static const struct hda_fixup cxt_fixups[] = { + { } + }, + }, ++ [CXT_FIXUP_HUAWEI_MATEBOOK_HP] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x18, 0x03211020 }, /* Headphone */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk cxt5045_fixups[] = { +@@ -1097,6 +1107,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO), ++ SND_PCI_QUIRK(0x103c, 0x837b, "HP ProBook 440 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x83b3, "HP EliteBook 830 G5", CXT_FIXUP_HP_DOCK), +@@ -1133,6 +1144,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), ++ SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), + SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), + SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), +diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c +--- a/sound/hda/codecs/generic.c ++++ b/sound/hda/codecs/generic.c +@@ -2695,30 +2695,13 @@ static const struct snd_kcontrol_new out_jack_mode_enum = { + .put = out_jack_mode_put, + }; + +-static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx) +-{ +- struct hda_gen_spec *spec = codec->spec; +- const struct snd_kcontrol_new *kctl; +- int i; +- +- snd_array_for_each(&spec->kctls, i, kctl) { +- if (!strcmp(kctl->name, name) && kctl->index == idx) +- return true; +- } +- return false; +-} +- + static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin, + char *name, size_t name_len) + { + struct hda_gen_spec *spec = codec->spec; +- int idx = 0; + +- snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx); +- strlcat(name, " Jack Mode", name_len); +- +- for (; find_kctl_name(codec, name, idx); idx++) +- ; ++ snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len); ++ hda_append_suffix(name, " Jack Mode", name_len); + } + + static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin) +@@ -5695,7 +5678,7 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx, + break; + } + } +- strlcat(str, sfx, len); ++ hda_append_suffix(str, sfx, len); + } + + /* copy PCM stream info from @default_str, and override non-NULL entries +diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c +--- a/sound/hda/codecs/hdmi/hdmi.c ++++ b/sound/hda/codecs/hdmi/hdmi.c +@@ -2236,8 +2236,10 @@ void snd_hda_hdmi_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ +- if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) ++ if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) { ++ codec->acomp_requested_resume = 1; + return; ++ } + + snd_hda_hdmi_check_presence_and_report(codec, pin_nid, dev_id); + } +@@ -2346,6 +2348,7 @@ static const struct hda_device_id snd_hda_id_generichdmi[] = { + HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), ++ HDA_CODEC_ID_MODEL(0x4c545020, "Lisuan HDMI/DP", MODEL_GENERIC), + HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), +diff --git a/sound/hda/codecs/helpers/razer_blade16_2025.c b/sound/hda/codecs/helpers/razer_blade16_2025.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/helpers/razer_blade16_2025.c +@@ -0,0 +1,820 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Razer Blade 16 (2025) external smart-amplifier programming and fixup, ++ * to be included from the codec driver. ++ * ++ * The internal speakers are driven by an external smart amplifier whose ++ * crossover, voicing and protection live in a DSP reached over a vendor ++ * coef mailbox on NID 0x20. These tables hold the programming captured ++ * from the Windows driver, reduced to the NID-0x20 traffic this machine ++ * needs (everything else the HDA parser rebuilds). ++ * ++ * Each entry is either a plain codec coef write or a mailbox write: ++ * strobe == 0: coef[addr] = lo ++ * strobe != 0: amp[addr] = (hi << 16) | lo, with the bank selected on ++ * port 0x89 and the write committed by the strobe opcode. ++ */ ++ ++struct alc298_razer_blade16_2025_op { ++ u16 bank; /* mailbox bank (port 0x89); unused for a plain coef */ ++ u16 addr; /* mailbox register, or plain coef index */ ++ u16 hi; /* mailbox high word; unused for a plain coef */ ++ u16 lo; /* mailbox low word, or plain coef value */ ++ u16 strobe; /* mailbox commit opcode; 0 marks a plain coef write */ ++}; ++ ++/* program the external DSP at init; the amp is woken/parked separately */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_init[] = { ++ { 0x0000, 0xf102, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf103, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc000, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xf20d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf212, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xc001, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc003, 0x0000, 0x0022, 0xb031 }, ++ { 0x0000, 0xc004, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc005, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc007, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xc00e, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xf223, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xf224, 0x0000, 0x00db, 0xb031 }, ++ { 0x0000, 0xf225, 0x0000, 0x00ee, 0xb031 }, ++ { 0x0000, 0xf226, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xf227, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xf21a, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf242, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc120, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc200, 0x0000, 0x00d8, 0xb031 }, ++ { 0x0000, 0xc201, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xc202, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xc400, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc401, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc402, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xc403, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc404, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xc406, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc407, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc408, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xc300, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdf5f, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xdf60, 0x0000, 0x00a7, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x0084, 0xb031 }, ++ { 0x0000, 0xc206, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf10a, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0065, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xe706, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe707, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xe806, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe807, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xce04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xce05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xce07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xcf04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xcf05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xcf06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xcf07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xc130, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xe000, 0x0000, 0x00a8, 0xb031 }, ++ { 0x4100, 0x1888, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xed00, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xcc2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcc2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcc2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcd2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcd2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcc26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcc27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcd24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcd26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcd27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcc20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcc17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcd17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcc2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcd29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcd2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcc31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcc32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcc37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcd37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcc0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcc0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcd09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcd0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcd0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcc0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcc10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcc11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xcd0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcd10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcd11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xccd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccdb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcddb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc320, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xe604, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdd00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdc19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xdc1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xdc1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xdc1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xdc1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xdc20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xde19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xde1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xde1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xde1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xde1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xde20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xdb32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdd33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdb34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdd34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdb15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdd15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdd17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdb94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdd94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdb19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x001c, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdbbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xdbbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdbbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xdbbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xddbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xddbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xddbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xddbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xdb01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdd01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdc52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xde52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xdf0a, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdf0b, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdf01, 0x0000, 0x0073, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xebcb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xeb13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xeb14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xeb16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xeb21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xeb22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xeb23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xeb27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xeb2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xeb2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xeb30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xeb37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xeb40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xeb43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xeb44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xeb4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xeb4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xeb52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xeb53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xeb56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xeb57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xeb60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xeb63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xeb65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xeb66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xeb67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xeb6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xeb6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xebb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xeb75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xeb77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xebb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xeb92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xeb94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xeb95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xeb96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeba0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeba2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeba3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeba4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeba5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeba7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeba8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeba9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xebaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xebab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xeb01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xeb02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xeb03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xeb05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xeb06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xeb07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd0cb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xd0b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd010, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd011, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd012, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd013, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd014, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd015, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd016, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd017, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd020, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd021, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd022, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd023, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd024, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd025, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd026, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd027, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd028, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd029, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd02a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd02b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce6a, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xcc00, 0x0000, 0x00c1, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca62, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca65, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca68, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca75, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca7b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc860, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc861, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc862, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc863, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc864, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc865, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc867, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc868, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc869, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc86a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc86b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc875, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc87b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc836, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc833, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc834, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc83f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc83e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc83a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc83b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc830, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc826, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc890, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc823, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc824, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc82f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc82e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc820, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc816, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc880, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc813, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc814, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc81f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc81e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc81a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc81b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc810, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc846, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc844, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc84f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc84e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc84a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc84b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc500, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc501, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc502, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc503, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc504, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc505, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc506, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc507, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc510, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc511, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc512, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc513, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc514, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc515, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc516, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc517, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc518, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd251, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xecb0, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xec13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xec14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xec16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xec21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xec22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xec23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xec24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xec27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xec2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xec2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb2, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xec37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xec40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xec43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xec44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xec4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xec4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xecb4, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xec52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xec53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xec56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xec57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xec63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xec65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xec66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xec67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xec6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xec6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xec75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xec77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xecb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xec92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xec94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xec95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xec96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeca0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeca2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeca3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeca4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeca5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeca7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeca8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeca9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xecaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xecab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xec01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xec02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xec03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xec05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xec06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xec07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd1b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd110, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd111, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd112, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd113, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd114, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd115, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd116, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd117, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd120, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd121, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd122, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd123, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd124, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd125, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd126, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd127, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd128, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd129, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd12a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd12b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca82, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca85, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca88, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca95, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca9b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc960, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc961, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc962, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc963, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc964, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc965, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc967, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc968, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc969, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc96a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc96b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc975, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc97b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc936, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc933, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc934, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc93f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc93e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc93a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc93b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc930, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc926, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc990, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc923, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc924, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc92f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc92e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc920, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc916, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc980, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc913, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc914, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc91f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc91e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc91a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc91b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc910, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc946, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc944, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc94f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc94e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc94a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc94b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc600, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc601, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc602, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc603, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc604, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc605, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc606, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc607, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc610, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc611, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc612, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc613, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc614, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc615, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc616, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc617, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc618, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xc802, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc902, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd252, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdbb5, 0x0041, 0xd27f, 0xb037 }, ++ { 0x0000, 0xddb5, 0x0040, 0x6e3b, 0xb037 }, ++ { 0x0000, 0xdb93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdd93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdb12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdd12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0x0010, 0x0000, 0x0f21, 0x0000 }, ++}; ++ ++/* wake the amp (ea00=0x43, c121=0x0b, f109=0xe0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_wake[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++}; ++ ++/* park the amp (ea00=0x47, c121=0x0a, f109=0xa0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_sleep[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00a0, 0xb031 }, ++}; ++ ++/* ++ * Razer Blade 16 (2025): Realtek ALC298 driving an external smart amp. ++ * Crossover, voicing and speaker protection live in an external DSP ++ * reached over a vendor-coef mailbox on NID 0x20; the captured DSP ++ * programming is in the tables above. NID 0x14, the BIOS-disabled ++ * tweeter pin, is re-exposed via a pin-config override so the parser ++ * drives it as a normal internal speaker (shared volume/mute, auto-mute ++ * on headphone insertion). ++ * ++ * alc298_razer_blade16_2025_apply() runs one op-table: a plain coef write ++ * (strobe == 0) goes through alc_write_coef_idx(), while a mailbox op ++ * selects a bank and streams four PROC_COEF words (addr/hi/lo/commit) ++ * into a single coef index (0x23) on NID 0x20 to reach the external DSP ++ * -- that one-index/many-words form is why it cannot be expressed as an ++ * alc_process_coef_fw() table (one value per index). ++ */ ++static void alc298_razer_blade16_2025_apply(struct hda_codec *codec, ++ const struct alc298_razer_blade16_2025_op *seq, ++ int num) ++{ ++ int i; ++ ++ for (i = 0; i < num; i++) { ++ if (!seq[i].strobe) { ++ alc_write_coef_idx(codec, seq[i].addr, seq[i].lo); ++ continue; ++ } ++ alc_write_coef_idx(codec, 0x89, seq[i].bank); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x23); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].addr); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].hi); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].lo); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].strobe); ++ } ++} ++ ++static void alc298_razer_blade16_2025_pcm_hook(struct hda_pcm_stream *hinfo, ++ struct hda_codec *codec, ++ struct snd_pcm_substream *substream, ++ int action) ++{ ++ /* power the external amp only while a stream is running */ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_PREPARE: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} ++ ++static void alc298_fixup_razer_blade16_2025(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Pin every output to a fixed converter so the routing does not ++ * rely on the generic parser's DAC-allocation heuristics: without ++ * a fixed assignment the parser brings up only one speaker pin and ++ * leaves the other silent, and the allocation could change between ++ * kernel versions. Both speaker pins share DAC 0x03 through mixer ++ * 0x0d: that is the only converter that drives the woofer (NID ++ * 0x17) on this machine -- feeding it from DAC 0x02 or the digital ++ * converter 0x06 leaves the woofer silent. Both pins carry the ++ * same stereo stream and the external amp does the crossover. ++ * Headphone NID 0x21 keeps DAC 0x02. ++ */ ++ static const hda_nid_t preferred_pairs[] = { ++ 0x21, 0x02, ++ 0x14, 0x03, ++ 0x17, 0x03, ++ 0 ++ }; ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.preferred_dacs = preferred_pairs; ++ break; ++ case HDA_FIXUP_ACT_PROBE: ++ spec->gen.pcm_playback_hook = alc298_razer_blade16_2025_pcm_hook; ++ break; ++ case HDA_FIXUP_ACT_INIT: ++ /* ++ * Wake the amp, program its DSP at boot and on resume, then ++ * park it. It is woken again for playback and parked ++ * afterwards from the pcm_playback_hook. ++ */ ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_init, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_init)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -296,7 +296,8 @@ static void alc282_init(struct hda_codec *codec) + msleep(100); + + /* Headphone capless set to normal mode */ +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static void alc282_shutup(struct hda_codec *codec) +@@ -333,7 +334,8 @@ static void alc282_shutup(struct hda_codec *codec) + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static const struct coef_fw alc283_coefs[] = { +@@ -585,15 +587,19 @@ static void alc285_hp_init(struct hda_codec *codec) + + alc_write_coefex_idx(codec, 0x58, 0x00, 0xf888); /* HP depop procedure start */ + val = alc_read_coefex_idx(codec, 0x58, 0x00); +- for (i = 0; i < 20 && val & 0x8000; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x8000; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x00); + } /* Wait for depop procedure finish */ + +- alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ +- alc_update_coef_idx(codec, 0x38, 1<<4, coef38); +- alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); +- alc_update_coef_idx(codec, 0x36, 3<<13, coef36); ++ if (val != -1) ++ alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ ++ if (coef38 != -1) ++ alc_update_coef_idx(codec, 0x38, 1<<4, coef38); ++ if (coef0d != -1) ++ alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); ++ if (coef36 != -1) ++ alc_update_coef_idx(codec, 0x36, 3<<13, coef36); + + msleep(50); + alc_update_coef_idx(codec, 0x4a, 1<<15, 0); +@@ -858,7 +864,7 @@ static void alc294_hp_init(struct hda_codec *codec) + + /* Wait for depop procedure finish */ + val = alc_read_coefex_idx(codec, 0x58, 0x01); +- for (i = 0; i < 20 && val & 0x0080; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x0080; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x01); + } +@@ -1392,6 +1398,15 @@ static void alc245_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); + } + ++static void alc245_fixup_minisforum_jack_detect(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ if (action == HDA_FIXUP_ACT_INIT) ++ /* Clear Reset HP JD while preserving the other coefficient bits. */ ++ alc_update_coef_idx(codec, 0x4a, BIT(15), 0); ++} ++ + /* turn on/off mic-mute LED per capture hook via VREF change */ + static int vref_micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +@@ -2557,6 +2572,13 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, + } + } + ++static void alc285_lenovo_dac_rename(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ if (action == HDA_FIXUP_ACT_BUILD) ++ rename_ctl(codec, "Line Out Playback Volume", ++ "Headphone Playback Volume"); ++} + static void alc290_fixup_mono_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3262,6 +3284,36 @@ static void find_cirrus_companion_amps(struct hda_codec *cdc) + comp_generic_fixup(cdc, HDA_FIXUP_ACT_PRE_PROBE, bus, acpi_ids[i].hid, match, count); + } + ++static void aw88399_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) ++{ ++ comp_generic_fixup(cdc, action, "i2c", "AWDZ8399", "-%s:00-aw88399-hda.%d", 2); ++} ++ ++static void alc287_fixup_legion_16iax10h_aw88399(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Force DAC 0x02 for the bass speaker 0x17, as the default 0x06 lacks volume controls. ++ */ ++ static const hda_nid_t conn[] = { 0x02 }; ++ struct alc_spec *spec = codec->spec; ++ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++ alc_fixup_headset_mode_no_hp_mic(codec, fix, action); ++ alc_fixup_headset_jack(codec, fix, action); ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.suppress_auto_mic = 1; ++ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); ++ break; ++ case HDA_FIXUP_ACT_BUILD: ++ spec->gen.multiout.max_channels = 2; ++ spec->gen.pcm_rec[0]->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 2; ++ break; ++ } ++} ++ + static void cs35l41_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) + { + comp_generic_fixup(cdc, action, "i2c", "CSC3551", "-%s:00-cs35l41-hda.%d", 2); +@@ -3357,6 +3409,9 @@ static void alc287_fixup_acer_micmute_led(struct hda_codec *codec, + /* for alc285_fixup_ideapad_s740_coef() */ + #include "../helpers/ideapad_s740.c" + ++/* for alc298_fixup_razer_blade16_2025() */ ++#include "../helpers/razer_blade16_2025.c" ++ + static const struct coef_fw alc256_fixup_set_coef_defaults_coefs[] = { + WRITE_COEF(0x10, 0x0020), WRITE_COEF(0x24, 0x0000), + WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x29, 0x3000), +@@ -3939,6 +3994,7 @@ enum { + ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, + ALC269_FIXUP_ACER_AC700, + ALC269_FIXUP_LIMIT_INT_MIC_BOOST, ++ ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST, + ALC269VB_FIXUP_ASUS_ZENBOOK, + ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, + ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4007,6 +4063,7 @@ enum { + ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_FIXUP_DISABLE_DAC3, + ALC285_FIXUP_SPEAKER2_TO_DAC1, ++ ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_HEADSET_MIC, + ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS, +@@ -4076,7 +4133,6 @@ enum { + ALC294_FIXUP_ASUS_ALLY, + ALC294_FIXUP_ASUS_ALLY_PINS, + ALC294_FIXUP_ASUS_ALLY_VERBS, +- ALC294_FIXUP_ASUS_ALLY_SPEAKER, + ALC294_FIXUP_ASUS_HPE, + ALC294_FIXUP_ASUS_COEF_1B, + ALC294_FIXUP_ASUS_GX502_HP, +@@ -4101,6 +4157,8 @@ enum { + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, + ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, + ALC298_FIXUP_LG_GRAM_STYLE_14, ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS, ++ ALC298_FIXUP_RAZER_BLADE16_2025, + ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4118,7 +4176,6 @@ enum { + ALC269_FIXUP_LEMOTE_A190X, + ALC256_FIXUP_INTEL_NUC8_RUGGED, + ALC233_FIXUP_INTEL_NUC8_DMIC, +- ALC233_FIXUP_INTEL_NUC8_BOOST, + ALC256_FIXUP_INTEL_NUC10, + ALC255_FIXUP_XIAOMI_HEADSET_MIC, + ALC274_FIXUP_HP_MIC, +@@ -4149,6 +4206,7 @@ enum { + ALC298_FIXUP_LENOVO_C940_DUET7, + ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, + ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK, + ALC287_FIXUP_13S_GEN2_SPEAKERS, + ALC256_FIXUP_SET_COEF_DEFAULTS, + ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, +@@ -4193,7 +4251,6 @@ enum { + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC2XX_FIXUP_HEADSET_MIC, + ALC289_FIXUP_DELL_CS35L41_SPI_2, +- ALC294_FIXUP_CS35L41_I2C_2, + ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_HEADPHONE_AMP_VOL, + ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, +@@ -4207,6 +4264,7 @@ enum { + ALC287_FIXUP_LENOVO_THKPAD_WH_ALC1318, + ALC256_FIXUP_CHROME_BOOK, + ALC245_FIXUP_CLEVO_NOISY_MIC, ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT, + ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE, + ALC233_FIXUP_MEDION_MTL_SPK, + ALC233_FIXUP_STARLABS_STARFIGHTER, +@@ -4216,7 +4274,6 @@ enum { + ALC274_FIXUP_HP_AIO_BIND_DACS, + ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2, + ALC285_FIXUP_ASUS_GA605K_HEADSET_MIC, +- ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1, + ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC, + ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK, + ALC256_FIXUP_VAIO_RPL_MIC_NO_PRESENCE, +@@ -4236,6 +4293,10 @@ enum { + ALC236_FIXUP_DELL_HP_POP_NOISE, + ALC274_FIXUP_HP_89E9_GPIO, + ALC274_FIXUP_HP_VERBS, ++ ALC287_FIXUP_AW88399_I2C_2, ++ ALC287_FIXUP_LENOVO_LEGION_AW88399, ++ ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ++ ALC285_LENOVO_DAC_RENAME, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4273,6 +4334,20 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + } + + static const struct hda_fixup alc269_fixups[] = { ++ [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170121 }, /* tweeter as internal speaker, seq 1 */ ++ { 0x17, 0x90170120 }, /* woofer as internal speaker, seq 0 */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC298_FIXUP_RAZER_BLADE16_2025, ++ }, ++ [ALC298_FIXUP_RAZER_BLADE16_2025] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc298_fixup_razer_blade16_2025, ++ }, + [ALC233_FIXUP_WUJIE_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -4713,6 +4788,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chain_id = ALC271_FIXUP_DMIC, + }, + [ALC269_FIXUP_LIMIT_INT_MIC_BOOST] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc269_fixup_limit_int_mic_boost ++ }, ++ [ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, +@@ -5088,11 +5167,7 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + .chained = true, +- .chain_id = ALC233_FIXUP_INTEL_NUC8_BOOST, +- }, +- [ALC233_FIXUP_INTEL_NUC8_BOOST] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc269_fixup_limit_int_mic_boost ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC255_FIXUP_DELL_SPK_NOISE] = { + .type = HDA_FIXUP_FUNC, +@@ -5153,6 +5228,10 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC285_FIXUP_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, ++ }, ++ [ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, +@@ -5672,11 +5751,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC294_FIXUP_ASUS_ALLY_SPEAKER +- }, +- [ALC294_FIXUP_ASUS_ALLY_SPEAKER] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC285_FIXUP_THINKPAD_X1_GEN7] = { + .type = HDA_FIXUP_FUNC, +@@ -6223,6 +6298,16 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_LENOVO_XPAD_ACPI, + }, ++ [ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ /* Same as ALC285_FIXUP_THINKPAD_HEADSET_JACK, except that the ++ * mute LEDs are driven through ideapad_laptop rather than ++ * thinkpad_acpi. ++ */ ++ .chain_id = ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ }, + [ALC623_FIXUP_LENOVO_THINKSTATION_P340] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_shutup, +@@ -6662,10 +6747,6 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC289_FIXUP_DUAL_SPK + }, +- [ALC294_FIXUP_CS35L41_I2C_2] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = cs35l41_fixup_i2c_two, +- }, + [ALC256_FIXUP_ACER_SFG16_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, +@@ -6738,6 +6819,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, + }, ++ [ALC245_FIXUP_MINISFORUM_JACK_DETECT] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc245_fixup_minisforum_jack_detect, ++ }, + [ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -6758,6 +6843,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC233_FIXUP_STARLABS_STARFIGHTER] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_starlabs_starfighter, ++ .chained = true, ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC294_FIXUP_BASS_SPEAKER_15] = { + .type = HDA_FIXUP_FUNC, +@@ -6785,11 +6872,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1 +- }, +- [ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, +@@ -6907,6 +6990,26 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC274_FIXUP_HP_89E9_GPIO, + }, ++ [ALC287_FIXUP_AW88399_I2C_2] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = aw88399_fixup_i2c_two, ++ }, ++ [ALC287_FIXUP_LENOVO_LEGION_AW88399] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc287_fixup_legion_16iax10h_aw88399, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_AW88399_I2C_2, ++ }, ++ [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ++ }, ++ [ALC285_LENOVO_DAC_RENAME] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_lenovo_dac_rename, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -6960,8 +7063,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), +@@ -6970,6 +7075,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x1844, "Acer Helios ZPS", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1025, 0x1909, "Acer Nitro ANV16-42", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z), + SND_PCI_QUIRK(0x1028, 0x053c, "Dell Latitude E5430", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x054b, "Dell XPS one 2710", ALC275_FIXUP_DELL_XPS), +@@ -7154,10 +7260,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), ++ SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), +@@ -7268,6 +7376,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x89e7, "HP Elite x2 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a05, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a06, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x8a0e, "HP Pavilion Laptop 15-eh2xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a0f, "HP Pavilion 14-ec1xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a1b, "HP 255 15.6 inch G9 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8a1f, "HP Laptop 14s-dr5xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7359,10 +7468,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), + SND_PCI_QUIRK(0x103c, 0x8c16, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), +- SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre 16", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c47, "HP EliteBook 840 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c48, "HP EliteBook 860 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7511,6 +7621,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f57, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x8f7e, "HP Messi", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f94, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f95, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x1043, 0x1024, "ASUS Zephyrus G14 2025", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), +@@ -7518,6 +7629,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1034, "ASUS GU605C", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), ++ SND_PCI_QUIRK(0x1043, 0x1044, "ASUS GA403UM", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1054, "ASUS G614FH/FM/FP", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x106f, "ASUS VivoBook X515UA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), +@@ -7525,16 +7637,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), ++ SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1194, "ASUS UM3406KA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), +- HDA_CODEC_QUIRK(0x1043, 0x1204, "ASUS Strix G16 G615JMR", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TAS2781_I2C), +- HDA_CODEC_QUIRK(0x1043, 0x1214, "ASUS ROG Strix G615LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TAS2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TXNW2781_I2C_ASUS), ++ SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x125e, "ASUS Q524UQK", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1271, "ASUS X430UN", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), +@@ -7583,7 +7694,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1863, "ASUS UX6404VI/VV", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), + SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), +@@ -7594,7 +7705,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x19f4, "ASUS UM3405GA", ALC294_FIXUP_ASUS_I2C_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), + SND_PCI_QUIRK(0x1043, 0x1a63, "ASUS UX3405MA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1a8e, "ASUS G712LWS", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x1043, 0x1a8f, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1b11, "ASUS UX431DA", ALC294_FIXUP_ASUS_COEF_1B), +@@ -7623,6 +7734,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1264, "ASUS UM5606KA", ALC294_FIXUP_BASS_SPEAKER_15), + SND_PCI_QUIRK(0x1043, 0x1e02, "ASUS UX3402ZA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1e10, "ASUS VivoBook X507UAR", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), ++ SND_PCI_QUIRK(0x1043, 0x1e3e, "ASUS VivoBook M515DA/X515DAP", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502), + SND_PCI_QUIRK(0x1043, 0x1e12, "ASUS UM3402", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1e1f, "ASUS Vivobook 15 X1504VAP", ALC2XX_FIXUP_HEADSET_MIC), +@@ -7634,8 +7746,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1e93, "ASUS ExpertBook B9403CVAR", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RC72LA", ALC287_FIXUP_ASUS_ALLY_X), + SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2), +- HDA_CODEC_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), +- SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x1c52, "ASUS Zephyrus G15 2022", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f11, "ASUS Zephyrus G14", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f12, "ASUS UM5302", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7846,7 +7957,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x2211, "Thinkpad W541", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad T440", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad X240", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2218, "Thinkpad X1 Carbon 2nd", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2223, "ThinkPad T550", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2226, "ThinkPad X250", ALC292_FIXUP_TPT440_DOCK), +@@ -7862,7 +7973,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x225c, "Lenovo ThinkPad X1 Carbon 6th Gen", ALC285_LENOVO_DAC_RENAME), ++ SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2288, "Thinkpad X390", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), +@@ -7937,7 +8049,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_SPEAKER2_TO_DAC1), ++ SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7972,6 +8084,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38b8, "Yoga S780-14.5 proX AMD YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38b9, "Yoga S780-14.5 proX AMD LX Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38ba, "Yoga S780-14.5 Air AMD quad YC", ALC287_FIXUP_TAS2781_I2C), ++ HDA_CODEC_QUIRK(0x17aa, 0x3926, "Legion Pro 5 16ADR10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ /* Legion R9000P ADR10 shares PCI SSID 17aa:38bb with Yoga S780-14.5 Air AMD quad AAC; ++ * use codec SSID to distinguish them ++ */ ++ HDA_CODEC_QUIRK(0x17aa, 0x3927, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3928, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), + SND_PCI_QUIRK(0x17aa, 0x38bb, "Yoga S780-14.5 Air AMD quad AAC", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38be, "Yoga S980-14.5 proX YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38bf, "Yoga S980-14.5 proX LX Dual", ALC287_FIXUP_TAS2781_I2C), +@@ -7989,7 +8107,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C), + /* Legion 7 15ASH11 shares PCI SSID 17aa:38f9 with Thinkbook 16P Gen5; + * use codec SSID to distinguish them + */ +@@ -7999,7 +8116,9 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38fc, "Lenovo Yoga Pro 7 15ASH11", ALC287_FIXUP_LENOVO_YOGA_PRO7), + SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), +- SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ HDA_CODEC_QUIRK(0x17aa, 0x3906, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET), + SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), +@@ -8007,14 +8126,19 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), ++ HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3937, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3938, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3939, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x393e, "Lenovo ThinkBook 14 G8+ IPH", ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK), + HDA_CODEC_QUIRK(0x17aa, 0x394c, "Lenovo Yoga Slim 7 14AGP11", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI), + SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), +- SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x501a, "Thinkpad", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x501e, "Thinkpad L440", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x5034, "Thinkpad T450", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5036, "Thinkpad T450s", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x503c, "Thinkpad L450", ALC292_FIXUP_TPT440_DOCK), +@@ -8027,7 +8151,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x505f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5062, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x508b, "Thinkpad X12 Gen 1", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), +- SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x511e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), +@@ -8041,9 +8165,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1854, 0x0489, "LG gram 16 (16Z90R-A)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x048a, "LG gram 17 (17ZD90R)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x0490, "LG Gram Style 14 (14Z90RS)", ALC298_FIXUP_LG_GRAM_STYLE_14), ++ SND_PCI_QUIRK(0x1854, 0x0554, "LG gram 16 (16Z90TR)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), + SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), ++ SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), + SND_PCI_QUIRK(0x1b35, 0x1235, "CZC B20", ALC269_FIXUP_CZC_B20), + SND_PCI_QUIRK(0x1b35, 0x1236, "CZC TMI", ALC269_FIXUP_CZC_TMI), + SND_PCI_QUIRK(0x1b35, 0x1237, "CZC L101", ALC269_FIXUP_CZC_L101), +@@ -8078,13 +8207,17 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1e39, 0xca14, "MEDION NM14LNL", ALC233_FIXUP_MEDION_MTL_SPK), + SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), + SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), ++ SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), ++ SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +@@ -8095,6 +8228,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), + SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), ++ SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), +@@ -8168,6 +8302,7 @@ static const struct hda_quirk alc269_fixup_vendor_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo XPAD", ALC269_FIXUP_LENOVO_XPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), ++ SND_PCI_QUIRK_VENDOR(0x2145, "Star Labs", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + {} + }; + +@@ -8307,6 +8442,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { + {.id = ALC2XX_FIXUP_HEADSET_MIC, .name = "alc2xx-fixup-headset-mic"}, + {.id = ALC245_FIXUP_BASS_HP_DAC, .name = "alc245-fixup-bass-hp-dac"}, + {.id = ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, .name = "alc256-honor-mrb-xxx-m1020-audio"}, ++ {.id = ALC287_FIXUP_LENOVO_LEGION_AW88399, .name = "alc287-lenovo-legion-aw88399"}, + {} + }; + #define ALC225_STANDARD_PINS \ +@@ -8912,6 +9048,7 @@ static int alc269_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->init_hook = alc256_init; + spec->gen.mixer_nid = 0; /* ALC256 does not have any loopback mixer path */ + if (codec->core.vendor_id == 0x10ec0236 && ++ codec->bus->pci && + codec->bus->pci->vendor != PCI_VENDOR_ID_AMD) + spec->en_3kpull_low = false; + break; +diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c +--- a/sound/hda/codecs/realtek/alc662.c ++++ b/sound/hda/codecs/realtek/alc662.c +@@ -325,6 +325,7 @@ enum { + ALC897_FIXUP_UNIS_H3C_X500S, + ALC897_FIXUP_HEADSET_MIC_PIN3, + ALC662_FIXUP_CSL_GPIO, ++ ALC897_FIXUP_BEELINK_SER6_AMP, + }; + + static const struct hda_fixup alc662_fixups[] = { +@@ -782,6 +783,10 @@ static const struct hda_fixup alc662_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_csl_amp, + }, ++ [ALC897_FIXUP_BEELINK_SER6_AMP] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_gpio4, ++ }, + }; + + static const struct hda_quirk alc662_fixup_tbl[] = { +@@ -853,6 +858,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { + SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), + SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), ++ SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), + + #if 0 + /* Below is a quirk table taken from the old code. +diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig +--- a/sound/hda/codecs/side-codecs/Kconfig ++++ b/sound/hda/codecs/side-codecs/Kconfig +@@ -13,6 +13,24 @@ config SND_HDA_CIRRUS_SCODEC_KUNIT_TEST + Documentation/dev-tools/kunit/. + If in doubt, say "N". + ++config SND_HDA_SCODEC_AW88399 ++ tristate ++ select SND_HDA_GENERIC ++ ++config SND_HDA_SCODEC_AW88399_I2C ++ tristate "Build AW88399 HD-audio side codec support for I2C Bus" ++ depends on I2C ++ depends on ACPI ++ depends on SND_SOC ++ select SND_HDA_SCODEC_AW88399 ++ select SND_SOC_AW88399_LIB ++ help ++ Say Y or M here to include AW88399 I2C HD-audio side codec support ++ in snd-hda-intel driver, such as ALC287. ++ ++comment "Set to Y if you want auto-loading the side codec driver" ++ depends on SND_HDA=y && SND_HDA_SCODEC_AW88399_I2C=m ++ + config SND_HDA_SCODEC_CS35L41 + tristate + select SND_HDA_GENERIC +diff --git a/sound/hda/codecs/side-codecs/Makefile b/sound/hda/codecs/side-codecs/Makefile +--- a/sound/hda/codecs/side-codecs/Makefile ++++ b/sound/hda/codecs/side-codecs/Makefile +@@ -3,6 +3,8 @@ subdir-ccflags-y += -I$(src)/../../common + + snd-hda-cirrus-scodec-y := cirrus_scodec.o + snd-hda-cirrus-scodec-test-y := cirrus_scodec_test.o ++snd-hda-scodec-aw88399-y := aw88399_hda.o ++snd-hda-scodec-aw88399-i2c-y := aw88399_hda_i2c.o + snd-hda-scodec-cs35l41-y := cs35l41_hda.o cs35l41_hda_property.o + snd-hda-scodec-cs35l41-i2c-y := cs35l41_hda_i2c.o + snd-hda-scodec-cs35l41-spi-y := cs35l41_hda_spi.o +@@ -16,6 +18,8 @@ snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o + + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC) += snd-hda-cirrus-scodec.o + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC_KUNIT_TEST) += snd-hda-cirrus-scodec-test.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399) += snd-hda-scodec-aw88399.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399_I2C) += snd-hda-scodec-aw88399-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.c +@@ -0,0 +1,373 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA side codec driver ++// ++// Based on cs35l41_hda.c and aw88399.c ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../generic.h" ++#include "hda_component.h" ++#include "aw88399_hda.h" ++ ++#define AW88399_HDA_I2C_BASE_ADDR 0x34 ++ ++static void aw88399_hda_playback_hook(struct device *dev, int action) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct aw88399 *core = aw88399->core; ++ int ret = 0; ++ ++ dev_dbg(aw88399->dev, "Playback action: %d\n", action); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_OPEN: ++ pm_runtime_get_sync(dev); ++ aw88399->playing = true; ++ break; ++ case HDA_GEN_PCM_ACT_PREPARE: ++ if (core) ++ aw88399_start(core, AW88399_SYNC_START); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ if (aw88399->aw_dev) ++ ret = aw88399_stop(aw88399->aw_dev); ++ if (ret) ++ dev_err(aw88399->dev, "Failed to stop amplifier: %d\n", ret); ++ break; ++ case HDA_GEN_PCM_ACT_CLOSE: ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ aw88399->playing = false; ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_put_autosuspend(dev); ++ break; ++ default: ++ dev_warn(aw88399->dev, "Unsupported action: %d\n", action); ++ break; ++ } ++} ++ ++static int aw88399_hda_bind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (!comp) ++ return -EINVAL; ++ ++ if (comp->dev) ++ return -EBUSY; ++ ++ comp->dev = dev; ++ ++ strscpy(comp->name, dev_name(dev), sizeof(comp->name)); ++ ++ comp->playback_hook = aw88399_hda_playback_hook; ++ ++ dev_info(aw88399->dev, ++ "AW88399 Bound - SSID: %s, channel: %d\n", ++ aw88399->acpi_subsystem_id, aw88399->channel); ++ ++ return 0; ++} ++ ++static void aw88399_hda_unbind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (comp && (comp->dev == dev)) ++ memset(comp, 0, sizeof(*comp)); ++ ++ dev_dbg(aw88399->dev, "Unbound from HDA codec\n"); ++} ++ ++static const struct component_ops aw88399_hda_comp_ops = { ++ .bind = aw88399_hda_bind, ++ .unbind = aw88399_hda_unbind, ++}; ++ ++static int aw88399_hda_index_from_i2c(struct aw88399_hda *aw88399) ++{ ++ return to_i2c_client(aw88399->dev)->addr - AW88399_HDA_I2C_BASE_ADDR; ++} ++ ++static int aw88399_hda_init(struct aw88399_hda *aw88399) ++{ ++ struct device *dev = aw88399->dev; ++ struct i2c_client *i2c = to_i2c_client(dev); ++ struct aw88399 *core; ++ int ret; ++ ++ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); ++ if (!core) ++ return -ENOMEM; ++ ++ mutex_init(&core->lock); ++ core->reset_gpio = aw88399->reset_gpio; ++ core->regmap = aw88399->regmap; ++ core->bsts_unreliable = aw88399->bsts_unreliable; ++ ++ aw88399_hw_reset(core); ++ ++ ret = aw88399_init(core, i2c, aw88399->regmap); ++ if (ret) ++ return ret; ++ ++ /* Set channel BEFORE loading firmware so ACF parser sees correct value */ ++ if (core->aw_pa) ++ aw88399_dev_set_channel(core, aw88399->channel); ++ ++ ret = aw88399_request_firmware_file(core); ++ if (ret) ++ return ret; ++ ++ aw88399->core = core; ++ aw88399->aw_dev = core->aw_pa; ++ ++ return 0; ++} ++ ++static int aw88399_swap_channels(struct aw88399_hda *aw88399) ++{ ++ /* ++ * Certain Lenovo Legion laptops have their ++ * I2C wiring reversed: 0x34 is physically the right speaker, ++ * 0x35 is the left. Swap channels to correct L/R assignment. ++ * This is a model-specific hardware wiring issue, not a driver bug. ++ */ ++ aw88399->channel = 1 - aw88399->channel; ++ dev_dbg(aw88399->dev, ++ "Channel swap applied: index %d -> channel %d\n", ++ aw88399->index, aw88399->channel); ++ return 0; ++} ++ ++static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) ++{ ++ /* ++ * BSTS (boost-finished) status bit does not reliably report on ++ * some hardware. On certain Lenovo Legion laptops, both amps ++ * report BSTS=0 (boost not finished) during normal playback ++ * despite clean audio output. Skip BSTS in the startup status ++ * check to avoid false init failures. ++ */ ++ aw88399->bsts_unreliable = true; ++ dev_dbg(aw88399->dev, "BSTS status check disabled\n"); ++ return 0; ++} ++ ++static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) ++{ ++ aw88399_swap_channels(aw88399); ++ aw88399_skip_bsts_check(aw88399); ++ return 0; ++} ++ ++struct aw88399_prop_model { ++ const char *ssid; ++ int (*apply_prop)(struct aw88399_hda *aw88399); ++}; ++ ++static const struct aw88399_prop_model aw88399_prop_model_table[] = { ++ { "17AA3906", aw88399_apply_legion_quirks }, ++ { "17AA3907", aw88399_apply_legion_quirks }, ++ { "17AA3927", aw88399_apply_legion_quirks }, ++ { "17AA3928", aw88399_apply_legion_quirks }, ++ { "17AA3936", aw88399_apply_legion_quirks }, ++ { "17AA3937", aw88399_apply_legion_quirks }, ++ { "17AA3938", aw88399_apply_legion_quirks }, ++ { "17AA3939", aw88399_apply_legion_quirks }, ++ { } ++}; ++ ++static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) ++{ ++ struct acpi_device *adev; ++ const char *sub; ++ const struct aw88399_prop_model *model; ++ ++ aw88399->index = aw88399_hda_index_from_i2c(aw88399); ++ aw88399->channel = aw88399->index; ++ aw88399->acpi_subsystem_id = NULL; ++ ++ adev = acpi_dev_get_first_match_dev("AWDZ8399", NULL, -1); ++ if (!adev) { ++ dev_err(aw88399->dev, "Failed to find an ACPI device for AWDZ8399\n"); ++ return -ENODEV; ++ } ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); ++ acpi_dev_put(adev); ++ if (!physdev) ++ return -ENODEV; ++ ++ sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); ++ if (IS_ERR_OR_NULL(sub)) ++ return 0; ++ ++ aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); ++ kfree(sub); ++ if (!aw88399->acpi_subsystem_id) ++ return -ENOMEM; ++ ++ for (model = aw88399_prop_model_table; model->ssid; model++) { ++ if (!strcasecmp(model->ssid, aw88399->acpi_subsystem_id)) { ++ dev_info(aw88399->dev, ++ "Applying properties for SSID %s\n", ++ aw88399->acpi_subsystem_id); ++ return model->apply_prop(aw88399); ++ } ++ } ++ ++ return 0; ++} ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap) ++{ ++ struct aw88399_hda *aw88399; ++ int ret; ++ ++ aw88399 = devm_kzalloc(dev, sizeof(*aw88399), GFP_KERNEL); ++ if (!aw88399) ++ return -ENOMEM; ++ ++ if (IS_ERR(regmap)) ++ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to obtain regmap\n"); ++ ++ aw88399->dev = dev; ++ aw88399->regmap = regmap; ++ dev_set_drvdata(dev, aw88399); ++ ++ aw88399->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); ++ if (IS_ERR(aw88399->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(aw88399->reset_gpio), ++ "Failed to get reset GPIO\n"); ++ ++ ret = aw88399_hda_acpi_probe(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "ACPI probe failed\n"); ++ ++ ret = aw88399_hda_init(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "Chip initialization failed\n"); ++ ++ /* Enable runtime PM */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ ret = component_add(dev, &aw88399_hda_comp_ops); ++ if (ret) { ++ pm_runtime_disable(dev); ++ return dev_err_probe(dev, ret, "Failed to register component\n"); ++ } ++ ++ dev_info(dev, "AW88399 HDA side codec registered successfully\n"); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_probe, "SND_HDA_SCODEC_AW88399"); ++ ++void aw88399_hda_remove(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ pm_runtime_disable(dev); ++ ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ ++ component_del(dev, &aw88399_hda_comp_ops); ++ ++ dev_dbg(aw88399->dev, "AW88399 HDA side codec removed\n"); ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_remove, "SND_HDA_SCODEC_AW88399"); ++ ++static int aw88399_hda_runtime_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ return 0; ++} ++ ++static int aw88399_hda_runtime_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime resume\n"); ++ ++ if (aw88399->core && aw88399->aw_dev && aw88399->playing) ++ aw88399_start(aw88399->core, AW88399_SYNC_START); ++ ++ return 0; ++} ++ ++static int aw88399_hda_system_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ if (aw88399->core) ++ aw88399->core->fw_needs_reload = true; ++ ++ ret = pm_runtime_force_suspend(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force suspend failed: %d\n", ret); ++ ++ return ret; ++} ++ ++static int aw88399_hda_system_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System resume\n"); ++ ++ if (aw88399->aw_dev) ++ aw88399_hw_reset(aw88399->core); ++ ++ ret = pm_runtime_force_resume(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force resume failed: %d\n", ret); ++ ++ return ret; ++} ++ ++const struct dev_pm_ops aw88399_hda_pm_ops = { ++ RUNTIME_PM_OPS(aw88399_hda_runtime_suspend, aw88399_hda_runtime_resume, NULL) ++ SYSTEM_SLEEP_PM_OPS(aw88399_hda_system_suspend, aw88399_hda_system_resume) ++}; ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_pm_ops, "SND_HDA_SCODEC_AW88399"); ++ ++MODULE_DESCRIPTION("AW88399 HDA driver"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_AUTHOR("Marco Giunta "); ++MODULE_LICENSE("GPL"); ++MODULE_FIRMWARE("aw88399_acf.bin"); +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.h b/sound/hda/codecs/side-codecs/aw88399_hda.h +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.h +@@ -0,0 +1,36 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * AW88399 HDA side codec driver ++ */ ++ ++#ifndef __AW88399_HDA_H__ ++#define __AW88399_HDA_H__ ++ ++#include ++#include ++#include ++ ++struct aw88399; ++struct aw_device; ++ ++struct aw88399_hda { ++ struct device *dev; ++ struct regmap *regmap; ++ struct gpio_desc *reset_gpio; ++ struct aw_device *aw_dev; ++ struct aw88399 *core; ++ bool bsts_unreliable; ++ ++ const char *acpi_subsystem_id; ++ int index; ++ int channel; ++ ++ bool playing; ++}; ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap); ++void aw88399_hda_remove(struct device *dev); ++ ++extern const struct dev_pm_ops aw88399_hda_pm_ops; ++ ++#endif /* __AW88399_HDA_H__ */ +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +@@ -0,0 +1,54 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA I2C driver ++// ++// Based on cs35l41_hda_i2c.c ++// ++ ++#include ++#include ++#include ++ ++#include "aw88399_hda.h" ++ ++static int aw88399_hda_i2c_probe(struct i2c_client *clt) ++{ ++ if (!strstr(dev_name(&clt->dev), "AWDZ8399")) ++ return -ENODEV; ++ ++ return aw88399_hda_probe(&clt->dev, ++ devm_regmap_init_i2c(clt, &aw88399_remap_config)); ++} ++ ++static void aw88399_hda_i2c_remove(struct i2c_client *clt) ++{ ++ aw88399_hda_remove(&clt->dev); ++} ++ ++static const struct i2c_device_id aw88399_hda_i2c_id[] = { ++ { .name = "aw88399-hda" }, ++ { } ++}; ++ ++static const struct acpi_device_id aw88399_acpi_hda_match[] = { ++ { "AWDZ8399", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, aw88399_acpi_hda_match); ++ ++static struct i2c_driver aw88399_hda_i2c_driver = { ++ .driver = { ++ .name = "aw88399-hda", ++ .acpi_match_table = aw88399_acpi_hda_match, ++ .pm = &aw88399_hda_pm_ops, ++ }, ++ .probe = aw88399_hda_i2c_probe, ++ .remove = aw88399_hda_i2c_remove, ++ .id_table = aw88399_hda_i2c_id, ++}; ++module_i2c_driver(aw88399_hda_i2c_driver); ++ ++MODULE_DESCRIPTION("HDA AW88399 I2C driver"); ++MODULE_IMPORT_NS("SND_HDA_SCODEC_AW88399"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l41_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c +@@ -170,6 +170,7 @@ static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, + char *s, c; + int ret = 0; + ++ *firmware = NULL; + if (spkid > -1 && ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "cirrus/%s-%s-%s-%s-spkid%d-%s.%s", CS35L41_PART, + dsp_name, cs35l41_hda_fw_ids[cs35l41->firmware_type], +@@ -528,8 +529,8 @@ static int cs35l41_read_tuning_params(struct cs35l41_hda *cs35l41, const struct + + static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_filename) + { +- const struct firmware *tuning_param_file = NULL; +- char *tuning_param_filename = NULL; ++ const struct firmware *tuning_param_file __free(firmware) = NULL; ++ char *tuning_param_filename __free(kfree) = NULL; + int ret; + + ret = cs35l41_request_tuning_param_file(cs35l41, tuning_filename, &tuning_param_file, +@@ -548,19 +549,16 @@ static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_ + cs35l41_set_default_tuning_params(cs35l41); + } + +- release_firmware(tuning_param_file); +- kfree(tuning_param_filename); +- + return ret; + } + + static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; + struct cs_dsp *dsp = &cs35l41->cs_dsp; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + int ret; + + if (!cs35l41->halo_initialized) { +@@ -592,20 +590,13 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + + ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, + cs35l41_hda_fw_ids[cs35l41->firmware_type]); +- if (ret) +- goto err; ++ if (ret) { ++ cs35l41_set_default_tuning_params(cs35l41); ++ return ret; ++ } + + cs35l41_hda_apply_calibration(cs35l41); +- +-err: +- if (ret) +- cs35l41_set_default_tuning_params(cs35l41); +- release_firmware(wmfw_firmware); +- release_firmware(coeff_firmware); +- kfree(wmfw_filename); +- kfree(coeff_filename); +- +- return ret; ++ return 0; + } + + static void cs35l41_shutdown_dsp(struct cs35l41_hda *cs35l41) +@@ -1924,7 +1915,6 @@ int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) + { + struct acpi_device *adev; +- struct device *physdev; + struct spi_device *spi; + const char *sub; + int ret; +@@ -1936,7 +1926,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + cs35l41->dacpi = adev; +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + if (!physdev) { + acpi_dev_put(adev); + return -ENODEV; +@@ -1954,13 +1945,9 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + ret = cs35l41_hda_parse_acpi(cs35l41, physdev, id); +- if (ret) { +- put_device(physdev); ++ if (ret) + return ret; +- } + out: +- put_device(physdev); +- + cs35l41->bypass_fw = false; + if (cs35l41->control_bus == SPI) { + spi = to_spi_device(cs35l41->dev); +diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l56_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c +@@ -527,18 +527,6 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56, + base_name, NULL, NULL, "bin"); + } + +-static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmware, +- char *wmfw_filename, +- const struct firmware *coeff_firmware, +- char *coeff_filename) +-{ +- release_firmware(wmfw_firmware); +- kfree(wmfw_filename); +- +- release_firmware(coeff_firmware); +- kfree(coeff_filename); +-} +- + static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + { + int ret; +@@ -561,10 +549,10 @@ static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + + static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + unsigned int preloaded_fw_ver; + bool firmware_missing; + int ret; +@@ -606,14 +594,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing) { + if (!wmfw_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "wmfw"); +- goto err_fw_release; ++ return; + } else if (!coeff_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "bin"); +- goto err_fw_release; ++ return; + } + } + +- mutex_lock(&cs35l56->base.irq_lock); ++ guard(mutex)(&cs35l56->base.irq_lock); + + /* + * If the firmware hasn't been patched it must be shutdown before +@@ -624,14 +612,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing && (wmfw_firmware || coeff_firmware)) { + ret = cs35l56_firmware_shutdown(&cs35l56->base); + if (ret) +- goto err; ++ return; + } + + ret = cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename, "misc"); + if (ret) { + dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret); +- goto err; ++ return; + } + + if (wmfw_filename) +@@ -679,11 +667,6 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + err_powered_up: + if (!cs35l56->base.fw_patched) + cs_dsp_power_down(&cs35l56->cs_dsp); +-err: +- mutex_unlock(&cs35l56->base.irq_lock); +-err_fw_release: +- cs35l56_hda_release_firmware_files(wmfw_firmware, wmfw_filename, +- coeff_firmware, coeff_filename); + } + + static void cs35l56_hda_dsp_work(struct work_struct *work) +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -88,7 +88,6 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + { + struct gpio_desc *speaker_id; + struct acpi_device *adev; +- struct device *physdev; + LIST_HEAD(resources); + const char *sub; + uint32_t subid; +@@ -101,7 +100,8 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + return -ENODEV; + } + +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); + if (ret < 0) { + dev_err(p->dev, "Failed to get ACPI resource.\n"); +@@ -151,14 +151,12 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + end_2563: + acpi_dev_free_resource_list(&resources); + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- put_device(physdev); + acpi_dev_put(adev); + + return 0; + + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); + acpi_dev_put(adev); + + return ret; +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -328,7 +328,6 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + { + struct tasdevice_priv *p = tas_hda->priv; + struct acpi_device *adev; +- struct device *physdev; + u32 values[HDA_MAX_COMPONENTS]; + const char *property; + size_t nval; +@@ -341,7 +340,9 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + } + + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + if (!physdev) + return -ENODEV; +@@ -381,13 +382,11 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + goto err; + } + } +- put_device(physdev); + + return 0; ++ + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); +- + return ret; + } + +diff --git a/sound/hda/common/auto_parser.c b/sound/hda/common/auto_parser.c +--- a/sound/hda/common/auto_parser.c ++++ b/sound/hda/common/auto_parser.c +@@ -601,9 +601,9 @@ static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) + return -1; + } + +-/* get a unique suffix or an index number */ ++/* get a unique suffix */ + static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, +- int num_pins, int *indexp) ++ int num_pins) + { + static const char * const channel_sfx[] = { + " Front", " Surround", " CLFE", " Side" +@@ -615,11 +615,8 @@ static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, + return NULL; + if (num_pins == 1) + return ""; +- if (num_pins > ARRAY_SIZE(channel_sfx)) { +- if (indexp) +- *indexp = i; ++ if (num_pins > ARRAY_SIZE(channel_sfx)) + return ""; +- } + return channel_sfx[i]; + } + +@@ -638,27 +635,9 @@ static const char *check_output_pfx(struct hda_codec *codec, hda_nid_t nid) + return ""; + } + +-static int get_hp_label_index(struct hda_codec *codec, hda_nid_t nid, +- const hda_nid_t *pins, int num_pins) +-{ +- int i, j, idx = 0; +- +- const char *pfx = check_output_pfx(codec, nid); +- +- i = find_idx_in_nid_list(nid, pins, num_pins); +- if (i < 0) +- return -1; +- for (j = 0; j < i; j++) +- if (pfx == check_output_pfx(codec, pins[j])) +- idx++; +- +- return idx; +-} +- + static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- const char *name, char *label, int maxlen, +- int *indexp) ++ const char *name, char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + int attr = snd_hda_get_input_pin_attr(def_conf); +@@ -671,19 +650,11 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + + if (cfg) { + /* try to give a unique suffix if needed */ +- sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs, +- indexp); ++ sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs); ++ if (!sfx) ++ sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs); + if (!sfx) +- sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs, +- indexp); +- if (!sfx) { +- /* don't add channel suffix for Headphone controls */ +- int idx = get_hp_label_index(codec, nid, cfg->hp_pins, +- cfg->hp_outs); +- if (idx >= 0 && indexp) +- *indexp = idx; + sfx = ""; +- } + } + snprintf(label, maxlen, "%s%s%s", pfx, name, sfx); + return 1; +@@ -699,7 +670,6 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * @cfg: the parsed pin configuration + * @label: the string buffer to store + * @maxlen: the max length of string buffer (including termination) +- * @indexp: the pointer to return the index number (for multiple ctls) + * + * Get a label for the given pin. This function works for both input and + * output pins. When @cfg is given as non-NULL, the function tries to get +@@ -708,47 +678,33 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * This function tries to give a unique label string for the pin as much as + * possible. For example, when the multiple line-outs are present, it adds + * the channel suffix like "Front", "Surround", etc (only when @cfg is given). +- * If no unique name with a suffix is available and @indexp is non-NULL, the +- * index number is stored in the pointer. + */ + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp) ++ char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + const char *name = NULL; + int i; + bool hdmi; + +- if (indexp) +- *indexp = 0; + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) + return 0; + + switch (get_defcfg_device(def_conf)) { + case AC_JACK_LINE_OUT: + return fill_audio_out_name(codec, nid, cfg, "Line Out", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPEAKER: + return fill_audio_out_name(codec, nid, cfg, "Speaker", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_HP_OUT: + return fill_audio_out_name(codec, nid, cfg, "Headphone", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + hdmi = is_hdmi_cfg(def_conf); + name = hdmi ? "HDMI" : "SPDIF"; +- if (cfg && indexp) +- for (i = 0; i < cfg->dig_outs; i++) { +- hda_nid_t pin = cfg->dig_out_pins[i]; +- unsigned int c; +- if (pin == nid) +- break; +- c = snd_hda_codec_get_pincfg(codec, pin); +- if (hdmi == is_hdmi_cfg(c)) +- (*indexp)++; +- } + break; + default: + if (cfg) { +diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c +--- a/sound/hda/common/codec.c ++++ b/sound/hda/common/codec.c +@@ -39,6 +39,12 @@ static int call_exec_verb(struct hda_bus *bus, struct hda_codec *codec, + int err; + + CLASS(snd_hda_power_pm, pm)(codec); ++ if (pm.err < 0 && !bus->core.chip_init) { ++ codec_warn(codec, ++ "Failed to send cmd 0x%x ret=[%d], hda control stopped\n", ++ cmd, pm.err); ++ return pm.err; ++ } + guard(mutex)(&bus->core.cmd_mutex); + if (flags & HDA_RW_NO_RESPONSE_FALLBACK) + bus->no_response_fallback = 1; +@@ -2969,8 +2975,11 @@ static void hda_codec_pm_complete(struct device *dev) + dev->power.power_state = PMSG_RESUME; + + if (pm_runtime_suspended(dev) && (codec->jackpoll_interval || +- hda_codec_need_resume(codec) || codec->forced_resume)) ++ hda_codec_need_resume(codec) || codec->forced_resume || ++ codec->acomp_requested_resume)) { ++ codec->acomp_requested_resume = 0; + pm_request_resume(dev); ++ } + } + + static int hda_codec_pm_suspend(struct device *dev) +@@ -3372,7 +3381,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, + for (; knew->name; knew++) { + struct snd_kcontrol *kctl; + int addr = 0, idx = 0; +- if (knew->iface == (__force snd_ctl_elem_iface_t)-1) ++ if (knew->iface == -1) + continue; /* skip this codec private value */ + for (;;) { + kctl = snd_ctl_new1(knew, codec); +diff --git a/sound/hda/common/hda_auto_parser.h b/sound/hda/common/hda_auto_parser.h +--- a/sound/hda/common/hda_auto_parser.h ++++ b/sound/hda/common/hda_auto_parser.h +@@ -46,7 +46,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec, + int input); + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp); ++ char *label, int maxlen); + + enum { + INPUT_PIN_ATTR_UNUSED, /* pin not connected */ +diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h +--- a/sound/hda/common/hda_local.h ++++ b/sound/hda/common/hda_local.h +@@ -723,4 +723,11 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable); + #define codec_dbg(codec, fmt, args...) \ + dev_dbg(hda_codec_dev(codec), fmt, ##args) + ++/* append a suffix string safely; equivalent with strlcat() */ ++static inline void hda_append_suffix(char *str, const char *suffix, size_t size) ++{ ++ size_t len = strnlen(str, size); ++ strscpy(str + len, suffix, size - len); ++} ++ + #endif /* __SOUND_HDA_LOCAL_H */ +diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c +--- a/sound/hda/common/jack.c ++++ b/sound/hda/common/jack.c +@@ -612,10 +612,10 @@ static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid, + if (base_name) + strscpy(name, base_name, sizeof(name)); + else +- snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL); ++ snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name)); + if (phantom_jack) + /* Example final name: "Internal Mic Phantom Jack" */ +- strncat(name, " Phantom", sizeof(name) - strlen(name) - 1); ++ hda_append_suffix(name, " Phantom", sizeof(name)); + err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL); + if (err < 0) + return err; +diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c +--- a/sound/hda/controllers/intel.c ++++ b/sound/hda/controllers/intel.c +@@ -100,6 +100,8 @@ enum { + #define ATIHDMI_NUM_CAPTURE 0 + #define ATIHDMI_NUM_PLAYBACK 8 + ++/* Hygon HD Audio controller */ ++#define PCI_DEVICE_ID_HYGON_18H_M05H_HDA 0x14a9 + + static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; + static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +@@ -241,6 +243,7 @@ enum { + AZX_DRIVER_ZHAOXIN, + AZX_DRIVER_ZHAOXINHDMI, + AZX_DRIVER_LOONGSON, ++ AZX_DRIVER_HYGON, + AZX_DRIVER_GENERIC, + AZX_NUM_DRIVERS, /* keep this as last entry */ + }; +@@ -360,6 +363,7 @@ static const char * const driver_short_names[] = { + [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", + [AZX_DRIVER_ZHAOXINHDMI] = "HDA Zhaoxin HDMI", + [AZX_DRIVER_LOONGSON] = "HDA Loongson", ++ [AZX_DRIVER_HYGON] = "HDA Hygon", + [AZX_DRIVER_GENERIC] = "HD-Audio Generic", + }; + +@@ -1926,6 +1930,10 @@ static int azx_first_init(struct azx *chip) + if (chip->driver_type == AZX_DRIVER_ZHAOXINHDMI) + bus->polling_mode = 1; + ++ if (chip->driver_type == AZX_DRIVER_HYGON && ++ chip->pci->device == PCI_DEVICE_ID_HYGON_18H_M05H_HDA) ++ bus->access_sdnctl_in_dword = 1; ++ + bus->remap_addr = pcim_iomap_region(pci, 0, "ICH HD audio"); + if (IS_ERR(bus->remap_addr)) + return PTR_ERR(bus->remap_addr); +@@ -2189,6 +2197,15 @@ static int azx_probe(struct pci_dev *pci, + dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n"); + } + ++ /* A sanity check against wild device binding; ++ * here the range 0x200 is enough for the registers used at probe, ++ * but it doesn't mean covering all HD-audio registers ++ */ ++ if (pci_resource_len(pci, 0) < 0x200) { ++ dev_err(&pci->dev, "Too small PCI BAR0\n"); ++ return -EINVAL; ++ } ++ + err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { +@@ -2381,7 +2398,7 @@ static int azx_probe_continue(struct azx *chip) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (patch[dev] && *patch[dev]) { +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&pci->dev, "Applying patch firmware '%s'\n", + patch[dev]); +@@ -2390,7 +2407,6 @@ static int azx_probe_continue(struct azx *chip) + "Cannot load firmware, continue without patching\n"); + } else { + err = snd_hda_load_patch(&chip->bus, fw->size, fw->data); +- release_firmware(fw); + if (err < 0) + goto out_free; + } +@@ -2838,6 +2854,11 @@ static const struct pci_device_id azx_ids[] = { + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, + { PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI), + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, ++ /* Hygon HDAudio */ ++ { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_HDA), ++ .driver_data = AZX_DRIVER_HYGON | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_NO_MSI }, ++ /* Lisuan HD-audio */ ++ { PCI_DEVICE(0x4c54, 0x5010), .driver_data = AZX_DRIVER_GENERIC }, + { 0, } + }; + MODULE_DEVICE_TABLE(pci, azx_ids); +diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c +--- a/sound/hda/core/controller.c ++++ b/sound/hda/core/controller.c +@@ -511,7 +511,10 @@ void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus) + { + unsigned long timeout; + +- snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ else ++ snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); + + timeout = jiffies + msecs_to_jiffies(100); + while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout)) +@@ -576,7 +579,10 @@ static void azx_int_disable(struct hdac_bus *bus) + + /* disable interrupts in stream descriptor */ + list_for_each_entry(azx_dev, &bus->stream_list, list) +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); + + /* disable SIE for all streams & disable controller CIE and GIE */ + snd_hdac_chip_writel(bus, INTCTL, 0); +@@ -682,6 +688,11 @@ int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status, + sd_status = snd_hdac_stream_readb(azx_dev, SD_STS); + snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); + handled |= 1 << azx_dev->index; ++ if (sd_status & (SD_INT_FIFO_ERR | SD_INT_DESC_ERR)) { ++ dev_warn_ratelimited(bus->dev, ++ "stream %u dma error: 0x%02x\n", ++ azx_dev->index, sd_status); ++ } + if ((!azx_dev->substream && !azx_dev->cstream) || + !azx_dev->running || !(sd_status & SD_INT_COMPLETE)) + continue; +@@ -713,7 +724,7 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + BDL_SIZE, &s->bdl); + num_streams++; + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + } + + if (WARN_ON(!num_streams)) +@@ -722,12 +733,24 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + err = snd_dma_alloc_pages(dma_type, bus->dev, + num_streams * 8, &bus->posbuf); + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + list_for_each_entry(s, &bus->stream_list, list) + s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8); + + /* single page (at least 4096 bytes) must suffice for both ringbuffes */ +- return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ err = snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ if (err < 0) ++ goto error_posbuf; ++ return 0; ++ ++error_posbuf: ++ snd_dma_free_pages(&bus->posbuf); ++error_bdl: ++ list_for_each_entry(s, &bus->stream_list, list) { ++ if (s->bdl.area) ++ snd_dma_free_pages(&s->bdl); ++ } ++ return -ENOMEM; + } + EXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages); + +diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -664,6 +664,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x1af4, "QEMU" }, + { 0x1fa8, "Senarytech" }, + { 0x434d, "C-Media" }, ++ { 0x4c54, "Lisuan" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +@@ -766,7 +767,7 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor + + params_set_format(¶ms, snd_hdac_format_normalize(format)); + snd_mask_set(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT), +- (__force unsigned int)subformat); ++ subformat); + + bits = snd_pcm_hw_params_bits(¶ms); + if (maxbits) +diff --git a/sound/hda/core/hda_bus_type.c b/sound/hda/core/hda_bus_type.c +--- a/sound/hda/core/hda_bus_type.c ++++ b/sound/hda/core/hda_bus_type.c +@@ -39,10 +39,7 @@ EXPORT_SYMBOL_GPL(hdac_get_device_id); + + static int hdac_codec_match(struct hdac_device *dev, const struct hdac_driver *drv) + { +- if (hdac_get_device_id(dev, drv)) +- return 1; +- else +- return 0; ++ return !!hdac_get_device_id(dev, drv); + } + + static int hda_bus_match(struct device *dev, const struct device_driver *drv) +@@ -59,9 +56,7 @@ static int hda_bus_match(struct device *dev, const struct device_driver *drv) + */ + if (hdrv->match) + return hdrv->match(hdev, hdrv); +- else +- return hdac_codec_match(hdev, hdrv); +- return 1; ++ return hdac_codec_match(hdev, hdrv); + } + + static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env) +diff --git a/sound/hda/core/stream.c b/sound/hda/core/stream.c +--- a/sound/hda/core/stream.c ++++ b/sound/hda/core/stream.c +@@ -146,8 +146,12 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev) + stripe_ctl = snd_hdac_get_stream_stripe_ctl(bus, azx_dev->substream); + else + stripe_ctl = 0; +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, +- stripe_ctl); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); + } + /* set DMA start and interrupt mask */ + if (bus->access_sdnctl_in_dword) +@@ -166,11 +170,22 @@ EXPORT_SYMBOL_GPL(snd_hdac_stream_start); + */ + static void snd_hdac_stream_clear(struct hdac_stream *azx_dev) + { +- snd_hdac_stream_updateb(azx_dev, SD_CTL, +- SD_CTL_DMA_START | SD_INT_MASK, 0); +- snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ +- if (azx_dev->stripe) +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ struct hdac_bus *bus = azx_dev->bus; ++ ++ if (bus->access_sdnctl_in_dword) { ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } else { ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } ++ + azx_dev->running = false; + } + +@@ -225,12 +240,16 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + { + unsigned char val; + int dma_run_state; ++ struct hdac_bus *bus = azx_dev->bus; + + snd_hdac_stream_clear(azx_dev); + + dma_run_state = snd_hdac_stream_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START; + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); + + /* wait for hardware to report that the stream entered reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, (val & SD_CTL_STREAM_RESET), 3, 300); +@@ -238,7 +257,10 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + if (azx_dev->bus->dma_stop_delay && dma_run_state) + udelay(azx_dev->bus->dma_stop_delay); + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); + + /* wait for hardware to report that the stream is out of reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, !(val & SD_CTL_STREAM_RESET), 3, 300); +diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c +--- a/sound/isa/msnd/msnd_pinnacle.c ++++ b/sound/isa/msnd/msnd_pinnacle.c +@@ -367,7 +367,8 @@ static int snd_msnd_init_sma(struct snd_msnd *chip) + static int upload_dsp_code(struct snd_card *card) + { + struct snd_msnd *chip = card->private_data; +- const struct firmware *init_fw = NULL, *perm_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; ++ const struct firmware *perm_fw __free(firmware) = NULL; + int err; + + outb(HPBLKSEL_0, chip->io + HP_BLKS); +@@ -375,28 +376,21 @@ static int upload_dsp_code(struct snd_card *card) + err = request_firmware(&init_fw, INITCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " INITCODEFILE); +- goto cleanup1; ++ return err; + } + err = request_firmware(&perm_fw, PERMCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " PERMCODEFILE); +- goto cleanup; ++ return err; + } + + memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size); + if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) { + dev_warn(card->dev, LOGNAME ": Error uploading to DSP\n"); +- err = -ENODEV; +- goto cleanup; ++ return -ENODEV; + } + dev_info(card->dev, LOGNAME ": DSP firmware uploaded\n"); +- err = 0; +- +-cleanup: +- release_firmware(perm_fw); +-cleanup1: +- release_firmware(init_fw); +- return err; ++ return 0; + } + + #ifdef MSND_CLASSIC +diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c +--- a/sound/isa/sscape.c ++++ b/sound/isa/sscape.c +@@ -526,7 +526,7 @@ static int upload_dma_data(struct soundscape *s, const unsigned char *data, + static int sscape_upload_bootblock(struct snd_card *card) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + int data = 0; + int ret; + +@@ -537,8 +537,6 @@ static int sscape_upload_bootblock(struct snd_card *card) + } + ret = upload_dma_data(sscape, init_fw->data, init_fw->size); + +- release_firmware(init_fw); +- + guard(spinlock_irqsave)(&sscape->lock); + if (ret == 0) + data = host_read_ctrl_unsafe(sscape->io_base, 100); +@@ -562,7 +560,7 @@ static int sscape_upload_bootblock(struct snd_card *card) + static int sscape_upload_microcode(struct snd_card *card, int version) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + char name[14]; + int err; + +@@ -579,8 +577,6 @@ static int sscape_upload_microcode(struct snd_card *card, int version) + dev_info(card->dev, "sscape: MIDI firmware loaded %zu KBs\n", + init_fw->size >> 10); + +- release_firmware(init_fw); +- + return err; + } + +diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c +--- a/sound/isa/wavefront/wavefront_fx.c ++++ b/sound/isa/wavefront/wavefront_fx.c +@@ -232,41 +232,32 @@ snd_wavefront_fx_start (snd_wavefront_t *dev) + { + unsigned int i; + int err; +- const struct firmware *firmware = NULL; ++ const struct firmware *firmware __free(firmware) = NULL; + + if (dev->fx_initialized) + return 0; + + err = request_firmware(&firmware, "yamaha/yss225_registers.bin", + dev->card->dev); +- if (err < 0) { +- err = -1; +- goto out; +- } ++ if (err < 0) ++ return -1; + + for (i = 0; i + 1 < firmware->size; i += 2) { + if (firmware->data[i] >= 8 && firmware->data[i] < 16) { + outb(firmware->data[i + 1], + dev->base + firmware->data[i]); + } else if (firmware->data[i] == WAIT_IDLE) { +- if (!wavefront_fx_idle(dev)) { +- err = -1; +- goto out; +- } ++ if (!wavefront_fx_idle(dev)) ++ return -1; + } else { + dev_err(dev->card->dev, + "invalid address in register data\n"); +- err = -1; +- goto out; ++ return -1; + } + } + + dev->fx_initialized = 1; +- err = 0; +- +-out: +- release_firmware(firmware); +- return err; ++ return 0; + } + + MODULE_FIRMWARE("yamaha/yss225_registers.bin"); +diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c +--- a/sound/isa/wavefront/wavefront_synth.c ++++ b/sound/isa/wavefront/wavefront_synth.c +@@ -2053,7 +2053,7 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + const unsigned char *buf; + int len, err; + int section_cnt_downloaded = 0; +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + + err = request_firmware(&firmware, path, dev->card->dev); + if (err < 0) { +@@ -2108,11 +2108,9 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + section_cnt_downloaded++; + } + +- release_firmware(firmware); + return 0; + + failure: +- release_firmware(firmware); + dev_err(dev->card->dev, "firmware download failed!!!\n"); + return 1; + } +diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c +--- a/sound/pci/asihpi/asihpi.c ++++ b/sound/pci/asihpi/asihpi.c +@@ -280,7 +280,7 @@ static void print_hwparams(struct snd_pcm_substream *substream, + snd_pcm_format_width(params_format(p)) / 8); + } + +-#define INVALID_FORMAT (__force snd_pcm_format_t)(-1) ++#define INVALID_FORMAT -1 + + static const snd_pcm_format_t hpi_to_alsa_formats[] = { + INVALID_FORMAT, /* INVALID */ +diff --git a/sound/pci/asihpi/hpidspcd.c b/sound/pci/asihpi/hpidspcd.c +--- a/sound/pci/asihpi/hpidspcd.c ++++ b/sound/pci/asihpi/hpidspcd.c +@@ -23,7 +23,7 @@ struct dsp_code_private { + short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + u32 *os_error_code) + { +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + struct pci_dev *dev = os_data; + struct code_header header; + char fw_name[20]; +@@ -37,11 +37,11 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + if (err || !firmware) { + dev_err(&dev->dev, "%d, request_firmware failed for %s\n", + err, fw_name); +- goto error1; ++ goto error; + } + if (firmware->size < sizeof(header)) { + dev_err(&dev->dev, "Header size too small %s\n", fw_name); +- goto error2; ++ goto error; + } + memcpy(&header, firmware->data, sizeof(header)); + +@@ -51,7 +51,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Invalid firmware header size %d != file %zd\n", + header.size, firmware->size); +- goto error2; ++ goto error; + } + + if (HPI_VER_MAJOR(header.version) != HPI_VER_MAJOR(HPI_VER)) { +@@ -59,7 +59,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Incompatible firmware version DSP image %X != Driver %X\n", + header.version, HPI_VER); +- goto error2; ++ goto error; + } + + if (header.version != HPI_VER) { +@@ -72,19 +72,17 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dsp_code->pvt = kmalloc_obj(*dsp_code->pvt); + if (!dsp_code->pvt) { + err_ret = HPI_ERROR_MEMORY_ALLOC; +- goto error2; ++ goto error; + } + + dsp_code->pvt->dev = dev; +- dsp_code->pvt->firmware = firmware; ++ dsp_code->pvt->firmware = no_free_ptr(firmware); + dsp_code->header = header; + dsp_code->block_length = header.size / sizeof(u32); + dsp_code->word_count = sizeof(header) / sizeof(u32); + return 0; + +-error2: +- release_firmware(firmware); +-error1: ++error: + dsp_code->block_length = 0; + return err_ret; + } +diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c +--- a/sound/pci/asihpi/hpioctl.c ++++ b/sound/pci/asihpi/hpioctl.c +@@ -385,8 +385,7 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) { + memlen = pci_resource_len(pci_dev, idx); + pci.ap_mem_base[idx] = +- ioremap(pci_resource_start(pci_dev, idx), +- memlen); ++ pcim_iomap(pci_dev, idx, memlen); + if (!pci.ap_mem_base[idx]) { + HPI_DEBUG_LOG(ERROR, + "ioremap failed, aborting\n"); +@@ -509,13 +508,6 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + return 0; + + err: +- while (--idx >= 0) { +- if (pci.ap_mem_base[idx]) { +- iounmap(pci.ap_mem_base[idx]); +- pci.ap_mem_base[idx] = NULL; +- } +- } +- + if (adapter.p_buffer) { + adapter.buffer_size = 0; + vfree(adapter.p_buffer); +@@ -527,14 +519,11 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + + void asihpi_adapter_remove(struct pci_dev *pci_dev) + { +- int idx; + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter *pa; +- struct hpi_pci pci; + + pa = pci_get_drvdata(pci_dev); +- pci = pa->adapter->pci; + + /* Disable IRQ generation on DSP side */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, +@@ -550,10 +539,6 @@ void asihpi_adapter_remove(struct pci_dev *pci_dev) + hm.adapter_index = pa->adapter->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + +- /* unmap PCI memory space, mapped during device init. */ +- for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; ++idx) +- iounmap(pci.ap_mem_base[idx]); +- + if (pa->irq) + free_irq(pa->irq, pa); + +diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c +--- a/sound/pci/cs46xx/cs46xx_lib.c ++++ b/sound/pci/cs46xx/cs46xx_lib.c +@@ -387,7 +387,7 @@ static int load_firmware(struct snd_cs46xx *chip, + unsigned int nums, fwlen, fwsize; + const __le32 *fwdat; + struct dsp_module_desc *module = NULL; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + char fw_path[32]; + + sprintf(fw_path, "cs46xx/%s", fw_name); +@@ -395,10 +395,8 @@ static int load_firmware(struct snd_cs46xx *chip, + if (err < 0) + return err; + fwsize = fw->size / 4; +- if (fwsize < 2) { +- err = -EINVAL; +- goto error; +- } ++ if (fwsize < 2) ++ return -EINVAL; + + err = -ENOMEM; + module = kzalloc_obj(*module); +@@ -454,14 +452,12 @@ static int load_firmware(struct snd_cs46xx *chip, + } + + *module_ret = module; +- release_firmware(fw); + return 0; + + error_inval: + err = -EINVAL; + error: + free_module_desc(module); +- release_firmware(fw); + return err; + } + +@@ -500,22 +496,18 @@ MODULE_FIRMWARE("cs46xx/ba1"); + + static int load_firmware(struct snd_cs46xx *chip) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int i, size, err; + + err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev); + if (err < 0) + return err; +- if (fw->size != sizeof(*chip->ba1)) { +- err = -EINVAL; +- goto error; +- } ++ if (fw->size != sizeof(*chip->ba1)) ++ return -EINVAL; + + chip->ba1 = vmalloc(sizeof(*chip->ba1)); +- if (!chip->ba1) { +- err = -ENOMEM; +- goto error; +- } ++ if (!chip->ba1) ++ return -ENOMEM; + + memcpy_le32(chip->ba1, fw->data, sizeof(*chip->ba1)); + +@@ -524,11 +516,9 @@ static int load_firmware(struct snd_cs46xx *chip) + for (i = 0; i < BA1_MEMORY_COUNT; i++) + size += chip->ba1->memory[i].size; + if (size > BA1_DWORD_SIZE * 4) +- err = -EINVAL; ++ return -EINVAL; + +- error: +- release_firmware(fw); +- return err; ++ return 0; + } + + static __maybe_unused int snd_cs46xx_download_image(struct snd_cs46xx *chip) +diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c +--- a/sound/pci/emu10k1/emufx.c ++++ b/sound/pci/emu10k1/emufx.c +@@ -990,7 +990,7 @@ static int snd_emu10k1_list_controls(struct snd_emu10k1 *emu, + i < icode->gpr_list_control_count) { + memset(gctl, 0, sizeof(*gctl)); + id = &ctl->kcontrol->id; +- gctl->id.iface = (__force int)id->iface; ++ gctl->id.iface = id->iface; + strscpy(gctl->id.name, id->name, sizeof(gctl->id.name)); + gctl->id.index = id->index; + gctl->id.device = id->device; +@@ -1156,7 +1156,7 @@ static void + snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + if (high_res_gpr_volume) { +@@ -1180,7 +1180,7 @@ static void + snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + if (high_res_gpr_volume) { +@@ -1205,7 +1205,7 @@ static void + snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1218,7 +1218,7 @@ static void + snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1542,7 +1542,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[nctl + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -1551,7 +1551,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + ctl->value[0] = ctl->value[1] = 20; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[nctl + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2137,7 +2137,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[i + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2147,7 +2147,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + ctl->tlv = snd_emu10k1_bass_treble_db_scale; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[i + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c +--- a/sound/pci/korg1212/korg1212.c ++++ b/sound/pci/korg1212/korg1212.c +@@ -1980,7 +1980,7 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + __maybe_unused unsigned ioport_size; + __maybe_unused unsigned iomem2_size; + struct snd_korg1212 *korg1212 = card->private_data; +- const struct firmware *dsp_code; ++ const struct firmware *dsp_code __free(firmware) = NULL; + + err = pcim_enable_device(pci); + if (err < 0) +@@ -2147,10 +2147,8 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + korg1212->dma_dsp = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + dsp_code->size); +- if (!korg1212->dma_dsp) { +- release_firmware(dsp_code); ++ if (!korg1212->dma_dsp) + return -ENOMEM; +- } + + K1212_DEBUG_PRINTK("K1212_DEBUG: DSP Code area = 0x%p (0x%08x) %d bytes [%s]\n", + korg1212->dma_dsp->area, korg1212->dma_dsp->addr, dsp_code->size, +@@ -2158,8 +2156,6 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + memcpy(korg1212->dma_dsp->area, dsp_code->data, dsp_code->size); + +- release_firmware(dsp_code); +- + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_RebootCard, 0, 0, 0, 0); + + if (rc) +diff --git a/sound/pci/mixart/mixart_hwdep.c b/sound/pci/mixart/mixart_hwdep.c +--- a/sound/pci/mixart/mixart_hwdep.c ++++ b/sound/pci/mixart/mixart_hwdep.c +@@ -560,12 +560,11 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + "miXart8.xlx", "miXart8.elf", "miXart8AES.xlx" + }; + char path[32]; +- +- const struct firmware *fw_entry; + int i, err; + + for (i = 0; i < 3; i++) { + sprintf(path, "mixart/%s", fw_files[i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "miXart: can't load firmware %s\n", path); +@@ -573,7 +572,6 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + } + /* fake hwdep dsp record */ + err = mixart_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/pcxhr/pcxhr_hwdep.c b/sound/pci/pcxhr/pcxhr_hwdep.c +--- a/sound/pci/pcxhr/pcxhr_hwdep.c ++++ b/sound/pci/pcxhr/pcxhr_hwdep.c +@@ -367,7 +367,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + }; + char path[32]; + +- const struct firmware *fw_entry; + int i, err; + int fw_set = mgr->fw_file_set; + +@@ -375,6 +374,7 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + if (!fw_files[fw_set][i]) + continue; + sprintf(path, "pcxhr/%s", fw_files[fw_set][i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "pcxhr: can't load firmware %s\n", +@@ -383,7 +383,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + } + /* fake hwdep dsp record */ + err = pcxhr_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c +--- a/sound/pci/sis7019.c ++++ b/sound/pci/sis7019.c +@@ -1260,16 +1260,16 @@ static int sis_chip_create(struct snd_card *card, + sis->irq = -1; + sis->ioport = pci_resource_start(pci, 0); + +- rc = pcim_request_all_regions(pci, "SiS7019"); ++ rc = pcim_request_region(pci, 0, "SiS7019"); + if (rc) { +- dev_err(&pci->dev, "unable request regions\n"); ++ dev_err(&pci->dev, "unable request I/O region\n"); + return rc; + } + +- sis->ioaddr = devm_ioremap(&pci->dev, pci_resource_start(pci, 1), 0x4000); +- if (!sis->ioaddr) { ++ sis->ioaddr = pcim_iomap_region(pci, 1, "SiS7019"); ++ if (IS_ERR(sis->ioaddr)) { + dev_err(&pci->dev, "unable to remap MMIO, aborting\n"); +- return -EIO; ++ return PTR_ERR(sis->ioaddr); + } + + rc = sis_alloc_suspend(sis); +diff --git a/sound/sh/aica.c b/sound/sh/aica.c +--- a/sound/sh/aica.c ++++ b/sound/sh/aica.c +@@ -518,8 +518,9 @@ static const struct snd_kcontrol_new snd_aica_pcmvolume_control = { + static int load_aica_firmware(void) + { + int err; +- const struct firmware *fw_entry; + spu_reset(); ++ ++ const struct firmware *fw_entry __free(firmware) = NULL; + err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev); + if (unlikely(err)) + return err; +@@ -527,7 +528,6 @@ static int load_aica_firmware(void) + spu_disable(); + spu_memload(0, fw_entry->data, fw_entry->size); + spu_enable(); +- release_firmware(fw_entry); + return err; + } + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -726,6 +726,10 @@ config SND_SOC_AW87390 + sound quality, which is a new high efficiency, low + noise, constant large volume, 6th Smart K audio amplifier. + ++config SND_SOC_AW88399_LIB ++ tristate ++ select SND_SOC_AW88395_LIB ++ + config SND_SOC_AW88399 + tristate "Soc Audio for awinic aw88399" + depends on I2C +@@ -733,6 +737,7 @@ config SND_SOC_AW88399 + select REGMAP_I2C + select GPIOLIB + select SND_SOC_AW88395_LIB ++ select SND_SOC_AW88399_LIB + help + This option enables support for aw88399 Smart PA. + The awinic AW88399 is an I2S/TDM input, high efficiency +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -55,6 +55,7 @@ snd-soc-aw88395-y := aw88395/aw88395.o + snd-soc-aw88166-y := aw88166.o + snd-soc-aw88261-y := aw88261.o + snd-soc-aw88399-y := aw88399.o ++snd-soc-aw88399-lib-y := aw88399-lib.o + snd-soc-bd28623-y := bd28623.o + snd-soc-bt-sco-y := bt-sco.o + snd-soc-chv3-codec-y := chv3-codec.o +@@ -496,6 +497,7 @@ obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o + obj-$(CONFIG_SND_SOC_AW88166) +=snd-soc-aw88166.o + obj-$(CONFIG_SND_SOC_AW88261) +=snd-soc-aw88261.o + obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o ++obj-$(CONFIG_SND_SOC_AW88399_LIB) += snd-soc-aw88399-lib.o + obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o + obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o + obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o +diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -0,0 +1,1411 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// aw88399-lib.c -- AW88399 Common functions for ASoC and HDA audio drivers ++// ++// Copyright (c) 2023 AWINIC Technology CO., LTD ++// ++// Author: Weidong Wang ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "aw88395/aw88395_device.h" ++ ++const struct regmap_config aw88399_remap_config = { ++ .val_bits = 16, ++ .reg_bits = 8, ++ .max_register = AW88399_REG_MAX, ++ .reg_format_endian = REGMAP_ENDIAN_LITTLE, ++ .val_format_endian = REGMAP_ENDIAN_BIG, ++}; ++EXPORT_SYMBOL_GPL(aw88399_remap_config); ++ ++static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) ++{ ++ int ret; ++ ++ if (pwd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); ++ if (ret) ++ dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); ++ else ++ *int_status = reg_val; ++ ++ dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); ++} ++ ++static void aw_dev_clear_int_status(struct aw_device *aw_dev) ++{ ++ u16 int_status; ++ ++ /* read int status and clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ /* make sure int status is clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ if (int_status) ++ dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); ++} ++ ++static int aw_dev_get_iis_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { ++ dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode1 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ reg_val &= (~AW88399_CCO_MUX_MASK); ++ if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { ++ dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); ++ return -EPERM; ++ } ++ ++ /* change mode2 */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ ++ /* change mode1 */ ++ regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); ++ if (ret == 0) { ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ } ++ ++ return ret; ++} ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev) ++{ ++ int ret; ++ ++ ret = aw_dev_check_mode1_pll(aw_dev); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); ++ ret = aw_dev_check_mode2_pll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 check iis failed"); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_check_syspll); ++ ++static int aw_dev_check_sysst(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int check_val; ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) ++ check_val = AW88399_BIT_SYSST_NOSWS_CHECK; ++ else ++ check_val = AW88399_BIT_SYSST_SWS_CHECK; ++ ++ /* ++ * On some hardware the BSTS (boost-finished) status bit does not ++ * reliably assert even when audio output is working normally. ++ * Allow per-instance bypass when flagged by the side-codec driver. ++ */ ++ if (aw88399->bsts_unreliable) ++ check_val &= ~AW88399_BSTS_FINISHED_VALUE; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { ++ dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", ++ i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) ++{ ++ int ret; ++ ++ if (amppd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) ++{ ++ int ret; ++ ++ if (is_enable) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed\n", __func__); ++} ++EXPORT_SYMBOL_GPL(aw_dev_dsp_enable); ++ ++static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) ++{ ++ uint16_t icalkh_val, icalkl_val, icalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ icalk_val = icalkh_val & icalkl_val; ++ else ++ icalk_val = icalkh_val | icalkl_val; ++ ++ if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) ++ icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; ++ *icalk = (int16_t)icalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = vcalkh_val & vcalkl_val; ++ else ++ vcalk_val = vcalkh_val | vcalkl_val; ++ ++ if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) ++ vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ else ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ ++ if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) ++ vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; ++ ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw_dev_set_vcalb(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int vsense_select, vsense_value; ++ int32_t ical_k, vcal_k, vcalb; ++ int16_t icalk, vcalk; ++ uint16_t reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); ++ if (ret) ++ return ret; ++ ++ vsense_select = vsense_value & (~AW88399_VDSEL_MASK); ++ ++ ret = aw88399_dev_get_icalk(aw88399, &icalk); ++ if (ret) { ++ dev_err(aw_dev->dev, "get icalk failed\n"); ++ return ret; ++ } ++ ++ ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ ++ switch (vsense_select) { ++ case AW88399_DEV_VDSEL_VSENSE: ++ ret = aw88399_dev_get_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * ++ ical_k / vcal_k * aw88399->vcalb_init_val; ++ break; ++ case AW88399_DEV_VDSEL_DAC: ++ ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / ++ AW88399_ISCAL_DAC_FACTOR * ical_k / ++ vcal_k * aw88399->vcalb_init_val; ++ break; ++ default: ++ dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); ++ ret = -EINVAL; ++ break; ++ } ++ if (ret) ++ return ret; ++ ++ vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; ++ reg_val = (uint32_t)vcalb; ++ ++ regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); ++ ++ return 0; ++} ++ ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ uint16_t re_lbits, re_hbits; ++ u32 cali_re; ++ int ret; ++ ++ if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || ++ (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) ++ return -EINVAL; ++ ++ cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + ++ aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); ++ ++ re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; ++ re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); ++ if (ret) { ++ dev_err(aw_dev->dev, "set cali re error"); ++ return ret; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); ++ if (ret) ++ dev_err(aw_dev->dev, "set cali re error"); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_update_cali_re); ++ ++static int aw_dev_fw_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, fw_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate fw_end_addr */ ++ fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; ++ ++ /* write fw_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, fw_len_val); ++ if (ret) ++ return ret; ++ /* enable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); ++ ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ /* read crc check result */ ++ regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", ++ __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, cfg_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate cfg end addr */ ++ cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; ++ ++ /* write cfg_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); ++ if (ret) ++ return ret; ++ ++ /* enable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ /* read crc check result */ ++ ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", ++ check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_hw_crc_check(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); ++ if (ret) ++ return ret; ++ ++ ret = aw_dev_fw_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = aw_dev_cfg_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "cfg_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ ++ return ret; ++ ++crc_check_failed: ++ regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ return ret; ++} ++ ++static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) ++{ ++ int ret; ++ ++ if (flag) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++int aw_dev_get_dsp_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); ++ if (ret) ++ return ret; ++ if (!(reg_val & (~AW88399_WDT_CNT_MASK))) ++ return -EPERM; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_get_dsp_status); ++ ++static int aw_dev_dsp_check(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ switch (aw_dev->dsp_cfg) { ++ case AW88399_DEV_DSP_BYPASS: ++ dev_dbg(aw_dev->dev, "dsp bypass"); ++ ret = 0; ++ break; ++ case AW88399_DEV_DSP_WORK: ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_dsp_enable(aw_dev, true); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { ++ ret = aw_dev_get_dsp_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } ++ } ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) ++{ ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ unsigned int reg_value; ++ u16 real_value; ++ int ret; ++ ++ real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); ++ ++ real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_set_volume); ++ ++static void aw_dev_fade_in(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ u16 fade_in_vol = desc->ctl_volume; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_in_time == 0) { ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++ return; ++ } ++ ++ for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); ++ } ++ ++ if (i != fade_in_vol) ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++} ++ ++static void aw_dev_fade_out(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_out_time == 0) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ return; ++ } ++ ++ for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++ ++ if (i != AW88399_MUTE_VOL) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++} ++ ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) ++{ ++ if (is_mute) { ++ aw_dev_fade_out(aw_dev); ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); ++ } else { ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); ++ aw_dev_fade_in(aw_dev); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_mute); ++ ++static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ ++ if (dither) ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); ++ else ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); ++} ++ ++static int aw88399_dev_start(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ if (aw_dev->status == AW88399_DEV_PW_ON) { ++ dev_dbg(aw_dev->dev, "already power on"); ++ return 0; ++ } ++ ++ aw88399_dev_set_dither(aw88399, false); ++ ++ /* power on */ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ ret = aw_dev_check_syspll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "pll check failed cannot start"); ++ goto pll_check_fail; ++ } ++ ++ /* amppd on */ ++ aw_dev_amppd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 50); ++ ++ /* check i2s status */ ++ ret = aw_dev_check_sysst(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "sysst check failed"); ++ goto sysst_check_fail; ++ } ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { ++ ret = aw_dev_hw_crc_check(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp crc check failed"); ++ goto crc_check_fail; ++ } ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_set_vcalb(aw88399); ++ aw_dev_update_cali_re(&aw_dev->cali_desc); ++ ++ ret = aw_dev_dsp_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp status check failed"); ++ goto dsp_check_fail; ++ } ++ } else { ++ dev_dbg(aw_dev->dev, "start pa with dsp bypass"); ++ } ++ ++ /* enable tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, true); ++ ++ if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) ++ aw88399_dev_set_dither(aw88399, true); ++ ++ /* close mute */ ++ aw88399_dev_mute(aw_dev, false); ++ /* clear inturrupt */ ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev->status = AW88399_DEV_PW_ON; ++ ++ return 0; ++ ++dsp_check_fail: ++crc_check_fail: ++ aw_dev_dsp_enable(aw_dev, false); ++sysst_check_fail: ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev_amppd(aw_dev, true); ++pll_check_fail: ++ aw_dev_pwd(aw_dev, true); ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_container(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len, unsigned short base) ++{ ++ u32 tmp_len; ++ int i, ret; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { ++ tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); ++ ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, ++ &data[i], tmp_len); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ u32 dsp_ra; ++ int ret; ++ ++ ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, ++ &dsp_ra, AW_DSP_32_DATA); ++ if (ret) { ++ dev_err(aw_dev->dev, "read ra error"); ++ return ret; ++ } ++ ++ cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, ++ AW88399_DSP_RE_SHIFT); ++ ++ return 0; ++} ++ ++static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp config len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp config data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); ++ if (ret) ++ return ret; ++ ++ aw_dev->dsp_cfg_len = len; ++ ++ ret = aw_dev_get_ra(&aw_dev->cali_desc); ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ aw_dev->dsp_fw_len = len; ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); ++ ++ return ret; ++} ++ ++static int aw_dev_check_sram(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ ++ /* read dsp_rom_check_reg */ ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { ++ dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", ++ reg_val, AW88399_DSP_ROM_CHECK_DATA); ++ return -EPERM; ++ } ++ ++ /* check dsp_cfg_base_addr */ ++ aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, ++ AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { ++ dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", ++ reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); ++ return -EPERM; ++ } ++ ++ return 0; ++} ++ ++static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) ++{ ++ int ret; ++ ++ switch (flag) { ++ case AW88399_DEV_MEMCLK_PLL: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_DAPHCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select pll failed"); ++ break; ++ case AW88399_DEV_MEMCLK_OSC: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_OSCCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select OSC failed"); ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); ++ break; ++ } ++} ++ ++static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) ++{ ++ struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++ return; ++ } ++ if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) ++ profctrl_desc->cur_mode = AW88399_RCV_MODE; ++ else ++ profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; ++} ++ ++static int aw_dev_update_reg_container(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ u16 read_vol, reg_val; ++ int data_len, i, ret; ++ int16_t *reg_data; ++ u8 reg_addr; ++ ++ reg_data = (int16_t *)data; ++ data_len = len >> 1; ++ ++ if (data_len & 0x1) { ++ dev_err(aw_dev->dev, "data len:%d unsupported", data_len); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < data_len; i += 2) { ++ reg_addr = reg_data[i]; ++ reg_val = reg_data[i + 1]; ++ ++ if (reg_addr == AW88399_DSPVCALB_REG) { ++ aw88399->vcalb_init_val = reg_val; ++ continue; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL_REG) { ++ if (reg_val & (~AW88399_DSPBY_MASK)) ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; ++ else ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; ++ ++ reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | ++ AW88399_DSPBY_MASK); ++ reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | ++ AW88399_DSPBY_BYPASS_VALUE); ++ } ++ ++ if (reg_addr == AW88399_I2SCTRL3_REG) { ++ reg_val &= AW88399_I2STXEN_MASK; ++ reg_val |= AW88399_I2STXEN_DISABLE_VALUE; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL2_REG) { ++ read_vol = (reg_val & (~AW88399_VOL_MASK)) >> ++ AW88399_VOL_START_BIT; ++ aw_dev->volume_desc.init_volume = read_vol; ++ } ++ ++ if (reg_addr == AW88399_DBGCTRL_REG) { ++ if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) ++ aw88399->check_val = AW_EF_OR_CHECK; ++ else ++ aw88399->check_val = AW_EF_AND_CHECK; ++ ++ aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); ++ } ++ ++ if (reg_addr == AW88399_CRCCTRL_REG) ++ aw88399->crc_init_val = reg_val; ++ ++ ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); ++ if (ret) ++ return ret; ++ } ++ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ aw_dev_get_cur_mode_st(aw_dev); ++ ++ if (aw_dev->prof_cur != aw_dev->prof_index) ++ vol_desc->ctl_volume = 0; ++ else ++ aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); ++ ++ return 0; ++} ++ ++static int aw_dev_reg_update(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ if (!len || !data) { ++ dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_update_reg_container(aw88399, data, len); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "reg update failed"); ++ ++ return ret; ++} ++ ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) ++{ ++ struct aw_prof_info *prof_info = &aw_dev->prof_info; ++ struct aw_prof_desc *prof_desc; ++ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "index[%d] overflow count[%d]", ++ index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ *prof_name = prof_info->prof_name_list[prof_desc->id]; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_get_prof_name); ++ ++static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, ++ struct aw_prof_desc **prof_desc) ++{ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", ++ __func__, index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ *prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ return 0; ++} ++ ++static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_prof_desc *prof_index_desc; ++ struct aw_sec_data_desc *sec_desc; ++ char *prof_name; ++ int ret; ++ ++ if ((aw_dev->prof_cur == aw_dev->prof_index) && ++ (force_up_en == AW88399_FORCE_UPDATE_OFF)) { ++ dev_dbg(aw_dev->dev, "scene no change, not update"); ++ return 0; ++ } ++ ++ if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { ++ dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); ++ return -EPERM; ++ } ++ ++ ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "start update %s", prof_name); ++ ++ ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); ++ if (ret) ++ return ret; ++ ++ /* update reg */ ++ sec_desc = prof_index_desc->sec_desc; ++ ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, ++ sec_desc[AW88395_DATA_TYPE_REG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update reg failed"); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ ++ ret = aw_dev_check_sram(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "check sram failed"); ++ goto error; ++ } ++ ++ if (up_dsp_fw_en) { ++ dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); ++ ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_FW].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp fw failed"); ++ goto error; ++ } ++ } ++ ++ /* update dsp config */ ++ ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp cfg failed"); ++ goto error; ++ } ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ ++ aw_dev->prof_cur = aw_dev->prof_index; ++ ++ return 0; ++ ++error: ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ return ret; ++} ++ ++static void aw88399_start_pa(struct aw88399 *aw88399) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_START_RETRIES; i++) { ++ ret = aw88399_dev_start(aw88399); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); ++ ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed"); ++ continue; ++ } ++ } else { ++ dev_dbg(aw88399->aw_pa->dev, "start success\n"); ++ break; ++ } ++ } ++} ++ ++void aw88399_startup_work(struct work_struct *work) ++{ ++ struct aw88399 *aw88399 = ++ container_of(work, struct aw88399, start_work.work); ++ ++ mutex_lock(&aw88399->lock); ++ aw88399_start_pa(aw88399); ++ mutex_unlock(&aw88399->lock); ++} ++EXPORT_SYMBOL_GPL(aw88399_startup_work); ++ ++void aw88399_start(struct aw88399 *aw88399, bool sync_start) ++{ ++ int ret; ++ ++ if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) ++ return; ++ ++ if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) ++ return; ++ ++ ret = aw88399_dev_fw_update(aw88399, aw88399->fw_needs_reload ? ++ AW88399_DSP_FW_UPDATE_ON : AW88399_DSP_FW_UPDATE_OFF, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed."); ++ return; ++ } ++ ++ aw88399->fw_needs_reload = false; ++ ++ if (sync_start == AW88399_SYNC_START) ++ aw88399_start_pa(aw88399); ++ else ++ queue_delayed_work(system_dfl_wq, ++ &aw88399->start_work, ++ AW88399_START_WORK_DELAY_MS); ++} ++EXPORT_SYMBOL_GPL(aw88399_start); ++ ++static int aw_dev_check_sysint(struct aw_device *aw_dev) ++{ ++ u16 reg_val; ++ ++ aw_dev_get_int_status(aw_dev, ®_val); ++ if (reg_val & AW88399_BIT_SYSINT_CHECK) { ++ dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++int aw88399_stop(struct aw_device *aw_dev) ++{ ++ struct aw_sec_data_desc *dsp_cfg = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; ++ struct aw_sec_data_desc *dsp_fw = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; ++ int int_st; ++ ++ if (aw_dev->status == AW88399_DEV_PW_OFF) { ++ dev_dbg(aw_dev->dev, "already power off"); ++ return 0; ++ } ++ ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ aw88399_dev_mute(aw_dev, true); ++ usleep_range(AW88399_4000_US, AW88399_4000_US + 100); ++ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ int_st = aw_dev_check_sysint(aw_dev); ++ ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_amppd(aw_dev, true); ++ ++ if (int_st) { ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); ++ aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ } ++ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_stop); ++ ++static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); ++ if (ret) { ++ dev_err(aw_dev->dev, "aw_dev acf parse failed"); ++ return -EINVAL; ++ } ++ aw_dev->fade_in_time = AW88399_1000_US / 10; ++ aw_dev->fade_out_time = AW88399_1000_US >> 1; ++ aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; ++ aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; ++ ++ ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ /* close tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ /* enable amppd */ ++ aw_dev_amppd(aw_dev, true); ++ ++ /* close dsp */ ++ aw_dev_dsp_enable(aw_dev, false); ++ /* set power down */ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++ ++int aw88399_request_firmware_file(struct aw88399 *aw88399) ++{ ++ const struct firmware *cont = NULL; ++ int ret; ++ ++ aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; ++ ++ ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", ++ AW88399_ACF_FILE, cont ? cont->size : 0); ++ ++ aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, ++ struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); ++ if (!aw88399->aw_cfg) { ++ release_firmware(cont); ++ return -ENOMEM; ++ } ++ aw88399->aw_cfg->len = (int)cont->size; ++ memcpy(aw88399->aw_cfg->data, cont->data, cont->size); ++ release_firmware(cont); ++ ++ ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ mutex_lock(&aw88399->lock); ++ /* aw device init */ ++ ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "dev init failed"); ++ mutex_unlock(&aw88399->lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw88399_request_firmware_file); ++ ++void aw88399_hw_reset(struct aw88399 *aw88399) ++{ ++ if (aw88399->reset_gpio) { ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 0); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_hw_reset); ++ ++static void aw88399_parse_channel_dt(struct aw_device *aw_dev) ++{ ++ struct device_node *np = aw_dev->dev->of_node; ++ u32 channel_value; ++ int ret; ++ ++ ret = of_property_read_u32(np, "awinic,audio-channel", &channel_value); ++ if (ret) { ++ /* ++ * On ACPI systems, DT properties don't exist. Derive channel ++ * from I2C address: 0x34 -> channel 0 (left), 0x35 -> channel 1 (right) ++ */ ++ aw_dev->channel = aw_dev->i2c->addr - 0x34; ++ dev_dbg(aw_dev->dev, ++ "DT channel property not found, using I2C address-based channel %d (addr 0x%02x)\n", ++ aw_dev->channel, aw_dev->i2c->addr); ++ return; ++ } ++ aw_dev->channel = channel_value; ++} ++ ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) ++{ ++ struct aw_device *aw_dev; ++ unsigned int chip_id; ++ int ret; ++ ++ ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); ++ if (ret) { ++ dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); ++ return ret; ++ } ++ if (chip_id != AW88399_CHIP_ID) { ++ dev_err(&i2c->dev, "unsupported device"); ++ return -ENXIO; ++ } ++ dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); ++ ++ aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); ++ if (!aw_dev) ++ return -ENOMEM; ++ aw88399->aw_pa = aw_dev; ++ ++ aw_dev->i2c = i2c; ++ aw_dev->dev = &i2c->dev; ++ aw_dev->regmap = regmap; ++ mutex_init(&aw_dev->dsp_lock); ++ ++ aw_dev->chip_id = chip_id; ++ aw_dev->acf = NULL; ++ aw_dev->prof_info.prof_desc = NULL; ++ aw_dev->prof_info.count = 0; ++ aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; ++ aw_dev->channel = AW88399_DEV_DEFAULT_CH; ++ aw_dev->fw_status = AW88399_DEV_FW_FAILED; ++ ++ aw_dev->fade_step = AW88399_VOLUME_STEP_DB; ++ aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; ++ ++ aw88399_parse_channel_dt(aw_dev); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_init); ++ ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel) ++{ ++ aw88399->aw_pa->channel = channel; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_set_channel); ++ ++MODULE_DESCRIPTION("AW88399 common device library"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c +--- a/sound/soc/codecs/aw88399.c ++++ b/sound/soc/codecs/aw88399.c +@@ -10,1217 +10,12 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include + #include "aw88399.h" + #include "aw88395/aw88395_device.h" + +-static const struct regmap_config aw88399_remap_config = { +- .val_bits = 16, +- .reg_bits = 8, +- .max_register = AW88399_REG_MAX, +- .reg_format_endian = REGMAP_ENDIAN_LITTLE, +- .val_format_endian = REGMAP_ENDIAN_BIG, +-}; +- +-static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) +-{ +- int ret; +- +- if (pwd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); +- if (ret) +- dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); +- else +- *int_status = reg_val; +- +- dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); +-} +- +-static void aw_dev_clear_int_status(struct aw_device *aw_dev) +-{ +- u16 int_status; +- +- /* read int status and clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- /* make sure int status is clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- if (int_status) +- dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); +-} +- +-static int aw_dev_get_iis_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { +- dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode1 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); +- if (ret) +- return ret; +- +- reg_val &= (~AW88399_CCO_MUX_MASK); +- if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { +- dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); +- return -EPERM; +- } +- +- /* change mode2 */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); +- if (ret) +- return ret; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- +- /* change mode1 */ +- regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); +- if (ret == 0) { +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- } +- +- return ret; +-} +- +-static int aw_dev_check_syspll(struct aw_device *aw_dev) +-{ +- int ret; +- +- ret = aw_dev_check_mode1_pll(aw_dev); +- if (ret) { +- dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); +- ret = aw_dev_check_mode2_pll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 check iis failed"); +- return ret; +- } +- } +- +- return 0; +-} +- +-static int aw_dev_check_sysst(struct aw_device *aw_dev) +-{ +- unsigned int check_val; +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); +- if (ret) +- return ret; +- +- if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) +- check_val = AW88399_BIT_SYSST_NOSWS_CHECK; +- else +- check_val = AW88399_BIT_SYSST_SWS_CHECK; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- +- if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { +- dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", +- i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) +-{ +- int ret; +- +- if (amppd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) +-{ +- int ret; +- +- if (is_enable) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed\n", __func__); +-} +- +-static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) +-{ +- uint16_t icalkh_val, icalkl_val, icalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); +- if (ret) +- return ret; +- icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); +- if (ret) +- return ret; +- icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- icalk_val = icalkh_val & icalkl_val; +- else +- icalk_val = icalkh_val | icalkl_val; +- +- if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) +- icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; +- *icalk = (int16_t)icalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = vcalkh_val & vcalkl_val; +- else +- vcalk_val = vcalkh_val | vcalkl_val; +- +- if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) +- vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); +- if (ret) +- return ret; +- vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); +- if (ret) +- return ret; +- vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- else +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- +- if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) +- vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; +- +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw_dev_set_vcalb(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int vsense_select, vsense_value; +- int32_t ical_k, vcal_k, vcalb; +- int16_t icalk, vcalk; +- uint16_t reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); +- if (ret) +- return ret; +- +- vsense_select = vsense_value & (~AW88399_VDSEL_MASK); +- +- ret = aw88399_dev_get_icalk(aw88399, &icalk); +- if (ret) { +- dev_err(aw_dev->dev, "get icalk failed\n"); +- return ret; +- } +- +- ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- +- switch (vsense_select) { +- case AW88399_DEV_VDSEL_VSENSE: +- ret = aw88399_dev_get_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * +- ical_k / vcal_k * aw88399->vcalb_init_val; +- break; +- case AW88399_DEV_VDSEL_DAC: +- ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / +- AW88399_ISCAL_DAC_FACTOR * ical_k / +- vcal_k * aw88399->vcalb_init_val; +- break; +- default: +- dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); +- ret = -EINVAL; +- break; +- } +- if (ret) +- return ret; +- +- vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; +- reg_val = (uint32_t)vcalb; +- +- regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); +- +- return 0; +-} +- +-static int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- uint16_t re_lbits, re_hbits; +- u32 cali_re; +- int ret; +- +- if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || +- (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) +- return -EINVAL; +- +- cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + +- aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); +- +- re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; +- re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); +- if (ret) { +- dev_err(aw_dev->dev, "set cali re error"); +- return ret; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); +- if (ret) +- dev_err(aw_dev->dev, "set cali re error"); +- +- return ret; +-} +- +-static int aw_dev_fw_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, fw_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate fw_end_addr */ +- fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; +- +- /* write fw_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, fw_len_val); +- if (ret) +- return ret; +- /* enable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); +- +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- /* read crc check result */ +- regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", +- __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, cfg_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate cfg end addr */ +- cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; +- +- /* write cfg_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); +- if (ret) +- return ret; +- +- /* enable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); +- if (ret) +- return ret; +- +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- /* read crc check result */ +- ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", +- check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_hw_crc_check(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); +- if (ret) +- return ret; +- +- ret = aw_dev_fw_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "fw_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = aw_dev_cfg_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "cfg_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); +- if (ret) +- return ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- +- return ret; +- +-crc_check_failed: +- regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- return ret; +-} +- +-static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) +-{ +- int ret; +- +- if (flag) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static int aw_dev_get_dsp_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); +- if (ret) +- return ret; +- if (!(reg_val & (~AW88399_WDT_CNT_MASK))) +- return -EPERM; +- +- return 0; +-} +- +-static int aw_dev_dsp_check(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- switch (aw_dev->dsp_cfg) { +- case AW88399_DEV_DSP_BYPASS: +- dev_dbg(aw_dev->dev, "dsp bypass"); +- ret = 0; +- break; +- case AW88399_DEV_DSP_WORK: +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_dsp_enable(aw_dev, true); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { +- ret = aw_dev_get_dsp_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } +- } +- break; +- default: +- dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); +- ret = -EINVAL; +- break; +- } +- +- return ret; +-} +- +-static int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) +-{ +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- unsigned int reg_value; +- u16 real_value; +- int ret; +- +- real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); +- +- real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); +- +- ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); +- +- return ret; +-} +- +-static void aw_dev_fade_in(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- u16 fade_in_vol = desc->ctl_volume; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_in_time == 0) { +- aw_dev_set_volume(aw_dev, fade_in_vol); +- return; +- } +- +- for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); +- } +- +- if (i != fade_in_vol) +- aw_dev_set_volume(aw_dev, fade_in_vol); +-} +- +-static void aw_dev_fade_out(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_out_time == 0) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- return; +- } +- +- for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +- +- if (i != AW88399_MUTE_VOL) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +-} +- +-static void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) +-{ +- if (is_mute) { +- aw_dev_fade_out(aw_dev); +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); +- } else { +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); +- aw_dev_fade_in(aw_dev); +- } +-} +- +-static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- +- if (dither) +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); +- else +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); +-} +- +-static int aw88399_dev_start(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- if (aw_dev->status == AW88399_DEV_PW_ON) { +- dev_dbg(aw_dev->dev, "already power on"); +- return 0; +- } +- +- aw88399_dev_set_dither(aw88399, false); +- +- /* power on */ +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- ret = aw_dev_check_syspll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "pll check failed cannot start"); +- goto pll_check_fail; +- } +- +- /* amppd on */ +- aw_dev_amppd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 50); +- +- /* check i2s status */ +- ret = aw_dev_check_sysst(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "sysst check failed"); +- goto sysst_check_fail; +- } +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { +- ret = aw_dev_hw_crc_check(aw88399); +- if (ret) { +- dev_err(aw_dev->dev, "dsp crc check failed"); +- goto crc_check_fail; +- } +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_set_vcalb(aw88399); +- aw_dev_update_cali_re(&aw_dev->cali_desc); +- +- ret = aw_dev_dsp_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp status check failed"); +- goto dsp_check_fail; +- } +- } else { +- dev_dbg(aw_dev->dev, "start pa with dsp bypass"); +- } +- +- /* enable tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, true); +- +- if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) +- aw88399_dev_set_dither(aw88399, true); +- +- /* close mute */ +- aw88399_dev_mute(aw_dev, false); +- /* clear inturrupt */ +- aw_dev_clear_int_status(aw_dev); +- aw_dev->status = AW88399_DEV_PW_ON; +- +- return 0; +- +-dsp_check_fail: +-crc_check_fail: +- aw_dev_dsp_enable(aw_dev, false); +-sysst_check_fail: +- aw_dev_clear_int_status(aw_dev); +- aw_dev_amppd(aw_dev, true); +-pll_check_fail: +- aw_dev_pwd(aw_dev, true); +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- return ret; +-} +- +-static int aw_dev_dsp_update_container(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len, unsigned short base) +-{ +- u32 tmp_len; +- int i, ret; +- +- ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); +- if (ret) +- return ret; +- +- for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { +- tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); +- ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, +- &data[i], tmp_len); +- if (ret) +- return ret; +- } +- +- return 0; +-} +- +-static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- u32 dsp_ra; +- int ret; +- +- ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, +- &dsp_ra, AW_DSP_32_DATA); +- if (ret) { +- dev_err(aw_dev->dev, "read ra error"); +- return ret; +- } +- +- cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, +- AW88399_DSP_RE_SHIFT); +- +- return 0; +-} +- +-static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp config len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp config data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); +- if (ret) +- return ret; +- +- aw_dev->dsp_cfg_len = len; +- +- ret = aw_dev_get_ra(&aw_dev->cali_desc); +- +- return ret; +-} +- +-static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); +- return -EINVAL; +- } +- +- aw_dev->dsp_fw_len = len; +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); +- +- return ret; +-} +- +-static int aw_dev_check_sram(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- +- /* read dsp_rom_check_reg */ +- aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { +- dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", +- reg_val, AW88399_DSP_ROM_CHECK_DATA); +- return -EPERM; +- } +- +- /* check dsp_cfg_base_addr */ +- aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, +- AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); +- aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { +- dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", +- reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); +- return -EPERM; +- } +- +- return 0; +-} +- +-static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) +-{ +- int ret; +- +- switch (flag) { +- case AW88399_DEV_MEMCLK_PLL: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_DAPHCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select pll failed"); +- break; +- case AW88399_DEV_MEMCLK_OSC: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_OSCCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select OSC failed"); +- break; +- default: +- dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); +- break; +- } +-} +- +-static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) +-{ +- struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); +- if (ret) { +- dev_dbg(aw_dev->dev, "%s failed", __func__); +- return; +- } +- if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) +- profctrl_desc->cur_mode = AW88399_RCV_MODE; +- else +- profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; +-} +- +-static int aw_dev_update_reg_container(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- u16 read_vol, reg_val; +- int data_len, i, ret; +- int16_t *reg_data; +- u8 reg_addr; +- +- reg_data = (int16_t *)data; +- data_len = len >> 1; +- +- if (data_len & 0x1) { +- dev_err(aw_dev->dev, "data len:%d unsupported", data_len); +- return -EINVAL; +- } +- +- for (i = 0; i < data_len; i += 2) { +- reg_addr = reg_data[i]; +- reg_val = reg_data[i + 1]; +- +- if (reg_addr == AW88399_DSPVCALB_REG) { +- aw88399->vcalb_init_val = reg_val; +- continue; +- } +- +- if (reg_addr == AW88399_SYSCTRL_REG) { +- if (reg_val & (~AW88399_DSPBY_MASK)) +- aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; +- else +- aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; +- +- reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | +- AW88399_DSPBY_MASK); +- reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | +- AW88399_DSPBY_BYPASS_VALUE); +- } +- +- if (reg_addr == AW88399_I2SCTRL3_REG) { +- reg_val &= AW88399_I2STXEN_MASK; +- reg_val |= AW88399_I2STXEN_DISABLE_VALUE; +- } +- +- if (reg_addr == AW88399_SYSCTRL2_REG) { +- read_vol = (reg_val & (~AW88399_VOL_MASK)) >> +- AW88399_VOL_START_BIT; +- aw_dev->volume_desc.init_volume = read_vol; +- } +- +- if (reg_addr == AW88399_DBGCTRL_REG) { +- if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) +- aw88399->check_val = AW_EF_OR_CHECK; +- else +- aw88399->check_val = AW_EF_AND_CHECK; +- +- aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); +- } +- +- if (reg_addr == AW88399_CRCCTRL_REG) +- aw88399->crc_init_val = reg_val; +- +- ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); +- if (ret) +- return ret; +- } +- +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- aw_dev_get_cur_mode_st(aw_dev); +- +- if (aw_dev->prof_cur != aw_dev->prof_index) +- vol_desc->ctl_volume = 0; +- else +- aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); +- +- return 0; +-} +- +-static int aw_dev_reg_update(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- if (!len || !data) { +- dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_update_reg_container(aw88399, data, len); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "reg update failed"); +- +- return ret; +-} +- +-static int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) +-{ +- struct aw_prof_info *prof_info = &aw_dev->prof_info; +- struct aw_prof_desc *prof_desc; +- +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "index[%d] overflow count[%d]", +- index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- *prof_name = prof_info->prof_name_list[prof_desc->id]; +- +- return 0; +-} +- +-static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, +- struct aw_prof_desc **prof_desc) +-{ +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", +- __func__, index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- *prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- return 0; +-} +- +-static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_prof_desc *prof_index_desc; +- struct aw_sec_data_desc *sec_desc; +- char *prof_name; +- int ret; +- +- if ((aw_dev->prof_cur == aw_dev->prof_index) && +- (force_up_en == AW88399_FORCE_UPDATE_OFF)) { +- dev_dbg(aw_dev->dev, "scene no change, not update"); +- return 0; +- } +- +- if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { +- dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); +- return -EPERM; +- } +- +- ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "start update %s", prof_name); +- +- ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); +- if (ret) +- return ret; +- +- /* update reg */ +- sec_desc = prof_index_desc->sec_desc; +- ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, +- sec_desc[AW88395_DATA_TYPE_REG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update reg failed"); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- +- ret = aw_dev_check_sram(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "check sram failed"); +- goto error; +- } +- +- if (up_dsp_fw_en) { +- dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); +- ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, +- sec_desc[AW88395_DATA_TYPE_DSP_FW].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp fw failed"); +- goto error; +- } +- } +- +- /* update dsp config */ +- ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, +- sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp cfg failed"); +- goto error; +- } +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- +- aw_dev->prof_cur = aw_dev->prof_index; +- +- return 0; +- +-error: +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- return ret; +-} +- +-static void aw88399_start_pa(struct aw88399 *aw88399) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_START_RETRIES; i++) { +- ret = aw88399_dev_start(aw88399); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed"); +- continue; +- } +- } else { +- dev_dbg(aw88399->aw_pa->dev, "start success\n"); +- break; +- } +- } +-} +- +-static void aw88399_startup_work(struct work_struct *work) +-{ +- struct aw88399 *aw88399 = +- container_of(work, struct aw88399, start_work.work); +- +- mutex_lock(&aw88399->lock); +- aw88399_start_pa(aw88399); +- mutex_unlock(&aw88399->lock); +-} +- +-static void aw88399_start(struct aw88399 *aw88399, bool sync_start) +-{ +- int ret; +- +- if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) +- return; +- +- if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) +- return; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed."); +- return; +- } +- +- if (sync_start == AW88399_SYNC_START) +- aw88399_start_pa(aw88399); +- else +- queue_delayed_work(system_dfl_wq, +- &aw88399->start_work, +- AW88399_START_WORK_DELAY_MS); +-} +- +-static int aw_dev_check_sysint(struct aw_device *aw_dev) +-{ +- u16 reg_val; +- +- aw_dev_get_int_status(aw_dev, ®_val); +- if (reg_val & AW88399_BIT_SYSINT_CHECK) { +- dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw88399_stop(struct aw_device *aw_dev) +-{ +- struct aw_sec_data_desc *dsp_cfg = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; +- struct aw_sec_data_desc *dsp_fw = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; +- int int_st; +- +- if (aw_dev->status == AW88399_DEV_PW_OFF) { +- dev_dbg(aw_dev->dev, "already power off"); +- return 0; +- } +- +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- aw88399_dev_mute(aw_dev, true); +- usleep_range(AW88399_4000_US, AW88399_4000_US + 100); +- +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- int_st = aw_dev_check_sysint(aw_dev); +- +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_amppd(aw_dev, true); +- +- if (int_st) { +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); +- aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- } +- +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- + static struct snd_soc_dai_driver aw88399_dai[] = { + { + .name = "aw88399-aif", +@@ -1869,86 +664,6 @@ static int aw88399_calib_set(struct snd_kcontrol *kcontrol, + return 0; + } + +-static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); +- if (ret) { +- dev_err(aw_dev->dev, "aw_dev acf parse failed"); +- return -EINVAL; +- } +- aw_dev->fade_in_time = AW88399_1000_US / 10; +- aw_dev->fade_out_time = AW88399_1000_US >> 1; +- aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; +- aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); +- if (ret) { +- dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- /* close tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- /* enable amppd */ +- aw_dev_amppd(aw_dev, true); +- +- /* close dsp */ +- aw_dev_dsp_enable(aw_dev, false); +- /* set power down */ +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- +-static int aw88399_request_firmware_file(struct aw88399 *aw88399) +-{ +- const struct firmware *cont = NULL; +- int ret; +- +- aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +- +- ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", +- AW88399_ACF_FILE, cont ? cont->size : 0); +- +- aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, +- struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); +- return -ENOMEM; +- } +- aw88399->aw_cfg->len = (int)cont->size; +- memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); +- +- ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- mutex_lock(&aw88399->lock); +- /* aw device init */ +- ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88399->lock); +- +- return ret; +-} +- + static const struct snd_kcontrol_new aw88399_controls[] = { + SOC_SINGLE_EXT("PCM Playback Volume", AW88399_SYSCTRL2_REG, + 6, AW88399_MUTE_VOL, 0, aw88399_volume_get, +@@ -2040,70 +755,6 @@ static const struct snd_soc_component_driver soc_codec_dev_aw88399 = { + .num_controls = ARRAY_SIZE(aw88399_controls), + }; + +-static void aw88399_hw_reset(struct aw88399 *aw88399) +-{ +- if (aw88399->reset_gpio) { +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 0); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- } +-} +- +-static void aw88399_parse_channel_dt(struct aw_device *aw_dev) +-{ +- struct device_node *np = aw_dev->dev->of_node; +- u32 channel_value; +- +- of_property_read_u32(np, "awinic,audio-channel", &channel_value); +- aw_dev->channel = channel_value; +-} +- +-static int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) +-{ +- struct aw_device *aw_dev; +- unsigned int chip_id; +- int ret; +- +- ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); +- if (ret) { +- dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); +- return ret; +- } +- if (chip_id != AW88399_CHIP_ID) { +- dev_err(&i2c->dev, "unsupported device"); +- return -ENXIO; +- } +- dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); +- +- aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); +- if (!aw_dev) +- return -ENOMEM; +- aw88399->aw_pa = aw_dev; +- +- aw_dev->i2c = i2c; +- aw_dev->dev = &i2c->dev; +- aw_dev->regmap = regmap; +- mutex_init(&aw_dev->dsp_lock); +- +- aw_dev->chip_id = chip_id; +- aw_dev->acf = NULL; +- aw_dev->prof_info.prof_desc = NULL; +- aw_dev->prof_info.count = 0; +- aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; +- aw_dev->channel = AW88399_DEV_DEFAULT_CH; +- aw_dev->fw_status = AW88399_DEV_FW_FAILED; +- +- aw_dev->fade_step = AW88399_VOLUME_STEP_DB; +- aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; +- +- aw88399_parse_channel_dt(aw_dev); +- +- return 0; +-} +- + static int aw88399_i2c_probe(struct i2c_client *i2c) + { + struct aw88399 *aw88399; +diff --git a/sound/soc/codecs/aw88399.h b/sound/soc/codecs/aw88399.h +--- a/sound/soc/codecs/aw88399.h ++++ b/sound/soc/codecs/aw88399.h +@@ -10,512 +10,10 @@ + #ifndef __AW88399_H__ + #define __AW88399_H__ + +-/* registers list */ +-#define AW88399_ID_REG (0x00) +-#define AW88399_SYSST_REG (0x01) +-#define AW88399_SYSINT_REG (0x02) +-#define AW88399_SYSINTM_REG (0x03) +-#define AW88399_SYSCTRL_REG (0x04) +-#define AW88399_SYSCTRL2_REG (0x05) +-#define AW88399_I2SCTRL1_REG (0x06) +-#define AW88399_I2SCTRL2_REG (0x07) +-#define AW88399_I2SCTRL3_REG (0x08) +-#define AW88399_DACCFG1_REG (0x09) +-#define AW88399_DACCFG2_REG (0x0A) +-#define AW88399_DACCFG3_REG (0x0B) +-#define AW88399_DACCFG4_REG (0x0C) +-#define AW88399_DACCFG5_REG (0x0D) +-#define AW88399_DACCFG6_REG (0x0E) +-#define AW88399_DACCFG7_REG (0x0F) +-#define AW88399_MPDCFG1_REG (0x10) +-#define AW88399_MPDCFG2_REG (0x11) +-#define AW88399_MPDCFG3_REG (0x12) +-#define AW88399_MPDCFG4_REG (0x13) +-#define AW88399_PWMCTRL1_REG (0x14) +-#define AW88399_PWMCTRL2_REG (0x15) +-#define AW88399_PWMCTRL3_REG (0x16) +-#define AW88399_I2SCFG1_REG (0x17) +-#define AW88399_DBGCTRL_REG (0x18) +-#define AW88399_HAGCST_REG (0x20) +-#define AW88399_VBAT_REG (0x21) +-#define AW88399_TEMP_REG (0x22) +-#define AW88399_PVDD_REG (0x23) +-#define AW88399_ISNDAT_REG (0x24) +-#define AW88399_VSNDAT_REG (0x25) +-#define AW88399_I2SINT_REG (0x26) +-#define AW88399_I2SCAPCNT_REG (0x27) +-#define AW88399_ANASTA1_REG (0x28) +-#define AW88399_ANASTA2_REG (0x29) +-#define AW88399_ANASTA3_REG (0x2A) +-#define AW88399_TESTDET_REG (0x2B) +-#define AW88399_DSMCFG1_REG (0x30) +-#define AW88399_DSMCFG2_REG (0x31) +-#define AW88399_DSMCFG3_REG (0x32) +-#define AW88399_DSMCFG4_REG (0x33) +-#define AW88399_DSMCFG5_REG (0x34) +-#define AW88399_DSMCFG6_REG (0x35) +-#define AW88399_DSMCFG7_REG (0x36) +-#define AW88399_DSMCFG8_REG (0x37) +-#define AW88399_TESTIN_REG (0x38) +-#define AW88399_TESTOUT_REG (0x39) +-#define AW88399_MEMTEST_REG (0x3A) +-#define AW88399_VSNCTRL1_REG (0x3B) +-#define AW88399_ISNCTRL1_REG (0x3C) +-#define AW88399_ISNCTRL2_REG (0x3D) +-#define AW88399_DSPMADD_REG (0x40) +-#define AW88399_DSPMDAT_REG (0x41) +-#define AW88399_WDT_REG (0x42) +-#define AW88399_ACR1_REG (0x43) +-#define AW88399_ACR2_REG (0x44) +-#define AW88399_ASR1_REG (0x45) +-#define AW88399_ASR2_REG (0x46) +-#define AW88399_DSPCFG_REG (0x47) +-#define AW88399_ASR3_REG (0x48) +-#define AW88399_ASR4_REG (0x49) +-#define AW88399_DSPVCALB_REG (0x4A) +-#define AW88399_CRCCTRL_REG (0x4B) +-#define AW88399_DSPDBG1_REG (0x4C) +-#define AW88399_DSPDBG2_REG (0x4D) +-#define AW88399_DSPDBG3_REG (0x4E) +-#define AW88399_PLLCTRL1_REG (0x50) +-#define AW88399_PLLCTRL2_REG (0x51) +-#define AW88399_PLLCTRL3_REG (0x52) +-#define AW88399_CDACTRL1_REG (0x53) +-#define AW88399_CDACTRL2_REG (0x54) +-#define AW88399_CDACTRL3_REG (0x55) +-#define AW88399_SADCCTRL1_REG (0x56) +-#define AW88399_SADCCTRL2_REG (0x57) +-#define AW88399_BOPCTRL1_REG (0x58) +-#define AW88399_BOPCTRL2_REG (0x5A) +-#define AW88399_BOPCTRL3_REG (0x5B) +-#define AW88399_BOPCTRL4_REG (0x5C) +-#define AW88399_BOPCTRL5_REG (0x5D) +-#define AW88399_BOPCTRL6_REG (0x5E) +-#define AW88399_BOPCTRL7_REG (0x5F) +-#define AW88399_BSTCTRL1_REG (0x60) +-#define AW88399_BSTCTRL2_REG (0x61) +-#define AW88399_BSTCTRL3_REG (0x62) +-#define AW88399_BSTCTRL4_REG (0x63) +-#define AW88399_BSTCTRL5_REG (0x64) +-#define AW88399_BSTCTRL6_REG (0x65) +-#define AW88399_BSTCTRL7_REG (0x66) +-#define AW88399_BSTCTRL8_REG (0x67) +-#define AW88399_BSTCTRL9_REG (0x68) +-#define AW88399_BSTCTRL10_REG (0x69) +-#define AW88399_CPCTRL_REG (0x6A) +-#define AW88399_EFWH_REG (0x6C) +-#define AW88399_EFWM2_REG (0x6D) +-#define AW88399_EFWM1_REG (0x6E) +-#define AW88399_EFWL_REG (0x6F) +-#define AW88399_TESTCTRL1_REG (0x70) +-#define AW88399_TESTCTRL2_REG (0x71) +-#define AW88399_EFCTRL1_REG (0x72) +-#define AW88399_EFCTRL2_REG (0x73) +-#define AW88399_EFRH4_REG (0x74) +-#define AW88399_EFRH3_REG (0x75) +-#define AW88399_EFRH2_REG (0x76) +-#define AW88399_EFRH1_REG (0x77) +-#define AW88399_EFRL4_REG (0x78) +-#define AW88399_EFRL3_REG (0x79) +-#define AW88399_EFRL2_REG (0x7A) +-#define AW88399_EFRL1_REG (0x7B) +-#define AW88399_TM_REG (0x7C) +-#define AW88399_TM2_REG (0x7D) +- +-#define AW88399_REG_MAX (0x7E) +-#define AW88399_MUTE_VOL (1023) +- +-#define AW88399_DSP_CFG_ADDR (0x9B00) +-#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) +-#define AW88399_DSP_FW_ADDR (0x8980) +-#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) +-#define AW88399_DSP_ROM_CHECK_DATA (0x4638) +- +-#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) +-#define AW88399_CALI_RE_HBITS_SHIFT (16) +- +-#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) +-#define AW88399_CALI_RE_LBITS_SHIFT (0) +- +-#define AW88399_I2STXEN_START_BIT (9) +-#define AW88399_I2STXEN_BITS_LEN (1) +-#define AW88399_I2STXEN_MASK \ +- (~(((1<> (shift)) +-#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) +-#define AW88399_CRC_CHECK_PASS_VAL (0x4) +- +-#define AW88399_CRC_CFG_BASE_ADDR (0xD80) +-#define AW88399_CRC_FW_BASE_ADDR (0x4C0) +-#define AW88399_ACF_FILE "aw88399_acf.bin" +-#define AW88399_DEV_SYSST_CHECK_MAX (10) +-#define AW88399_CHIP_ID 0x2183 ++#include + + #define AW88399_I2C_NAME "aw88399" + +-#define AW88399_START_RETRIES (5) +-#define AW88399_START_WORK_DELAY_MS (0) +- + #define AW88399_RATES (SNDRV_PCM_RATE_8000_48000 | \ + SNDRV_PCM_RATE_96000) + #define AW88399_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ +@@ -550,80 +48,4 @@ + .put = profile_set, \ + } + +-enum { +- AW_EF_AND_CHECK = 0, +- AW_EF_OR_CHECK, +-}; +- +-enum { +- AW88399_DEV_VDSEL_DAC = 0, +- AW88399_DEV_VDSEL_VSENSE = 32, +-}; +- +-enum { +- AW88399_DSP_CRC_NA = 0, +- AW88399_DSP_CRC_OK = 1, +-}; +- +-enum { +- AW88399_DSP_FW_UPDATE_OFF = 0, +- AW88399_DSP_FW_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_FORCE_UPDATE_OFF = 0, +- AW88399_FORCE_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_1000_US = 1000, +- AW88399_2000_US = 2000, +- AW88399_3000_US = 3000, +- AW88399_4000_US = 4000, +-}; +- +-enum AW88399_DEV_STATUS { +- AW88399_DEV_PW_OFF = 0, +- AW88399_DEV_PW_ON, +-}; +- +-enum AW88399_DEV_FW_STATUS { +- AW88399_DEV_FW_FAILED = 0, +- AW88399_DEV_FW_OK, +-}; +- +-enum AW88399_DEV_MEMCLK { +- AW88399_DEV_MEMCLK_OSC = 0, +- AW88399_DEV_MEMCLK_PLL = 1, +-}; +- +-enum AW88399_DEV_DSP_CFG { +- AW88399_DEV_DSP_WORK = 0, +- AW88399_DEV_DSP_BYPASS = 1, +-}; +- +-enum { +- AW88399_NOT_RCV_MODE = 0, +- AW88399_RCV_MODE = 1, +-}; +- +-enum { +- AW88399_SYNC_START = 0, +- AW88399_ASYNC_START, +-}; +- +-struct aw88399 { +- struct aw_device *aw_pa; +- struct mutex lock; +- struct gpio_desc *reset_gpio; +- struct delayed_work start_work; +- struct regmap *regmap; +- struct aw_container *aw_cfg; +- +- unsigned int check_val; +- unsigned int crc_init_val; +- unsigned int vcalb_init_val; +- unsigned int dither_st; +-}; +- +-#endif ++#endif /* __AW88399_H__ */ +diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c +--- a/sound/soc/codecs/tas2781-fmwlib.c ++++ b/sound/soc/codecs/tas2781-fmwlib.c +@@ -1849,16 +1849,26 @@ static int tasdev_load_blk(struct tasdevice_priv *tas_priv, + } + } + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the hardware time to stabilize before ++ * next block re-transmission attempt. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + + if (block->is_pchksum_present) { + ret = tasdev_block_chksum(tas_priv, block, chn); + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the bus time to recover after ++ * a checksum mismatch error. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + } +diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c +--- a/sound/soc/fsl/fsl-asoc-card.c ++++ b/sound/soc/fsl/fsl-asoc-card.c +@@ -1118,7 +1118,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-format", &asrc_fmt); +- priv->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ priv->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", +diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c +--- a/sound/soc/fsl/fsl_asrc.c ++++ b/sound/soc/fsl/fsl_asrc.c +@@ -1358,7 +1358,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- asrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ asrc->asrc_format = asrc_fmt; + if (ret) { + ret = of_property_read_u32(np, "fsl,asrc-width", &width); + if (ret) { +diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c +--- a/sound/soc/fsl/fsl_asrc_m2m.c ++++ b/sound/soc/fsl/fsl_asrc_m2m.c +@@ -367,13 +367,13 @@ static int fsl_asrc_m2m_comp_set_params(struct snd_compr_stream *stream, + if (ret) + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.format) & cap.fmt_in) +- pair->sample_format[IN] = (__force snd_pcm_format_t)params->codec.format; ++ if (pcm_format_to_bits(params->codec.format) & cap.fmt_in) ++ pair->sample_format[IN] = params->codec.format; + else + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.pcm_format) & cap.fmt_out) +- pair->sample_format[OUT] = (__force snd_pcm_format_t)params->codec.pcm_format; ++ if (pcm_format_to_bits(params->codec.pcm_format) & cap.fmt_out) ++ pair->sample_format[OUT] = params->codec.pcm_format; + else + return -EINVAL; + +@@ -600,7 +600,7 @@ static int fsl_asrc_m2m_fill_codec_caps(struct fsl_asrc *asrc, + cap.rate_in, + cap.rate_in_count * sizeof(__u32)); + codec->descriptor[j].num_sample_rates = cap.rate_in_count; +- codec->descriptor[j].formats = (__force __u32)k; ++ codec->descriptor[j].formats = k; + codec->descriptor[j].pcm_formats = cap.fmt_out; + codec->descriptor[j].src.out_sample_rate_min = cap.rate_out[0]; + codec->descriptor[j].src.out_sample_rate_max = +diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c +--- a/sound/soc/fsl/fsl_easrc.c ++++ b/sound/soc/fsl/fsl_easrc.c +@@ -2227,7 +2227,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- easrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ easrc->asrc_format = asrc_fmt; + if (ret) { + dev_err(dev, "failed to asrc format\n"); + return ret; +diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c +--- a/sound/soc/fsl/fsl_qmc_audio.c ++++ b/sound/soc/fsl/fsl_qmc_audio.c +@@ -503,8 +503,8 @@ static int qmc_dai_constraints_interleaved(struct snd_pcm_substream *substream, + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_INTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +@@ -532,8 +532,8 @@ static int qmc_dai_constraints_noninterleaved(struct snd_pcm_substream *substrea + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c +--- a/sound/soc/fsl/imx-card.c ++++ b/sound/soc/fsl/imx-card.c +@@ -531,7 +531,7 @@ static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); +- snd_mask_set(mask, (__force unsigned int)data->asrc_format); ++ snd_mask_set(mask, data->asrc_format); + + return 0; + } +@@ -684,7 +684,7 @@ static int imx_card_parse_of(struct imx_card_data *data) + } + + ret = of_property_read_u32(args.np, "fsl,asrc-format", &asrc_fmt); +- data->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ data->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(args.np, "fsl,asrc-width", &width); +diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c +--- a/sound/soc/intel/avs/probes.c ++++ b/sound/soc/intel/avs/probes.c +@@ -144,7 +144,7 @@ static int avs_probe_compr_set_params(struct snd_compr_stream *cstream, + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; +- bps = snd_pcm_format_physical_width((__force snd_pcm_format_t)params->codec.format); ++ bps = snd_pcm_format_physical_width(params->codec.format); + if (bps < 0) + return bps; + format_val = snd_hdac_stream_format(params->codec.ch_out, bps, params->codec.sample_rate); +diff --git a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +@@ -172,7 +172,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +@@ -184,7 +184,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +@@ -99,7 +99,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +@@ -112,7 +112,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + return 0; +diff --git a/sound/soc/mediatek/mt8186/mt8186-mt6366.c b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +--- a/sound/soc/mediatek/mt8186/mt8186-mt6366.c ++++ b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +@@ -387,7 +387,7 @@ static int mt8186_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, fmt); + +diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +--- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c ++++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +@@ -623,7 +623,7 @@ static int mt8188_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8189/mt8189-nau8825.c b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +--- a/sound/soc/mediatek/mt8189/mt8189-nau8825.c ++++ b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +@@ -174,7 +174,7 @@ static int mt8189_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +--- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c ++++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +@@ -382,7 +382,7 @@ static int mt8192_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359.c b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +--- a/sound/soc/mediatek/mt8195/mt8195-mt6359.c ++++ b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +@@ -387,7 +387,7 @@ static int mt8195_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +@@ -650,7 +650,7 @@ static int mt8195_etdm_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +--- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c ++++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +@@ -180,7 +180,7 @@ static int mt8196_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-glue.c +--- a/sound/soc/meson/meson-codec-glue.c ++++ b/sound/soc/meson/meson-codec-glue.c +@@ -74,7 +74,7 @@ int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream, + data->params.rates = snd_pcm_rate_to_rate_bit(params_rate(params)); + data->params.rate_min = params_rate(params); + data->params.rate_max = params_rate(params); +- data->params.formats = 1ULL << (__force int) params_format(params); ++ data->params.formats = 1ULL << params_format(params); + data->params.channels_min = params_channels(params); + data->params.channels_max = params_channels(params); + data->params.sig_bits = dai->driver->playback.sig_bits; +diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c +--- a/sound/sparc/dbri.c ++++ b/sound/sparc/dbri.c +@@ -1840,6 +1840,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + int rval = D_INTR_GETRVAL(x); + #endif + ++#ifdef DBRI_DEBUG + if (channel == D_INTR_CMD) { + dprintk(D_CMD, "INTR: Command: %-5s Value:%d\n", + cmds[command], val); +@@ -1847,6 +1848,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + dprintk(D_INT, "INTR: Chan:%d Code:%d Val:%#x\n", + channel, code, rval); + } ++#endif + + switch (code) { + case D_INTR_CMDI: +diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c +--- a/sound/usb/6fire/firmware.c ++++ b/sound/usb/6fire/firmware.c +@@ -194,23 +194,20 @@ static int usb6fire_fw_ezusb_upload( + int ret; + u8 data; + struct usb_device *device = interface_to_usbdev(intf); +- const struct firmware *fw = NULL; +- struct ihex_record *rec = kmalloc_obj(struct ihex_record); ++ struct ihex_record *rec __free(kfree) = kmalloc_obj(struct ihex_record); + + if (!rec) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { +- kfree(rec); + dev_err(&intf->dev, + "error requesting ezusb firmware %s.\n", fwname); + return ret; + } + ret = usb6fire_fw_ihex_init(fw, rec); + if (ret < 0) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "error validating ezusb firmware %s.\n", fwname); + return ret; +@@ -219,8 +216,6 @@ static int usb6fire_fw_ezusb_upload( + data = 0x01; /* stop ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: begin message.\n", + fwname); +@@ -231,8 +226,6 @@ static int usb6fire_fw_ezusb_upload( + ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address, + rec->data, rec->len); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: data urb.\n", + fwname); +@@ -240,8 +233,6 @@ static int usb6fire_fw_ezusb_upload( + } + } + +- release_firmware(fw); +- kfree(rec); + if (postdata) { /* write data after firmware has been uploaded */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr, + postdata, postlen); +@@ -270,19 +261,18 @@ static int usb6fire_fw_fpga_upload( + int ret; + int i; + struct usb_device *device = interface_to_usbdev(intf); +- u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); ++ u8 *buffer __free(kfree) = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); + const char *c; + const char *end; +- const struct firmware *fw; + + if (!buffer) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + dev_err(&intf->dev, "unable to get fpga firmware %s.\n", + fwname); +- kfree(buffer); + return -EIO; + } + +@@ -291,8 +281,6 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0); + if (ret) { +- kfree(buffer); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload fpga firmware: begin urb.\n"); + return ret; +@@ -304,15 +292,11 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_fpga_write(device, buffer, i); + if (ret < 0) { +- release_firmware(fw); +- kfree(buffer); + dev_err(&intf->dev, + "unable to upload fpga firmware: fw urb.\n"); + return ret; + } + } +- release_firmware(fw); +- kfree(buffer); + + ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0); + if (ret) { +diff --git a/sound/usb/card.c b/sound/usb/card.c +--- a/sound/usb/card.c ++++ b/sound/usb/card.c +@@ -715,7 +715,7 @@ static void snd_usb_init_quirk_flags(int idx, struct snd_usb_audio *chip) + + /* old style option found: the position-based integer value */ + if (quirk_flags[idx] && +- !kstrtou32(quirk_flags[idx], 0, &chip->quirk_flags)) { ++ !kstrtou64(quirk_flags[idx], 0, &chip->quirk_flags)) { + snd_usb_apply_flag_dbg("module param", chip, chip->quirk_flags); + return; + } +@@ -905,6 +905,40 @@ static int try_to_register_card(struct snd_usb_audio *chip, int ifnum) + return 0; + } + ++static void usb_audio_disconnect_components(struct snd_usb_audio *chip) ++{ ++ struct snd_usb_stream *as; ++ struct snd_usb_endpoint *ep; ++ struct usb_mixer_interface *mixer; ++ struct list_head *p; ++ ++ /* release the pcm resources */ ++ list_for_each_entry(as, &chip->pcm_list, list) { ++ snd_usb_stream_disconnect(as); ++ } ++ /* release the endpoint resources */ ++ list_for_each_entry(ep, &chip->ep_list, list) { ++ snd_usb_endpoint_release(ep); ++ } ++ /* release the midi resources */ ++ list_for_each(p, &chip->midi_list) { ++ snd_usbmidi_disconnect(p); ++ } ++ snd_usb_midi_v2_disconnect_all(chip); ++ /* ++ * Nice to check quirk && quirk->shares_media_device and ++ * then call the snd_media_device_delete(). Don't have ++ * access to the quirk here. snd_media_device_delete() ++ * accesses mixer_list ++ */ ++ snd_media_device_delete(chip); ++ ++ /* release mixer resources */ ++ list_for_each_entry(mixer, &chip->mixer_list, list) { ++ snd_usb_mixer_disconnect(mixer); ++ } ++} ++ + /* + * probe the active usb device + * +@@ -1077,8 +1111,10 @@ static int usb_audio_probe(struct usb_interface *intf, + * decrement before memory is possibly returned. + */ + atomic_dec(&chip->active); +- if (!chip->num_interfaces) ++ if (!chip->num_interfaces) { ++ usb_audio_disconnect_components(chip); + snd_card_free(chip->card); ++ } + } + return err; + } +@@ -1091,48 +1127,18 @@ static bool __usb_audio_disconnect(struct usb_interface *intf, + struct snd_usb_audio *chip, + struct snd_card *card) + { +- struct list_head *p; +- + guard(mutex)(®ister_mutex); + + if (platform_ops && platform_ops->disconnect_cb) + platform_ops->disconnect_cb(chip); + + if (atomic_inc_return(&chip->shutdown) == 1) { +- struct snd_usb_stream *as; +- struct snd_usb_endpoint *ep; +- struct usb_mixer_interface *mixer; +- + /* wait until all pending tasks done; + * they are protected by snd_usb_lock_shutdown() + */ + snd_refcount_sync(&chip->usage_count); + snd_card_disconnect(card); +- /* release the pcm resources */ +- list_for_each_entry(as, &chip->pcm_list, list) { +- snd_usb_stream_disconnect(as); +- } +- /* release the endpoint resources */ +- list_for_each_entry(ep, &chip->ep_list, list) { +- snd_usb_endpoint_release(ep); +- } +- /* release the midi resources */ +- list_for_each(p, &chip->midi_list) { +- snd_usbmidi_disconnect(p); +- } +- snd_usb_midi_v2_disconnect_all(chip); +- /* +- * Nice to check quirk && quirk->shares_media_device and +- * then call the snd_media_device_delete(). Don't have +- * access to the quirk here. snd_media_device_delete() +- * accesses mixer_list +- */ +- snd_media_device_delete(chip); +- +- /* release mixer resources */ +- list_for_each_entry(mixer, &chip->mixer_list, list) { +- snd_usb_mixer_disconnect(mixer); +- } ++ usb_audio_disconnect_components(chip); + } + + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) +diff --git a/sound/usb/clock.c b/sound/usb/clock.c +--- a/sound/usb/clock.c ++++ b/sound/usb/clock.c +@@ -600,7 +600,7 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, + static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate) + { +- int cur_rate, prev_rate; ++ int cur_rate, prev_rate = 0; + int clock; + + /* First, try to find a valid clock. This may trigger +@@ -625,9 +625,12 @@ static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + return clock; + } + +- prev_rate = get_sample_rate_v2v3(chip, fmt->iface, fmt->altsetting, clock); +- if (prev_rate == rate) +- goto validation; ++ if (!(chip->quirk_flags & QUIRK_FLAG_ALWAYS_SET_RATE)) { ++ prev_rate = get_sample_rate_v2v3(chip, fmt->iface, ++ fmt->altsetting, clock); ++ if (prev_rate == rate) ++ goto validation; ++ } + + cur_rate = snd_usb_set_sample_rate_v2v3(chip, fmt, clock, rate); + if (cur_rate < 0) { +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -1237,7 +1237,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + /* try to use enough URBs to contain an entire ALSA buffer */ + max_urbs = min((unsigned) MAX_URBS, + MAX_QUEUE * packs_per_ms / urb_packs); +- ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ ep->nurbs = MAX_URBS; ++ else ++ ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); + } + + if (fmt->fmt_type == UAC_FORMAT_TYPE_II) +@@ -1262,6 +1265,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + goto out_of_memory; + u->urb->pipe = ep->pipe; + u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ u->urb->transfer_flags |= URB_ISO_ASAP; + u->urb->interval = 1 << ep->datainterval; + u->urb->context = u; + u->urb->complete = snd_complete_urb; +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,6 +505,19 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + ++/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels ++ * and sits on the monitor mixer branch, but it is traced through to the ++ * Speaker output terminal and gets named "Speaker Playback Volume". Userspace ++ * then adopts it as the stream's hardware volume, and any setting below 0 dB ++ * attenuates some channels but not others (20 dB imbalance at 80%). Give it a ++ * non-standard name so that it is no longer taken for the stream's master ++ * volume, while remaining reachable for anyone who wants the monitor gain. ++ */ ++static const struct usbmix_name_map audient_id14_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -588,6 +601,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2573, 0x0008), + .map = maya44_map, + }, ++ { ++ /* Audient iD14 */ ++ .id = USB_ID(0x2708, 0x0008), ++ .map = audient_id14_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3920,6 +3920,7 @@ static int snd_rme_digiface_controls_create(struct usb_mixer_interface *mixer) + #define SND_DJM_450_IDX 0x5 + #define SND_DJM_A9_IDX 0x6 + #define SND_DJM_V10_IDX 0x7 ++#define SND_DJM_S11_IDX 0x8 + + #define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \ + .name = _name, \ +@@ -4260,6 +4261,21 @@ static const struct snd_djm_ctl snd_djm_ctls_v10[] = { + // playback channels are fixed and controlled by hardware knobs on the mixer + }; + ++// DJM-S11 ++static const u16 snd_djm_opts_s11_cap1[] = { ++ 0x0100, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d }; ++static const u16 snd_djm_opts_s11_cap2[] = { ++ 0x0200, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d }; ++static const u16 snd_djm_opts_s11_cap3[] = { ++ 0x0307, 0x0308, 0x0309, 0x030a, 0x030d, 0x0311, 0x0312 }; ++ ++static const struct snd_djm_ctl snd_djm_ctls_s11[] = { ++ SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), ++ SND_DJM_CTL("Input 1 Capture Switch", s11_cap1, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 2 Capture Switch", s11_cap2, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 3 Capture Switch", s11_cap3, 3, SND_DJM_WINDEX_CAP) ++}; ++ + static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_250MK2_IDX] = SND_DJM_DEVICE(250mk2), + [SND_DJM_750_IDX] = SND_DJM_DEVICE(750), +@@ -4269,6 +4285,7 @@ static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_450_IDX] = SND_DJM_DEVICE(450), + [SND_DJM_A9_IDX] = SND_DJM_DEVICE(a9), + [SND_DJM_V10_IDX] = SND_DJM_DEVICE(v10), ++ [SND_DJM_S11_IDX] = SND_DJM_DEVICE(s11), + }; + + static int snd_djm_controls_info(struct snd_kcontrol *kctl, +@@ -4573,6 +4590,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) + case USB_ID(0x2b73, 0x0034): /* Pioneer DJ DJM-V10 */ + err = snd_djm_controls_create(mixer, SND_DJM_V10_IDX); + break; ++ case USB_ID(0x2b73, 0x0037): /* Pioneer DJ DJM-S11 */ ++ err = snd_djm_controls_create(mixer, SND_DJM_S11_IDX); ++ break; + case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ + err = hp_dock_mixer_create(mixer); + break; +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -3320,6 +3320,73 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * Pioneer DJ / AlphaTheta DJM-S11 ++ */ ++ USB_DEVICE(0x2b73, 0x0037), ++ QUIRK_DRIVER_INFO { ++ QUIRK_DATA_COMPOSITE { ++ { ++ QUIRK_DATA_STANDARD_MIXER(0) ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(1) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 14, ++ .iface = 1, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x01, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(2) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 10, ++ .iface = 2, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x82, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC | ++ USB_ENDPOINT_USAGE_IMPLICIT_FB, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ /* Audio Control (unknown purpose) */ ++ .ifnum = 3, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ { ++ .ifnum = 4, ++ .type = QUIRK_MIDI_STANDARD_INTERFACE ++ }, ++ { ++ /* HID */ ++ .ifnum = 5, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* + * MacroSilicon MS2100/MS2106 based AV capture cards +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2145,7 +2145,7 @@ struct usb_string_match { + + struct usb_audio_quirk_flags_table { + u32 id; +- u32 flags; ++ u64 flags; + const struct usb_string_match *usb_string_match; + }; + +@@ -2314,7 +2314,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0661, 0x0883, /* iBasso DC04 Ultra */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x0666, 0x0880, /* SPACETOUCH USB Audio */ +- QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), ++ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY | ++ QUIRK_FLAG_CTL_MSG_DELAY_5M), + DEVICE_FLG(0x06f8, 0xb000, /* Hercules DJ Console (Windows Edition) */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x06f8, 0xd002, /* Hercules DJ Console (Macintosh Edition) */ +@@ -2337,6 +2338,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x0951, 0x16ad, /* Kingston HyperX */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), ++ DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ ++ QUIRK_FLAG_ALWAYS_SET_RATE), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2432,6 +2435,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x1bcf, 0x2283, /* NexiGo N930AF FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), ++ DEVICE_FLG(0x1e0b, 0xd01e, /* Generic USB Audio Device */ ++ QUIRK_FLAG_PLAYBACK_URB_FIXUP), + DEVICE_FLG(0x1ff7, 0x0f81, /* SC13A Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x2040, 0x7200, /* Hauppauge HVR-950Q */ +@@ -2528,6 +2533,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ + QUIRK_FLAG_ALIGN_TRANSFER), ++ DEVICE_FLG(0x84ef, 0x002a, /* Valeton GP-200 */ ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), + +@@ -2641,18 +2648,12 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), + QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), ++ QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL + }; + +-const char *snd_usb_quirk_flag_find_name(unsigned long index) +-{ +- if (index >= ARRAY_SIZE(snd_usb_audio_quirk_flag_names)) +- return NULL; +- +- return snd_usb_audio_quirk_flag_names[index]; +-} +- +-u32 snd_usb_quirk_flags_from_name(const char *name) ++static u64 snd_usb_quirk_flags_from_name(const char *name) + { + int i; + +@@ -2661,7 +2662,7 @@ u32 snd_usb_quirk_flags_from_name(const char *name) + + for (i = 0; snd_usb_audio_quirk_flag_names[i]; i++) { + if (strcasecmp(name, snd_usb_audio_quirk_flag_names[i]) == 0) +- return BIT_U32(i); ++ return BIT_U64(i); + } + + return 0; +@@ -2719,7 +2720,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + { + u16 chip_vid = USB_ID_VENDOR(chip->usb_id); + u16 chip_pid = USB_ID_PRODUCT(chip->usb_id); +- u32 mask_flags, unmask_flags, bit; ++ u64 mask_flags, unmask_flags, bit; + char *p, *field, *flag; + bool is_unmask; + u16 vid, pid; +@@ -2773,7 +2774,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + is_unmask = false; + } + +- if (!kstrtou32(flag, 16, &bit)) { ++ if (!kstrtou64(flag, 16, &bit)) { + if (is_unmask) + unmask_flags |= bit; + else +diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h +--- a/sound/usb/quirks.h ++++ b/sound/usb/quirks.h +@@ -56,7 +56,4 @@ void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip); + void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + const char *str); + +-const char *snd_usb_quirk_flag_find_name(unsigned long flag); +-u32 snd_usb_quirk_flags_from_name(const char *name); +- + #endif /* __USBAUDIO_QUIRKS_H */ +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -44,7 +44,7 @@ struct snd_usb_audio { + atomic_t active; + atomic_t shutdown; + struct snd_refcount usage_count; +- unsigned int quirk_flags; ++ u64 quirk_flags; + unsigned int need_delayed_register:1; /* warn for delayed registration */ + int num_interfaces; + int last_iface; +@@ -254,6 +254,16 @@ extern bool snd_usb_skip_validation; + * check being non-fatal and only disabling GET_CUR instead of the whole mixer. + * The current volume will then be provided by the internal cache that stores + * the last set volume ++ * QUIRK_FLAG_PLAYBACK_URB_FIXUP ++ * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. ++ * This is needed for devices that exhibit boot-time audio stuttering due ++ * to insufficient buffer depth combined with xHCI scheduling variability. ++ * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling ++ * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. ++ * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * Issue SET_CUR for the sample rate even when the clock already reports the ++ * requested rate. A device advertising a single rate is otherwise never sent ++ * the request at all, and some require it before streaming will start. + */ + + enum { +@@ -288,10 +298,12 @@ enum { + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, + QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, ++ QUIRK_TYPE_ALWAYS_SET_RATE = 32, + /* Please also edit snd_usb_audio_quirk_flag_names */ + }; + +-#define QUIRK_FLAG(x) BIT_U32(QUIRK_TYPE_ ## x) ++#define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) + + #define QUIRK_FLAG_GET_SAMPLE_RATE QUIRK_FLAG(GET_SAMPLE_RATE) + #define QUIRK_FLAG_SHARE_MEDIA_DEVICE QUIRK_FLAG(SHARE_MEDIA_DEVICE) +@@ -324,5 +336,7 @@ enum { + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) + #define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) ++#define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + + #endif /* __USBAUDIO_H */ +diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pcm.h +--- a/sound/usb/usx2y/us144mkii_pcm.h ++++ b/sound/usb/usx2y/us144mkii_pcm.h +@@ -7,7 +7,7 @@ + #include "us144mkii.h" + + /** +- * tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. ++ * var tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. + * + * Defines the supported PCM formats, rates, channels, and buffer/period sizes + * for the TASCAM US-144MKII audio interface. +@@ -15,14 +15,14 @@ + extern const struct snd_pcm_hardware tascam_pcm_hw; + + /** +- * tascam_playback_ops - ALSA PCM operations for playback. ++ * var tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream operations. + */ + extern const struct snd_pcm_ops tascam_playback_ops; + + /** +- * tascam_capture_ops - ALSA PCM operations for capture. ++ * var tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operations. + */ +diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c +--- a/sound/usb/usx2y/usbusx2yaudio.c ++++ b/sound/usb/usx2y/usbusx2yaudio.c +@@ -167,7 +167,6 @@ static int usx2y_urb_submit(struct snd_usx2y_substream *subs, struct urb *urb, i + if (!urb) + return -ENODEV; + urb->start_frame = frame + NRURBS * nr_of_packs(); // let hcd do rollover sanity checks +- urb->hcpriv = NULL; + urb->dev = subs->usx2y->dev; /* we need to set this at each time */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { diff --git a/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..3f242445c1579b0c099731b4e2eca8f7d012c567 GIT binary patch literal 594 zcmV-Y0T0G7ApH{yo**>W7I8F6`` z3@O%T{*$U2sUD+czQH-Gh^y(7$^6!0b#3p;8T!hqS6}ns;=R5hOl2KwZ&5c5 zo10oHQrXfak|oa=m7{V)R8lOx@@YegTV!vJxe%;s#N{|gtkOv;>9oB4qkTdm~jTSVX=oF1#FEbs;rmhD3Qr;CgJy~)R zC~6#(5Qe{#8@GoCB8p!DG%}9BU6|3C(Ocwv2JvQmS&aWtjNRGZzphME4Z2kagN5> geMl&jar`-1UK<2q_plT?+)3-d1Uc8E@9VVLdSN#jZU6uP literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch b/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch new file mode 100644 index 0000000..75c49b4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch @@ -0,0 +1,37891 @@ +diff --git a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +--- a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c ++++ b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +@@ -147,7 +147,7 @@ static const struct reg_default halo_register_defaults[] = { + { 0x2b805c0, 0 }, /* HALO_SCRATCH1 */ + { 0x2b805c8, 0 }, /* HALO_SCRATCH2 */ + { 0x2b805d0, 0 }, /* HALO_SCRATCH3 */ +- { 0x2b805c8, 0 }, /* HALO_SCRATCH4 */ ++ { 0x2b805d8, 0 }, /* HALO_SCRATCH4 */ + { 0x2bc1000, 0 }, /* HALO_CCM_CORE_CONTROL */ + { 0x2bc7000, 0 }, /* HALO_WDT_CONTROL */ + +diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c +--- a/drivers/soundwire/bus_type.c ++++ b/drivers/soundwire/bus_type.c +@@ -105,6 +105,9 @@ static int sdw_bus_probe(struct device *dev) + } + slave->index = ret; + ++ /* Create IRQ mapping now so the driver can get it in probe() */ ++ sdw_irq_create_mapping(slave); ++ + ret = drv->probe(slave, id); + if (ret) { + ida_free(&slave->bus->slave_ida, slave->index); +@@ -117,9 +120,6 @@ static int sdw_bus_probe(struct device *dev) + if (drv->ops && drv->ops->read_prop) + drv->ops->read_prop(slave); + +- if (slave->prop.use_domain_irq) +- sdw_irq_create_mapping(slave); +- + /* init the dynamic sysfs attributes we need */ + ret = sdw_slave_sysfs_dpn_init(slave); + if (ret < 0) +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -152,6 +152,10 @@ + #define LPI_MI2S_TX_4 146 + #define SENARY_MI2S_RX 147 + #define SENARY_MI2S_TX 148 ++#define LPI_MI2S_RX_5 149 ++#define LPI_MI2S_TX_5 150 ++#define LPI_MI2S_RX_6 151 ++#define LPI_MI2S_TX_6 152 + + #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 + #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 +@@ -233,11 +237,39 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + ++#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++ + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 + #define LPASS_HW_DCODEC_VOTE 103 +- +-#define Q6AFE_MAX_CLK_ID 104 ++#define LPASS_HW_LPR_VOTE 104 + + #define LPASS_CLK_ATTRIBUTE_INVALID 0x0 + #define LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 +diff --git a/include/linux/platform_data/omap-twl4030.h b/include/linux/platform_data/omap-twl4030.h +deleted file mode 100644 +--- a/include/linux/platform_data/omap-twl4030.h ++++ /dev/null +@@ -1,42 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/** +- * omap-twl4030.h - ASoC machine driver for TI SoC based boards with twl4030 +- * codec, header. +- * +- * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com +- * All rights reserved. +- * +- * Author: Peter Ujfalusi +- */ +- +-#ifndef _OMAP_TWL4030_H_ +-#define _OMAP_TWL4030_H_ +- +-/* To select if only one channel is connected in a stereo port */ +-#define OMAP_TWL4030_LEFT (1 << 0) +-#define OMAP_TWL4030_RIGHT (1 << 1) +- +-struct omap_tw4030_pdata { +- const char *card_name; +- /* Voice port is connected to McBSP3 */ +- bool voice_connected; +- +- /* The driver will parse the connection flags if this flag is set */ +- bool custom_routing; +- /* Flags to indicate connected audio ports. */ +- u8 has_hs; +- u8 has_hf; +- u8 has_predriv; +- u8 has_carkit; +- bool has_ear; +- +- bool has_mainmic; +- bool has_submic; +- bool has_hsmic; +- bool has_carkitmic; +- bool has_digimic0; +- bool has_digimic1; +- u8 has_linein; +-}; +- +-#endif /* _OMAP_TWL4030_H_ */ +diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h +--- a/include/sound/cs35l56.h ++++ b/include/sound/cs35l56.h +@@ -417,7 +417,6 @@ void cs35l56_wait_control_port_ready(void); + void cs35l56_wait_min_reset_pulse(void); + void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); +diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h +--- a/include/sound/dmaengine_pcm.h ++++ b/include/sound/dmaengine_pcm.h +@@ -175,12 +175,6 @@ int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream, + struct dmaengine_pcm { + struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1]; + const struct snd_dmaengine_pcm_config *config; +- struct snd_soc_component component; + unsigned int flags; + }; +- +-static inline struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p) +-{ +- return container_of(p, struct dmaengine_pcm, component); +-} + #endif +diff --git a/include/sound/hda-mlink.h b/include/sound/hda-mlink.h +--- a/include/sound/hda-mlink.h ++++ b/include/sound/hda-mlink.h +@@ -9,6 +9,22 @@ + struct hdac_bus; + struct hdac_ext_link; + ++/** ++ * enum hda_bus_ml_link_type - mlink link type, used by SOF link DMA ++ * allocator constraints (see struct sof_intel_hda_dev). ++ * ++ * @HDA_BUS_ML_LINK_HDA: non-alt link, i.e. HDA codec or iDisp ++ * @HDA_BUS_ML_LINK_SDW: alt link, SoundWire ++ * @HDA_BUS_ML_LINK_UAOL: alt link, USB Audio Offload ++ * @HDA_BUS_ML_LINK_OTHER: alt link, SSP or DMIC ++ */ ++enum hda_bus_ml_link_type { ++ HDA_BUS_ML_LINK_HDA, ++ HDA_BUS_ML_LINK_SDW, ++ HDA_BUS_ML_LINK_UAOL, ++ HDA_BUS_ML_LINK_OTHER, ++}; ++ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK) + + int hda_bus_ml_init(struct hdac_bus *bus); +@@ -49,11 +65,12 @@ int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num); + int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + int channel_mask, int stream_id, int dir); + +-void hda_bus_ml_put_all(struct hdac_bus *bus); + void hda_bus_ml_reset_losidv(struct hdac_bus *bus); + int hda_bus_ml_resume(struct hdac_bus *bus); + int hda_bus_ml_suspend(struct hdac_bus *bus); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink); ++ + struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus); +@@ -169,11 +186,13 @@ hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } + +-static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { } + static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { } + static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; } + static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; } + ++static inline enum hda_bus_ml_link_type ++hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) { return HDA_BUS_ML_LINK_HDA; } ++ + static inline struct hdac_ext_link * + hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; } + +diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h +--- a/include/sound/sdca_asoc.h ++++ b/include/sound/sdca_asoc.h +@@ -25,6 +25,8 @@ struct snd_soc_dai_driver; + struct snd_soc_dai_ops; + struct snd_soc_dapm_route; + struct snd_soc_dapm_widget; ++struct snd_soc_pcm_stream; ++struct sdca_entity; + + /* convenient macro to handle the mono volume in 7.8 fixed format representation */ + #define SDCA_SINGLE_Q78_TLV(xname, xreg, xmin, xmax, xstep, tlv_array) \ +@@ -82,6 +84,11 @@ int sdca_asoc_populate_component(struct device *dev, + struct snd_soc_dai_driver **dai_drv, int *num_dai_drv, + const struct snd_soc_dai_ops *ops); + ++int sdca_asoc_populate_rate_format(struct device *dev, ++ struct sdca_function_data *function, ++ struct sdca_entity *entity, ++ struct snd_soc_pcm_stream *stream); ++ + int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap, + struct sdca_function_data *function, + struct snd_pcm_substream *substream, +@@ -100,9 +107,9 @@ int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays); ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays); + #endif // __SDCA_ASOC_H__ +diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h +--- a/include/sound/sdca_function.h ++++ b/include/sound/sdca_function.h +@@ -298,7 +298,7 @@ enum sdca_xu_controls { + }; + + /** +- * enum sdca_set_index_range - Column definitions UMP SetIndex ++ * enum sdca_fdl_set_index_range - Column definitions UMP SetIndex + */ + enum sdca_fdl_set_index_range { + SDCA_FDL_SET_INDEX_SET_NUMBER = 0, +@@ -803,6 +803,8 @@ struct sdca_control_range { + * @mode: Access mode of the Control. + * @layers: Bitmask of access layers of the Control. + * @deferrable: Indicates if the access to the Control can be deferred. ++ * @is_volatile: Indicates the Control registers are forced to be treated ++ * as volatile. + * @has_default: Indicates the Control has a default value to be written. + * @has_reset: Indicates the Control has a defined reset value. + * @has_fixed: Indicates the Control only supports a single value. +@@ -1116,7 +1118,6 @@ struct sdca_entity_ge { + + /** + * struct sdca_entity_hide - information specific to HIDE Entities +- * @hid: HID device structure + * @num_hidtx_ids: number of HIDTx Report ID + * @num_hidrx_ids: number of HIDRx Report ID + * @hidtx_ids: HIDTx Report ID +@@ -1127,11 +1128,8 @@ struct sdca_entity_ge { + * within this Device + * @max_delay: the maximum time in microseconds allowed for the Device + * to change the ownership from Device to Host +- * @hid_report_desc: HID Report Descriptor for the HIDE Entity +- * @hid_desc: HID descriptor for the HIDE Entity + */ + struct sdca_entity_hide { +- struct hid_device *hid; + unsigned int *hidtx_ids; + unsigned int *hidrx_ids; + int num_hidtx_ids; +@@ -1139,12 +1137,10 @@ struct sdca_entity_hide { + unsigned int af_number_list[SDCA_MAX_FUNCTION_COUNT]; + unsigned int hide_reside_function_num; + unsigned int max_delay; +- unsigned char *hid_report_desc; +- struct hid_descriptor hid_desc; + }; + + /** +- * enum sdca_xu_reset_machanism - SDCA FDL Resets ++ * enum sdca_xu_reset_mechanism - SDCA FDL Resets + */ + enum sdca_xu_reset_mechanism { + SDCA_XU_RESET_FUNCTION = 0x0, +@@ -1401,6 +1397,16 @@ struct sdca_fdl_data { + int num_sets; + }; + ++/** ++ * struct sdca_function_hid - information about a function's HID descriptors ++ * @report_desc: HID Report Descriptor for the HID Function ++ * @desc: HID descriptor for the HID Function ++ */ ++struct sdca_function_hid { ++ unsigned char *report_desc; ++ struct hid_descriptor desc; ++}; ++ + /** + * struct sdca_function_data - top-level information for one SDCA function + * @desc: Pointer to short descriptor from initial parsing. +@@ -1415,6 +1421,7 @@ struct sdca_fdl_data { + * @reset_max_delay: Maximum Function reset delay in microseconds, before an + * error should be reported. + * @fdl_data: FDL data for this Function, if available. ++ * @hid: HID data for this Function, if available. + */ + struct sdca_function_data { + struct sdca_function_desc *desc; +@@ -1430,6 +1437,10 @@ struct sdca_function_data { + unsigned int reset_max_delay; + + struct sdca_fdl_data fdl_data; ++ ++ union { ++ struct sdca_function_hid hid; ++ }; + }; + + static inline u32 sdca_range(struct sdca_control_range *range, +@@ -1451,8 +1462,7 @@ static inline u32 sdca_range_search(struct sdca_control_range *range, + return 0; + } + +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function); ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function); + + const char *sdca_find_terminal_name(enum sdca_terminal_type type); + +@@ -1469,5 +1479,7 @@ struct sdca_control_range *sdca_selector_find_range(struct device *dev, + struct sdca_cluster *sdca_id_find_cluster(struct device *dev, + struct sdca_function_data *function, + const int id); ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, ++ const char *entity_label); + + #endif +diff --git a/include/sound/sdca_hid.h b/include/sound/sdca_hid.h +--- a/include/sound/sdca_hid.h ++++ b/include/sound/sdca_hid.h +@@ -16,18 +16,21 @@ struct sdca_interrupt; + + #if IS_ENABLED(CONFIG_SND_SOC_SDCA_HID) + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity); ++int sdca_add_hid_device(struct sdca_interrupt *interrupt); ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt); + int sdca_hid_process_report(struct sdca_interrupt *interrupt); + + #else + +-static inline int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++static inline int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { + return 0; + } + ++static inline void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + return 0; +diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h +--- a/include/sound/simple_card_utils.h ++++ b/include/sound/simple_card_utils.h +@@ -181,7 +181,7 @@ void simple_util_canonicalize_platform(struct snd_soc_dai_link_component *platfo + void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + int is_single_links); + +-void simple_util_clean_reference(struct snd_soc_card *card); ++void simple_util_clean_reference(struct simple_util_priv *priv); + + void simple_util_parse_convert(struct device_node *np, char *prefix, + struct simple_util_data *data); +@@ -189,18 +189,37 @@ bool simple_util_is_convert_required(const struct simple_util_data *data); + + int simple_util_get_sample_fmt(struct simple_util_data *data); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix); ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property); ++static inline int simple_util_parse_routing(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_routing, ++ prefix, "routing"); ++} ++ ++static inline int simple_util_parse_widgets(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_simple_widgets, ++ prefix, "widgets"); ++} ++ ++static inline int simple_util_parse_pin_switches(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_pin_switches, ++ prefix, "pin-switches"); ++} ++ ++static inline int simple_util_parse_aux_devs(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_aux_devs, ++ prefix, "aux-devs"); ++} + + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, char *pin); +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, +- char *prefix); ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix); + int simple_util_init_priv(struct simple_util_priv *priv, + struct link_info *li); + void simple_util_remove(struct platform_device *pdev); +diff --git a/include/sound/soc-acpi.h b/include/sound/soc-acpi.h +--- a/include/sound/soc-acpi.h ++++ b/include/sound/soc-acpi.h +@@ -57,7 +57,7 @@ static inline struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg) + #endif + + /** +- * snd_soc_acpi_mach_params: interface for machine driver configuration ++ * struct snd_soc_acpi_mach_params - interface for machine driver configuration + * + * @acpi_ipc_irq_index: used for BYT-CR detection + * @platform: string used for HDAudio codec support +@@ -93,7 +93,7 @@ struct snd_soc_acpi_mach_params { + }; + + /** +- * snd_soc_acpi_endpoint - endpoint descriptor ++ * struct snd_soc_acpi_endpoint - endpoint descriptor + * @num: endpoint number (mandatory, unique per device) + * @aggregated: 0 (independent) or 1 (logically grouped) + * @group_position: zero-based order (only when @aggregated is 1) +@@ -107,7 +107,7 @@ struct snd_soc_acpi_endpoint { + }; + + /** +- * snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device ++ * struct snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device + * @adr: 64 bit ACPI _ADR value + * @num_endpoints: number of endpoints for this device + * @endpoints: array of endpoints +@@ -121,7 +121,7 @@ struct snd_soc_acpi_adr_device { + }; + + /** +- * snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices ++ * struct snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices + * @mask: one bit set indicates the link this list applies to + * @num_adr: ARRAY_SIZE of devices + * @adr_d: array of devices +@@ -167,8 +167,8 @@ struct snd_soc_acpi_link_adr { + #define SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME BIT(4) + + /** +- * snd_soc_acpi_mach: ACPI-based machine descriptor. Most of the fields are +- * related to the hardware, except for the firmware and topology file names. ++ * struct snd_soc_acpi_mach - ACPI-based machine descriptor. Most of the fields ++ * are related to the hardware, except for the firmware and topology file names. + * A platform supported by legacy and Sound Open Firmware (SOF) would expose + * all firmware/topology related fields. + * +@@ -192,6 +192,7 @@ struct snd_soc_acpi_link_adr { + * the initial selection in the snd_soc_acpi_mach table. + * @pdata: intended for platform data or machine specific-ops. This structure + * is not constant since this field may be updated at run-time ++ * @mach_params: machine driver configuration + * @sof_tplg_filename: Sound Open Firmware topology file name, if enabled + * @tplg_quirk_mask: quirks to select different topology files dynamically + * @get_function_tplg_files: This is an optional callback, if specified then instead of +@@ -199,11 +200,11 @@ struct snd_soc_acpi_link_adr { + * files to be loaded. + * Return value: The number of the files or negative ERRNO. 0 means that the single topology + * file should be used, no function topology split can be used on the machine. +- * @card: the pointer of the card +- * @mach: the pointer of the machine driver +- * @prefix: the prefix of the topology file name. Typically, it is the path. +- * @tplg_files: the pointer of the array of the topology file names. +- * @best_effort: ignore non supported links and try to build the card in best effort ++ * card: the pointer of the card ++ * mach: the pointer of the machine driver ++ * prefix: the prefix of the topology file name. Typically, it is the path. ++ * tplg_files: the pointer of the array of the topology file names. ++ * best_effort: ignore non supported links and try to build the card in best effort + * with supported links + */ + /* Descriptor for SST ASoC machine driver */ +diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h +--- a/include/sound/soc-component.h ++++ b/include/sound/soc-component.h +@@ -253,6 +253,9 @@ struct snd_soc_component { + void *mark_pm; + + struct dentry *debugfs_root; ++ ++ /* Component private data */ ++ void *priv; + }; + + #define for_each_component_dais(component, dai)\ +@@ -283,6 +286,14 @@ static inline int snd_soc_component_cache_sync( + return regcache_sync(component->regmap); + } + ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); ++const char *snd_soc_component_name(struct snd_soc_component *component); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv); ++void *snd_soc_component_to_priv(struct snd_soc_component *component); ++ + void snd_soc_component_set_aux(struct snd_soc_component *component, + struct snd_soc_aux_dev *aux); + int snd_soc_component_init(struct snd_soc_component *component); +diff --git a/include/sound/soc.h b/include/sound/soc.h +--- a/include/sound/soc.h ++++ b/include/sound/soc.h +@@ -447,15 +447,16 @@ static inline int snd_soc_resume(struct device *dev) + } + #endif + int snd_soc_poweroff(struct device *dev); +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev); +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai); +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, int num_dai); ++#define snd_soc_register_component(x, ...) _Generic((x), \ ++struct device * : snd_soc_register_component_d, \ ++struct snd_soc_component * : snd_soc_register_component_c)(x, __VA_ARGS__) ++ + int devm_snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); +diff --git a/include/sound/sof/dai-amd.h b/include/sound/sof/dai-amd.h +--- a/include/sound/sof/dai-amd.h ++++ b/include/sound/sof/dai-amd.h +@@ -18,6 +18,7 @@ struct sof_ipc_dai_acp_params { + uint32_t fsync_rate; /* FSYNC frequency in Hz */ + uint32_t tdm_slots; + uint32_t tdm_mode; ++ uint32_t format; + } __packed; + + /* ACPDMIC Configuration Request - SOF_IPC_DAI_AMD_CONFIG */ +diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h +--- a/include/sound/sof/dai.h ++++ b/include/sound/sof/dai.h +@@ -90,6 +90,8 @@ enum sof_ipc_dai_type { + SOF_DAI_AMD_HS_VIRTUAL, /**< AMD ACP HS VIRTUAL */ + SOF_DAI_IMX_MICFIL, /** < i.MX MICFIL PDM */ + SOF_DAI_AMD_SDW, /**< AMD ACP SDW */ ++ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ ++ SOF_DAI_AMD_I2S, /**< AMD ACP I2S */ + }; + + /* general purpose DAI configuration */ +@@ -121,6 +123,7 @@ struct sof_ipc_dai_config { + struct sof_ipc_dai_mtk_afe_params afe; + struct sof_ipc_dai_micfil_params micfil; + struct sof_ipc_dai_acp_sdw_params acp_sdw; ++ struct sof_ipc_dai_acp_params acp_i2s; + }; + } __packed; + +diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h +--- a/include/sound/sof/header.h ++++ b/include/sound/sof/header.h +@@ -179,7 +179,7 @@ struct sof_ipc_compound_hdr { + uint32_t count; /**< count of 0 means end of compound sequence */ + } __packed; + +-/** ++/* + * OOPS header architecture specific data. + */ + struct sof_ipc_dsp_oops_arch_hdr { +@@ -187,7 +187,7 @@ struct sof_ipc_dsp_oops_arch_hdr { + uint32_t totalsize; /* Total size of oops message */ + } __packed; + +-/** ++/* + * OOPS header platform specific data. + */ + struct sof_ipc_dsp_oops_plat_hdr { +diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h +--- a/include/sound/sof/ipc4/header.h ++++ b/include/sound/sof/ipc4/header.h +@@ -187,6 +187,10 @@ enum sof_ipc4_pipeline_state { + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID_MASK GENMASK(23, 20) + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_CORE_ID_SHIFT) + ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT 29 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_MASK BIT(29) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT) ++ + /* pipeline set state ipc msg */ + #define SOF_IPC4_GLB_PIPE_STATE_ID_SHIFT 16 + #define SOF_IPC4_GLB_PIPE_STATE_ID_MASK GENMASK(23, 16) +@@ -536,12 +540,63 @@ enum sof_ipc4_notification_type { + SOF_IPC4_NOTIFY_TYPE_LAST, + }; + ++enum sof_ipc4_resource_type { ++ SOF_IPC4_MODULE_INSTANCE, ++ SOF_IPC4_PIPELINE, ++ SOF_IPC4_GATEWAY, ++ SOF_IPC4_EDF_TASK, ++ SOF_IPC4_INVALID_RESOURCE_TYPE, ++}; ++ ++enum sof_ipc4_event_type { ++ /* Underrun detected by the Mixer */ ++ SOF_IPC4_MIXER_UNDERRUN_DETECTED = 1, ++ /* Error caught during data processing */ ++ SOF_IPC4_PROCESS_DATA_ERROR = 3, ++ /* Underrun detected by gateway. */ ++ SOF_IPC4_GATEWAY_UNDERRUN_DETECTED = 6, ++ /* Overrun detected by gateway */ ++ SOF_IPC4_GATEWAY_OVERRUN_DETECTED, ++}; ++ ++/** ++ * struct sof_ipc4_process_data_error_event_data - process data error event payload ++ * @error_code: Error code returned by data processing function ++ */ ++struct sof_ipc4_process_data_error_event_data { ++ uint32_t error_code; ++}; ++ ++/** ++ * struct sof_ipc4_mixer_underrun_event_data - mixer underrun event payload ++ * @eos_flag: Indicates EndOfStream ++ * @data_mixed: Data processed by module (in bytes) ++ * @expected_data_mixed: Expected data to be processed (in bytes) ++ */ ++struct sof_ipc4_mixer_underrun_event_data { ++ uint32_t eos_flag; ++ uint32_t data_mixed; ++ uint32_t expected_data_mixed; ++}; ++ ++/** ++ * union sof_ipc4_resource_event_data - resource event specific payload ++ * @dws: Raw event data payload as six dwords ++ * @process_data_error: SOF_IPC4_PROCESS_DATA_ERROR payload ++ * @mixer_underrun: SOF_IPC4_MIXER_UNDERRUN_DETECTED payload ++ */ ++union sof_ipc4_resource_event_data { ++ uint32_t dws[6]; ++ struct sof_ipc4_process_data_error_event_data process_data_error; ++ struct sof_ipc4_mixer_underrun_event_data mixer_underrun; ++}; ++ + struct sof_ipc4_notify_resource_data { + uint32_t resource_type; + uint32_t resource_id; + uint32_t event_type; + uint32_t reserved; +- uint32_t data[6]; ++ union sof_ipc4_resource_event_data data; + } __packed __aligned(4); + + #define SOF_IPC4_DEBUG_DESCRIPTOR_SIZE 12 /* 3 x u32 */ +@@ -654,11 +709,81 @@ enum sof_ipc4_mod_init_ext_obj_id { + SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, + }; + +-/* DP module memory configuration data object for ext_init object array */ ++/* DP module memory configuration data object for object array */ + struct sof_ipc4_mod_init_ext_dp_memory_data { ++ u32 domain_id; /* userspace domain ID */ ++ u32 stack_bytes; /* required stack size in bytes */ ++ u32 heap_bytes; /* required heap size in bytes */ ++} __packed __aligned(4); ++ ++/* ++ * This set of macros are very similar to the set above, but these are ++ * for building payload to SOF_IPC4_GLB_CREATE_PIPELINE message. ++ * ++ * Macros for creating struct sof_ipc4_glb_pipe_payload payload with ++ * its associated data. struct sof_ipc4_glb_pipe_payload should be the ++ * first piece of payload following SOF_IPC4_GLB_CREATE_PIPELINE msg, ++ * and its existence is indicated with SOF_IPC4_GLB_PIPE_PAYLOAD bit. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_payload.word0 ++ */ ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_MASK GENMASK(23, 0) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT 24 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK BIT(24) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT) ++ ++struct sof_ipc4_glb_pipe_payload { ++ u32 word0; ++ u32 rsvd1; ++ u32 rsvd2; ++} __packed __aligned(4); ++ ++/* ++ * SOF_IPC4_GLB_CREATE_PIPELINE payload may be followed by arbitrary ++ * number of object array objects. SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY-bit ++ * indicates that an array object follows struct ++ * sof_ipc4_glb_pipe_payload. ++ * ++ * The object header's SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST-bit in struct ++ * sof_ipc4_glb_pipe_ext_object indicates if the array is continued ++ * with another object. The header has also fields to identify the ++ * object, SOF_IPC4_GLB_PIPE_EXT_OBJ_ID, and to indicate the object's ++ * size in 32-bit words, SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS, not ++ * including the header itself. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_ext_object.header ++ */ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK BIT(0) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT 1 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_MASK GENMASK(15, 1) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT 16 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_MASK GENMASK(31, 16) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT) ++ ++struct sof_ipc4_glb_pipe_ext_object { ++ u32 header; ++ u32 data[]; ++} __packed __aligned(4); ++ ++enum sof_ipc4_glb_pipe_ext_obj_id { ++ SOF_IPC4_GLB_PIPE_DATA_ID_INVALID = 0, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MAX = SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++}; ++ ++/* Pipeline memory configuration data object for ext_init object array */ ++struct sof_ipc4_glb_pipe_ext_obj_memory_data { + u32 domain_id; /* userspace domain ID */ +- u32 stack_bytes; /* stack size in bytes, 0 means default size */ +- u32 heap_bytes; /* stack size in bytes, 0 means default size */ ++ u32 stack_bytes; /* stack size in bytes */ ++ u32 heap_bytes; /* heap size in bytes */ + } __packed __aligned(4); + + /** @}*/ +diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h +--- a/include/sound/tas2781-dsp.h ++++ b/include/sound/tas2781-dsp.h +@@ -201,6 +201,11 @@ struct tasdevice_rca { + int ncfgs; + struct tasdevice_config_info **cfg_info; + int profile_cfg_id; ++ /* ++ * Used among SmartAMP for PDM microphone recording or IV data ++ * capture. ++ */ ++ int capture_profile_id; + /* + * Since version 0x105, the keyword 'init' was introduced into the + * profile, which is used for chip initialization, particularly to +@@ -222,7 +227,7 @@ void tasdevice_calbin_remove(void *context); + int tasdevice_select_tuningprm_cfg(void *context, int prm, + int cfg_no, int rca_conf_no); + int tasdevice_prmg_load(void *context, int prm_no); +-void tasdevice_tuning_switch(void *context, int state); ++void tasdevice_tuning_switch(void *context, int state, bool is_cap); + int tas2781_load_calibration(void *context, char *file_name, + unsigned short i); + +diff --git a/include/sound/tlv320aic32x4.h b/include/sound/tlv320aic32x4.h +deleted file mode 100644 +--- a/include/sound/tlv320aic32x4.h ++++ /dev/null +@@ -1,43 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- * tlv320aic32x4.h -- TLV320AIC32X4 Soc Audio driver platform data +- * +- * Copyright 2011 Vista Silicon S.L. +- * +- * Author: Javier Martin +- */ +- +-#ifndef _AIC32X4_PDATA_H +-#define _AIC32X4_PDATA_H +- +-#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 +-#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 +-#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 +-#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 +-#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 +- +-#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 +-#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 +- +-/* GPIO API */ +-#define AIC32X4_MFPX_DEFAULT_VALUE 0xff +- +-#define AIC32X4_MFP1_DIN_DISABLED 0 +-#define AIC32X4_MFP1_DIN_ENABLED 0x2 +-#define AIC32X4_MFP1_GPIO_IN 0x4 +- +-#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 +- +-#define AIC32X4_MFP_GPIO_ENABLED 0x4 +- +-#define AIC32X4_MFP5_GPIO_DISABLED 0x0 +-#define AIC32X4_MFP5_GPIO_INPUT 0x8 +-#define AIC32X4_MFP5_GPIO_OUTPUT 0xc +-#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 +- +-struct aic32x4_setup_data { +- unsigned int gpio_func[5]; +-}; +-#endif +diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h +--- a/include/sound/wm8904.h ++++ b/include/sound/wm8904.h +@@ -116,7 +116,7 @@ + #define WM8904_DRC_REGS 4 + #define WM8904_EQ_REGS 24 + +-/** ++/* + * DRC configurations are specified with a label and a set of register + * values to write (the enable bits will be ignored). At runtime an + * enumerated control will be presented for each DRC block allowing +@@ -131,7 +131,7 @@ struct wm8904_drc_cfg { + u16 regs[WM8904_DRC_REGS]; + }; + +-/** ++/* + * ReTune Mobile configurations are specified with a label, sample + * rate and set of values to write (the enable bits will be ignored). + * +diff --git a/include/uapi/sound/skl-tplg-interface.h b/include/uapi/sound/skl-tplg-interface.h +deleted file mode 100644 +--- a/include/uapi/sound/skl-tplg-interface.h ++++ /dev/null +@@ -1,168 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * skl-tplg-interface.h - Intel DSP FW private data interface +- * +- * Copyright (C) 2015 Intel Corp +- * Author: Jeeja KP +- * Nilofer, Samreen +- */ +- +-#ifndef __HDA_TPLG_INTERFACE_H__ +-#define __HDA_TPLG_INTERFACE_H__ +- +-#include +- +-/* +- * Default types range from 0~12. type can range from 0 to 0xff +- * SST types start at higher to avoid any overlapping in future +- */ +-#define SKL_CONTROL_TYPE_BYTE_TLV 0x100 +-#define SKL_CONTROL_TYPE_MIC_SELECT 0x102 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT 0x103 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC 0x104 +- +-#define HDA_SST_CFG_MAX 900 /* size of copier cfg*/ +-#define MAX_IN_QUEUE 8 +-#define MAX_OUT_QUEUE 8 +- +-#define SKL_UUID_STR_SZ 40 +-/* Event types goes here */ +-/* Reserve event type 0 for no event handlers */ +-enum skl_event_types { +- SKL_EVENT_NONE = 0, +- SKL_MIXER_EVENT, +- SKL_MUX_EVENT, +- SKL_VMIXER_EVENT, +- SKL_PGA_EVENT +-}; +- +-/** +- * enum skl_ch_cfg - channel configuration +- * +- * @SKL_CH_CFG_MONO: One channel only +- * @SKL_CH_CFG_STEREO: L & R +- * @SKL_CH_CFG_2_1: L, R & LFE +- * @SKL_CH_CFG_3_0: L, C & R +- * @SKL_CH_CFG_3_1: L, C, R & LFE +- * @SKL_CH_CFG_QUATRO: L, R, Ls & Rs +- * @SKL_CH_CFG_4_0: L, C, R & Cs +- * @SKL_CH_CFG_5_0: L, C, R, Ls & Rs +- * @SKL_CH_CFG_5_1: L, C, R, Ls, Rs & LFE +- * @SKL_CH_CFG_DUAL_MONO: One channel replicated in two +- * @SKL_CH_CFG_I2S_DUAL_STEREO_0: Stereo(L,R) in 4 slots, 1st stream:[ L, R, -, - ] +- * @SKL_CH_CFG_I2S_DUAL_STEREO_1: Stereo(L,R) in 4 slots, 2nd stream:[ -, -, L, R ] +- * @SKL_CH_CFG_INVALID: Invalid +- */ +-enum skl_ch_cfg { +- SKL_CH_CFG_MONO = 0, +- SKL_CH_CFG_STEREO = 1, +- SKL_CH_CFG_2_1 = 2, +- SKL_CH_CFG_3_0 = 3, +- SKL_CH_CFG_3_1 = 4, +- SKL_CH_CFG_QUATRO = 5, +- SKL_CH_CFG_4_0 = 6, +- SKL_CH_CFG_5_0 = 7, +- SKL_CH_CFG_5_1 = 8, +- SKL_CH_CFG_DUAL_MONO = 9, +- SKL_CH_CFG_I2S_DUAL_STEREO_0 = 10, +- SKL_CH_CFG_I2S_DUAL_STEREO_1 = 11, +- SKL_CH_CFG_7_1 = 12, +- SKL_CH_CFG_4_CHANNEL = SKL_CH_CFG_7_1, +- SKL_CH_CFG_INVALID +-}; +- +-enum skl_module_type { +- SKL_MODULE_TYPE_MIXER = 0, +- SKL_MODULE_TYPE_COPIER, +- SKL_MODULE_TYPE_UPDWMIX, +- SKL_MODULE_TYPE_SRCINT, +- SKL_MODULE_TYPE_ALGO, +- SKL_MODULE_TYPE_BASE_OUTFMT, +- SKL_MODULE_TYPE_KPB, +- SKL_MODULE_TYPE_MIC_SELECT, +-}; +- +-enum skl_core_affinity { +- SKL_AFFINITY_CORE_0 = 0, +- SKL_AFFINITY_CORE_1, +- SKL_AFFINITY_CORE_MAX +-}; +- +-enum skl_pipe_conn_type { +- SKL_PIPE_CONN_TYPE_NONE = 0, +- SKL_PIPE_CONN_TYPE_FE, +- SKL_PIPE_CONN_TYPE_BE +-}; +- +-enum skl_hw_conn_type { +- SKL_CONN_NONE = 0, +- SKL_CONN_SOURCE = 1, +- SKL_CONN_SINK = 2 +-}; +- +-enum skl_dev_type { +- SKL_DEVICE_BT = 0x0, +- SKL_DEVICE_DMIC = 0x1, +- SKL_DEVICE_I2S = 0x2, +- SKL_DEVICE_SLIMBUS = 0x3, +- SKL_DEVICE_HDALINK = 0x4, +- SKL_DEVICE_HDAHOST = 0x5, +- SKL_DEVICE_NONE +-}; +- +-/** +- * enum skl_interleaving - interleaving style +- * +- * @SKL_INTERLEAVING_PER_CHANNEL: [s1_ch1...s1_chN,...,sM_ch1...sM_chN] +- * @SKL_INTERLEAVING_PER_SAMPLE: [s1_ch1...sM_ch1,...,s1_chN...sM_chN] +- */ +-enum skl_interleaving { +- SKL_INTERLEAVING_PER_CHANNEL = 0, +- SKL_INTERLEAVING_PER_SAMPLE = 1, +-}; +- +-enum skl_sample_type { +- SKL_SAMPLE_TYPE_INT_MSB = 0, +- SKL_SAMPLE_TYPE_INT_LSB = 1, +- SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- SKL_SAMPLE_TYPE_FLOAT = 4 +-}; +- +-enum module_pin_type { +- /* All pins of the module takes same PCM inputs or outputs +- * e.g. mixout +- */ +- SKL_PIN_TYPE_HOMOGENEOUS, +- /* All pins of the module takes different PCM inputs or outputs +- * e.g mux +- */ +- SKL_PIN_TYPE_HETEROGENEOUS, +-}; +- +-enum skl_module_param_type { +- SKL_PARAM_DEFAULT = 0, +- SKL_PARAM_INIT, +- SKL_PARAM_SET, +- SKL_PARAM_BIND +-}; +- +-struct skl_dfw_algo_data { +- __u32 set_params:2; +- __u32 rsvd:30; +- __u32 param_id; +- __u32 max; +- char params[]; +-} __packed; +- +-enum skl_tkn_dir { +- SKL_DIR_IN, +- SKL_DIR_OUT +-}; +- +-enum skl_tuple_type { +- SKL_TYPE_TUPLE, +- SKL_TYPE_DATA +-}; +- +-#endif +diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h +--- a/include/uapi/sound/snd_ar_tokens.h ++++ b/include/uapi/sound/snd_ar_tokens.h +@@ -58,7 +58,7 @@ enum ar_event_types { + #define SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX 256 + #define SND_SOC_AR_TPLG_VOL_CTL 257 + +-/** ++/* + * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: Sub Graph Instance Id + * + * %AR_TKN_U32_SUB_GRAPH_PERF_MODE: Performance mode of subgraph +@@ -168,6 +168,60 @@ enum ar_event_types { + * LOG_WAIT = 0, + * LOG_IMMEDIATELY = 1 + * ++ * %AR_TKN_U16_MODULE_SYNC_SRC: Frame sync source ++ * AR_AUDIO_IF_SYNC_SRC_EXTERNAL = 0, ++ * AR_AUDIO_IF_SYNC_SRC_INTERNAL = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: Enable data-out tri-state control ++ * AR_AUDIO_IF_CTRL_DATA_OE_DISABLE = 0, ++ * AR_AUDIO_IF_CTRL_DATA_OE_ENABLE = 1 ++ * ++ * %AR_TKN_U32_MODULE_SLOT_MASK: Active TDM slot bitmask ++ * ++ * %AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: Number of slots per TDM frame ++ * ++ * %AR_TKN_U16_MODULE_SLOT_WIDTH: Slot width in bits (16 or 32) ++ * ++ * %AR_TKN_U16_MODULE_SYNC_MODE: Frame sync mode ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT = 0, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT = 1, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_LONG = 2 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: Invert frame sync pulse polarity ++ * AR_AUDIO_IF_SYNC_NORMAL = 0, ++ * AR_AUDIO_IF_SYNC_INVERTED = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: Data delay relative to frame sync ++ * AR_AUDIO_IF_DATA_DELAY_NONE = 0, ++ * AR_AUDIO_IF_DATA_DELAY_1_CYCLE = 1, ++ * AR_AUDIO_IF_DATA_DELAY_2_CYCLE = 2 ++ * ++ * %AR_TKN_U16_MODULE_INTF_MODE: Audio IF interface mode ++ * AR_AUDIO_IF_INTF_MODE_TDM = 0, ++ * AR_AUDIO_IF_INTF_MODE_PCM = 1, ++ * AR_AUDIO_IF_INTF_MODE_I2S = 2 ++ * ++ * %AR_TKN_U16_MODULE_QAIF_TYPE: QAIF hardware port type index ++ * AR_AUDIO_IF_QAIF = 0, ++ * AR_AUDIO_IF_QAIF_VA = 1 ++ * ++ * %AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: Active lane bitmask for multi-lane ++ * ++ * %AR_TKN_U32_MODULE_FRAME_SYNC_RATE: Frame sync rate in Hz ++ * ++ * %AR_TKN_U16_MODULE_BIT_CLK_TYPE: Bit clock type ++ * AR_AUDIO_IF_BIT_CLK_INTERNAL = 0, ++ * AR_AUDIO_IF_BIT_CLK_EXTERNAL = 1, ++ * AR_AUDIO_IF_BIT_CLK_SKIP = 2 ++ * ++ * %AR_TKN_U8_MODULE_INV_INT_BIT_CLK: Invert internal bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * ++ * %AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: Invert external bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * + * %AR_TKN_DAI_INDEX: dai index + * + */ +@@ -240,6 +294,45 @@ enum ar_event_types { + #define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID 260 + #define AR_TKN_U32_MODULE_LOG_MODE 261 + ++#define AR_TKN_U16_MODULE_SYNC_SRC 262 ++#define AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE 263 ++#define AR_TKN_U32_MODULE_SLOT_MASK 264 ++#define AR_TKN_U16_MODULE_NSLOTS_PER_FRAME 265 ++#define AR_TKN_U16_MODULE_SLOT_WIDTH 266 ++#define AR_TKN_U16_MODULE_SYNC_MODE 267 ++#define AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE 268 ++#define AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY 269 ++#define AR_TKN_U16_MODULE_INTF_MODE 270 ++#define AR_TKN_U16_MODULE_QAIF_TYPE 271 ++#define AR_TKN_U32_MODULE_ACTIVE_LANE_MASK 272 ++#define AR_TKN_U32_MODULE_FRAME_SYNC_RATE 273 ++#define AR_TKN_U16_MODULE_BIT_CLK_TYPE 274 ++#define AR_TKN_U8_MODULE_INV_INT_BIT_CLK 275 ++#define AR_TKN_U8_MODULE_INV_EXT_BIT_CLK 276 ++ ++#define AR_AUDIO_IF_SYNC_SRC_EXTERNAL 0 ++#define AR_AUDIO_IF_SYNC_SRC_INTERNAL 1 ++#define AR_AUDIO_IF_CTRL_DATA_OE_DISABLE 0 ++#define AR_AUDIO_IF_CTRL_DATA_OE_ENABLE 1 ++#define AR_AUDIO_IF_INTF_MODE_TDM 0 ++#define AR_AUDIO_IF_INTF_MODE_PCM 1 ++#define AR_AUDIO_IF_INTF_MODE_I2S 2 ++#define AR_AUDIO_IF_QAIF 0 ++#define AR_AUDIO_IF_QAIF_VA 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT 0 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_LONG 2 ++#define AR_AUDIO_IF_SYNC_NORMAL 0 ++#define AR_AUDIO_IF_SYNC_INVERTED 1 ++#define AR_AUDIO_IF_DATA_DELAY_NONE 0 ++#define AR_AUDIO_IF_DATA_DELAY_1_CYCLE 1 ++#define AR_AUDIO_IF_DATA_DELAY_2_CYCLE 2 ++#define AR_AUDIO_IF_BIT_CLK_INTERNAL 0 ++#define AR_AUDIO_IF_BIT_CLK_EXTERNAL 1 ++#define AR_AUDIO_IF_BIT_CLK_SKIP 2 ++#define AR_AUDIO_IF_CLK_NORMAL 0 ++#define AR_AUDIO_IF_CLK_INVERTED 1 ++ + #define SND_SOC_AR_TPLG_MODULE_CFG_TYPE 0x01001006 + struct audioreach_module_priv_data { + __le32 size; /* size in bytes of the array, including all elements */ +diff --git a/include/uapi/sound/snd_sst_tokens.h b/include/uapi/sound/snd_sst_tokens.h +deleted file mode 100644 +--- a/include/uapi/sound/snd_sst_tokens.h ++++ /dev/null +@@ -1,324 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * snd_sst_tokens.h - Intel SST tokens definition +- * +- * Copyright (C) 2016 Intel Corp +- * Author: Shreyas NC +- */ +-#ifndef __SND_SST_TOKENS_H__ +-#define __SND_SST_TOKENS_H__ +- +-/** +- * %SKL_TKN_UUID: Module UUID +- * +- * %SKL_TKN_U8_BLOCK_TYPE: Type of the private data block.Can be: +- * tuples, bytes, short and words +- * +- * %SKL_TKN_U8_IN_PIN_TYPE: Input pin type, +- * homogenous=0, heterogenous=1 +- * +- * %SKL_TKN_U8_OUT_PIN_TYPE: Output pin type, +- * homogenous=0, heterogenous=1 +- * %SKL_TKN_U8_DYN_IN_PIN: Configure Input pin dynamically +- * if true +- * +- * %SKL_TKN_U8_DYN_OUT_PIN: Configure Output pin dynamically +- * if true +- * +- * %SKL_TKN_U8_IN_QUEUE_COUNT: Store the number of Input pins +- * +- * %SKL_TKN_U8_OUT_QUEUE_COUNT: Store the number of Output pins +- * +- * %SKL_TKN_U8_TIME_SLOT: TDM slot number +- * +- * %SKL_TKN_U8_CORE_ID: Stores module affinity value.Can take +- * the values: +- * SKL_AFFINITY_CORE_0 = 0, +- * SKL_AFFINITY_CORE_1, +- * SKL_AFFINITY_CORE_MAX +- * +- * %SKL_TKN_U8_MOD_TYPE: Module type value. +- * +- * %SKL_TKN_U8_CONN_TYPE: Module connection type can be a FE, +- * BE or NONE as defined : +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA) +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA) +- * +- * %SKL_TKN_U8_DEV_TYPE: Type of device to which the module is +- * connected +- * Can take the values: +- * SKL_DEVICE_BT = 0x0, +- * SKL_DEVICE_DMIC = 0x1, +- * SKL_DEVICE_I2S = 0x2, +- * SKL_DEVICE_SLIMBUS = 0x3, +- * SKL_DEVICE_HDALINK = 0x4, +- * SKL_DEVICE_HDAHOST = 0x5, +- * SKL_DEVICE_NONE +- * +- * %SKL_TKN_U8_HW_CONN_TYPE: Connection type of the HW to which the +- * module is connected +- * SKL_CONN_NONE = 0, +- * SKL_CONN_SOURCE = 1, +- * SKL_CONN_SINK = 2 +- * +- * %SKL_TKN_U16_PIN_INST_ID: Stores the pin instance id +- * +- * %SKL_TKN_U16_MOD_INST_ID: Stores the mdule instance id +- * +- * %SKL_TKN_U32_MAX_MCPS: Module max mcps value +- * +- * %SKL_TKN_U32_MEM_PAGES: Module resource pages +- * +- * %SKL_TKN_U32_OBS: Stores Output Buffer size +- * +- * %SKL_TKN_U32_IBS: Stores input buffer size +- * +- * %SKL_TKN_U32_VBUS_ID: Module VBUS_ID. PDM=0, SSP0=0, +- * SSP1=1,SSP2=2, +- * SSP3=3, SSP4=4, +- * SSP5=5, SSP6=6,INVALID +- * +- * %SKL_TKN_U32_PARAMS_FIXUP: Module Params fixup mask +- * %SKL_TKN_U32_CONVERTER: Module params converter mask +- * %SKL_TKN_U32_PIPE_ID: Stores the pipe id +- * +- * %SKL_TKN_U32_PIPE_CONN_TYPE: Type of the token to which the pipe is +- * connected to. It can be +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA), +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA), +- * +- * %SKL_TKN_U32_PIPE_PRIORITY: Pipe priority value +- * %SKL_TKN_U32_PIPE_MEM_PGS: Pipe resource pages +- * +- * %SKL_TKN_U32_DIR_PIN_COUNT: Value for the direction to set input/output +- * formats and the pin count. +- * The first 4 bits have the direction +- * value and the next 4 have +- * the pin count value. +- * SKL_DIR_IN = 0, SKL_DIR_OUT = 1. +- * The input and output formats +- * share the same set of tokens +- * with the distinction between input +- * and output made by reading direction +- * token. +- * +- * %SKL_TKN_U32_FMT_CH: Supported channel count +- * +- * %SKL_TKN_U32_FMT_FREQ: Supported frequency/sample rate +- * +- * %SKL_TKN_U32_FMT_BIT_DEPTH: Supported container size +- * +- * %SKL_TKN_U32_FMT_SAMPLE_SIZE:Number of samples in the container +- * +- * %SKL_TKN_U32_FMT_CH_CONFIG: Supported channel configurations for the +- * input/output. +- * +- * %SKL_TKN_U32_FMT_INTERLEAVE: Interleaving style which can be per +- * channel or per sample. The values can be : +- * SKL_INTERLEAVING_PER_CHANNEL = 0, +- * SKL_INTERLEAVING_PER_SAMPLE = 1, +- * +- * %SKL_TKN_U32_FMT_SAMPLE_TYPE: +- * Specifies the sample type. Can take the +- * values: SKL_SAMPLE_TYPE_INT_MSB = 0, +- * SKL_SAMPLE_TYPE_INT_LSB = 1, +- * SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- * SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- * SKL_SAMPLE_TYPE_FLOAT = 4 +- * +- * %SKL_TKN_U32_CH_MAP: Channel map values +- * %SKL_TKN_U32_MOD_SET_PARAMS: It can take these values: +- * SKL_PARAM_DEFAULT, SKL_PARAM_INIT, +- * SKL_PARAM_SET, SKL_PARAM_BIND +- * +- * %SKL_TKN_U32_MOD_PARAM_ID: ID of the module params +- * +- * %SKL_TKN_U32_CAPS_SET_PARAMS: +- * Set params value +- * +- * %SKL_TKN_U32_CAPS_PARAMS_ID: Params ID +- * +- * %SKL_TKN_U32_CAPS_SIZE: Caps size +- * +- * %SKL_TKN_U32_PROC_DOMAIN: Specify processing domain +- * +- * %SKL_TKN_U32_LIB_COUNT: Specifies the number of libraries +- * +- * %SKL_TKN_STR_LIB_NAME: Specifies the library name +- * +- * %SKL_TKN_U32_PMODE: Specifies the power mode for pipe +- * +- * %SKL_TKL_U32_D0I3_CAPS: Specifies the D0i3 capability for module +- * +- * %SKL_TKN_U32_DMA_BUF_SIZE: DMA buffer size in millisec +- * +- * %SKL_TKN_U32_PIPE_DIR: Specifies pipe direction. Can be +- * playback/capture. +- * +- * %SKL_TKN_U32_NUM_CONFIGS: Number of pipe configs +- * +- * %SKL_TKN_U32_PATH_MEM_PGS: Size of memory (in pages) required for pipeline +- * and its data +- * +- * %SKL_TKN_U32_PIPE_CONFIG_ID: Config id for the modules in the pipe +- * and PCM params supported by that pipe +- * config. This is used as index to fill +- * up the pipe config and module config +- * structure. +- * +- * %SKL_TKN_U32_CFG_FREQ: +- * %SKL_TKN_U8_CFG_CHAN: +- * %SKL_TKN_U8_CFG_BPS: PCM params (freq, channels, bits per sample) +- * supported for each of the pipe configs. +- * +- * %SKL_TKN_CFG_MOD_RES_ID: Module's resource index for each of the +- * pipe config +- * +- * %SKL_TKN_CFG_MOD_FMT_ID: Module's interface index for each of the +- * pipe config +- * +- * %SKL_TKN_U8_NUM_MOD: Number of modules in the manifest +- * +- * %SKL_TKN_MM_U8_MOD_IDX: Current index of the module in the manifest +- * +- * %SKL_TKN_MM_U8_NUM_RES: Number of resources for the module +- * +- * %SKL_TKN_MM_U8_NUM_INTF: Number of interfaces for the module +- * +- * %SKL_TKN_MM_U32_RES_ID: Resource index for the resource info to +- * be filled into. +- * A module can support multiple resource +- * configuration and is represnted as a +- * resource table. This index is used to +- * fill information into appropriate index. +- * +- * %SKL_TKN_MM_U32_CPS: DSP cycles per second +- * +- * %SKL_TKN_MM_U32_DMA_SIZE: Allocated buffer size for gateway DMA +- * +- * %SKL_TKN_MM_U32_CPC: DSP cycles allocated per frame +- * +- * %SKL_TKN_MM_U32_RES_PIN_ID: Resource pin index in the module +- * +- * %SKL_TKN_MM_U32_INTF_PIN_ID: Interface index in the module +- * +- * %SKL_TKN_MM_U32_PIN_BUF: Buffer size of the module pin +- * +- * %SKL_TKN_MM_U32_FMT_ID: Format index for each of the interface/ +- * format information to be filled into. +- * +- * %SKL_TKN_MM_U32_NUM_IN_FMT: Number of input formats +- * %SKL_TKN_MM_U32_NUM_OUT_FMT: Number of output formats +- * +- * %SKL_TKN_U32_ASTATE_IDX: Table Index for the A-State entry to be filled +- * with kcps and clock source +- * +- * %SKL_TKN_U32_ASTATE_COUNT: Number of valid entries in A-State table +- * +- * %SKL_TKN_U32_ASTATE_KCPS: Specifies the core load threshold (in kilo +- * cycles per second) below which DSP is clocked +- * from source specified by clock source. +- * +- * %SKL_TKN_U32_ASTATE_CLK_SRC: Clock source for A-State entry +- * +- * %SKL_TKN_U32_FMT_CFG_IDX: Format config index +- * +- * module_id and loadable flags dont have tokens as these values will be +- * read from the DSP FW manifest +- * +- * Tokens defined can be used either in the manifest or widget private data. +- * +- * SKL_TKN_MM is used as a suffix for all tokens that represent +- * module data in the manifest. +- */ +-enum SKL_TKNS { +- SKL_TKN_UUID = 1, +- SKL_TKN_U8_NUM_BLOCKS, +- SKL_TKN_U8_BLOCK_TYPE, +- SKL_TKN_U8_IN_PIN_TYPE, +- SKL_TKN_U8_OUT_PIN_TYPE, +- SKL_TKN_U8_DYN_IN_PIN, +- SKL_TKN_U8_DYN_OUT_PIN, +- SKL_TKN_U8_IN_QUEUE_COUNT, +- SKL_TKN_U8_OUT_QUEUE_COUNT, +- SKL_TKN_U8_TIME_SLOT, +- SKL_TKN_U8_CORE_ID, +- SKL_TKN_U8_MOD_TYPE, +- SKL_TKN_U8_CONN_TYPE, +- SKL_TKN_U8_DEV_TYPE, +- SKL_TKN_U8_HW_CONN_TYPE, +- SKL_TKN_U16_MOD_INST_ID, +- SKL_TKN_U16_BLOCK_SIZE, +- SKL_TKN_U32_MAX_MCPS, +- SKL_TKN_U32_MEM_PAGES, +- SKL_TKN_U32_OBS, +- SKL_TKN_U32_IBS, +- SKL_TKN_U32_VBUS_ID, +- SKL_TKN_U32_PARAMS_FIXUP, +- SKL_TKN_U32_CONVERTER, +- SKL_TKN_U32_PIPE_ID, +- SKL_TKN_U32_PIPE_CONN_TYPE, +- SKL_TKN_U32_PIPE_PRIORITY, +- SKL_TKN_U32_PIPE_MEM_PGS, +- SKL_TKN_U32_DIR_PIN_COUNT, +- SKL_TKN_U32_FMT_CH, +- SKL_TKN_U32_FMT_FREQ, +- SKL_TKN_U32_FMT_BIT_DEPTH, +- SKL_TKN_U32_FMT_SAMPLE_SIZE, +- SKL_TKN_U32_FMT_CH_CONFIG, +- SKL_TKN_U32_FMT_INTERLEAVE, +- SKL_TKN_U32_FMT_SAMPLE_TYPE, +- SKL_TKN_U32_FMT_CH_MAP, +- SKL_TKN_U32_PIN_MOD_ID, +- SKL_TKN_U32_PIN_INST_ID, +- SKL_TKN_U32_MOD_SET_PARAMS, +- SKL_TKN_U32_MOD_PARAM_ID, +- SKL_TKN_U32_CAPS_SET_PARAMS, +- SKL_TKN_U32_CAPS_PARAMS_ID, +- SKL_TKN_U32_CAPS_SIZE, +- SKL_TKN_U32_PROC_DOMAIN, +- SKL_TKN_U32_LIB_COUNT, +- SKL_TKN_STR_LIB_NAME, +- SKL_TKN_U32_PMODE, +- SKL_TKL_U32_D0I3_CAPS, /* Typo added at v4.10 */ +- SKL_TKN_U32_D0I3_CAPS = SKL_TKL_U32_D0I3_CAPS, +- SKL_TKN_U32_DMA_BUF_SIZE, +- +- SKL_TKN_U32_PIPE_DIRECTION, +- SKL_TKN_U32_PIPE_CONFIG_ID, +- SKL_TKN_U32_NUM_CONFIGS, +- SKL_TKN_U32_PATH_MEM_PGS, +- +- SKL_TKN_U32_CFG_FREQ, +- SKL_TKN_U8_CFG_CHAN, +- SKL_TKN_U8_CFG_BPS, +- SKL_TKN_CFG_MOD_RES_ID, +- SKL_TKN_CFG_MOD_FMT_ID, +- SKL_TKN_U8_NUM_MOD, +- +- SKL_TKN_MM_U8_MOD_IDX, +- SKL_TKN_MM_U8_NUM_RES, +- SKL_TKN_MM_U8_NUM_INTF, +- SKL_TKN_MM_U32_RES_ID, +- SKL_TKN_MM_U32_CPS, +- SKL_TKN_MM_U32_DMA_SIZE, +- SKL_TKN_MM_U32_CPC, +- SKL_TKN_MM_U32_RES_PIN_ID, +- SKL_TKN_MM_U32_INTF_PIN_ID, +- SKL_TKN_MM_U32_PIN_BUF, +- SKL_TKN_MM_U32_FMT_ID, +- SKL_TKN_MM_U32_NUM_IN_FMT, +- SKL_TKN_MM_U32_NUM_OUT_FMT, +- +- SKL_TKN_U32_ASTATE_IDX, +- SKL_TKN_U32_ASTATE_COUNT, +- SKL_TKN_U32_ASTATE_KCPS, +- SKL_TKN_U32_ASTATE_CLK_SRC, +- +- SKL_TKN_U32_FMT_CFG_IDX = 96, +- SKL_TKN_MAX = SKL_TKN_U32_FMT_CFG_IDX, +-}; +- +-#endif +diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h +--- a/include/uapi/sound/sof/tokens.h ++++ b/include/uapi/sound/sof/tokens.h +@@ -111,8 +111,8 @@ + + #define SOF_TKN_COMP_SCHED_DOMAIN 418 + #define SOF_TKN_COMP_DOMAIN_ID 419 +-#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 420 +-#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 421 ++#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 420 ++#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 421 + + /* SSP */ + #define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 +@@ -223,6 +223,7 @@ + #define SOF_TKN_AMD_ACPI2S_RATE 1700 + #define SOF_TKN_AMD_ACPI2S_CH 1701 + #define SOF_TKN_AMD_ACPI2S_TDM_MODE 1702 ++#define SOF_TKN_AMD_ACPI2S_FORMAT 1703 + + /* MICFIL PDM */ + #define SOF_TKN_IMX_MICFIL_RATE 2000 +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -171,13 +171,13 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + case HDA_GEN_PCM_ACT_OPEN: + pm_runtime_get_sync(dev); + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + tas_hda->priv->playback_started = true; + } + break; + case HDA_GEN_PCM_ACT_CLOSE: + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -720,7 +720,7 @@ static int tas2781_runtime_suspend(struct device *dev) + * Stop the playback if it's unused. + */ + if (tas_hda->priv->playback_started) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -750,7 +750,7 @@ static int tas2781_system_suspend(struct device *dev) + + /* Shutdown chip before system suspend */ + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + + /* + * Reset GPIO may be shared, so cannot reset here. +@@ -783,7 +783,7 @@ static int tas2781_system_resume(struct device *dev) + TASDEVICE_BIN_BLK_PRE_POWER_UP); + + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + + return 0; + } +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -399,11 +399,11 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + pm_runtime_get_sync(dev); + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + } else if (action == HDA_GEN_PCM_ACT_CLOSE) { + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + pm_runtime_put_autosuspend(dev); + } + } +@@ -847,7 +847,7 @@ static int tas2781_runtime_suspend(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + tas_priv->tasdevice[tas_priv->index].cur_book = -1; + tas_priv->tasdevice[tas_priv->index].cur_conf = -1; +@@ -864,7 +864,7 @@ static int tas2781_runtime_resume(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + + return 0; + } +@@ -882,7 +882,7 @@ static int tas2781_system_suspend(struct device *dev) + /* Shutdown chip before system suspend */ + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + return 0; + } +@@ -917,7 +917,7 @@ static int tas2781_system_resume(struct device *dev) + tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; + + if (tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + } + + return ret; +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -63,6 +63,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA1"), ++ }, ++ }, + {} + }; + +@@ -379,5 +386,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { + }; + EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); + ++struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { ++ { ++ .id = "AMDI1010", ++ .drv_name = "acp7x-dsp", ++ .pdata = &acp_quirk_data, ++ .fw_filename = "sof-acp7x.ri", ++ .sof_tplg_filename = "sof-acp7x.tplg", ++ }, ++ {}, ++}; ++EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); ++ + MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); + MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c +--- a/sound/soc/amd/acp-pcm-dma.c ++++ b/sound/soc/amd/acp-pcm-dma.c +@@ -691,12 +691,10 @@ static irqreturn_t dma_irq_handler(int irq, void *arg) + { + u16 dscr_idx; + u32 intr_flag, ext_intr_status; +- struct audio_drv_data *irq_data; ++ struct audio_drv_data *irq_data = arg; + void __iomem *acp_mmio; +- struct device *dev = arg; + bool valid_irq = false; + +- irq_data = dev_get_drvdata(dev); + acp_mmio = irq_data->acp_mmio; + + ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT); +@@ -1291,10 +1289,10 @@ static int acp_audio_probe(struct platform_device *pdev) + + irq = platform_get_irq(pdev, 0); + if (irq < 0) +- return -ENODEV; ++ return irq; + + status = devm_request_irq(&pdev->dev, irq, dma_irq_handler, +- 0, "ACP_IRQ", &pdev->dev); ++ 0, "ACP_IRQ", audio_drv_data); + if (status) { + dev_err(&pdev->dev, "ACP IRQ request failed\n"); + return status; +diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c +--- a/sound/soc/amd/acp/acp-i2s.c ++++ b/sound/soc/amd/acp/acp-i2s.c +@@ -686,6 +686,10 @@ static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_d + return 0; + } + ++static const u64 acp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .startup = acp_i2s_startup, + .hw_params = acp_i2s_hwparams, +@@ -693,6 +697,8 @@ const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .trigger = acp_i2s_trigger, + .set_fmt = acp_i2s_set_fmt, + .set_tdm_slot = acp_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, "SND_SOC_ACP_COMMON"); + +diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c +--- a/sound/soc/amd/acp/acp-mach-common.c ++++ b/sound/soc/amd/acp/acp-mach-common.c +@@ -938,15 +938,10 @@ static int acp_max98388_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai = + snd_soc_card_get_codec_dai(card, + MAX98388_CODEC_DAI); +- int ret; + +- ret = snd_soc_dai_set_fmt(codec_dai, +- SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | +- SND_SOC_DAIFMT_NB_NF); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_fmt(codec_dai, ++ SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_NF); + } + + static const struct snd_soc_ops acp_max98388_ops = { +diff --git a/sound/soc/amd/acp/acp-pci.c b/sound/soc/amd/acp/acp-pci.c +--- a/sound/soc/amd/acp/acp-pci.c ++++ b/sound/soc/amd/acp/acp-pci.c +@@ -118,17 +118,10 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + if (!chip) + return -ENOMEM; + +- if (pci_enable_device(pci)) ++ if (pcim_enable_device(pci)) + return dev_err_probe(&pci->dev, -ENODEV, + "pci_enable_device failed\n"); + +- ret = pci_request_regions(pci, "AMD ACP3x audio"); +- if (ret < 0) { +- dev_err(&pci->dev, "pci_request_regions failed\n"); +- ret = -ENOMEM; +- goto disable_pci; +- } +- + pci_set_master(pci); + + chip->acp_rev = pci->revision; +@@ -161,24 +154,21 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + break; + default: + dev_err(dev, "Unsupported device revision:0x%x\n", pci->revision); +- ret = -EINVAL; +- goto release_regions; ++ return -EINVAL; + } + chip->flag = flag; + + addr = pci_resource_start(pci, 0); +- chip->base = devm_ioremap(&pci->dev, addr, pci_resource_len(pci, 0)); +- if (!chip->base) { +- ret = -ENOMEM; +- goto release_regions; +- } ++ chip->base = pcim_iomap_region(pci, 0, "AMD ACP3x audio"); ++ if (IS_ERR(chip->base)) ++ return PTR_ERR(chip->base); + + chip->addr = addr; + + chip->acp_hw_ops_init(chip); + ret = acp_hw_init(chip); + if (ret) +- goto release_regions; ++ goto de_init; + + ret = devm_request_irq(dev, pci->irq, irq_handler, + IRQF_SHARED, "ACP_I2S_IRQ", chip); +@@ -214,10 +204,6 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + + de_init: + acp_hw_deinit(chip); +-release_regions: +- pci_release_regions(pci); +-disable_pci: +- pci_disable_device(pci); + + return ret; + }; +diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +--- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +@@ -531,15 +531,15 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(soc_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + soc_sdw_quirk, quirk_override); + soc_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = soc_sdw_quirk; +- dev_dbg(card->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); ++ dev_dbg(&pdev->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ + for (i = 0; i < ctx->codec_info_list_count; i++) + codec_info_list[i].amp_num = 0; +@@ -556,12 +556,12 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:acp-dmic cfg-mics:%d", + card->components, + 1); +@@ -570,9 +570,9 @@ static int mc_probe(struct platform_device *pdev) + } + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -590,8 +590,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sdw", }, +- {} ++ { .name = "amd_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -314,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + if (ret < 0) + return ret; + +@@ -399,12 +399,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -423,15 +423,15 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -449,8 +449,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sof_sdw", }, +- {} ++ { .name = "amd_sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sof-mach.c b/sound/soc/amd/acp/acp-sof-mach.c +--- a/sound/soc/amd/acp/acp-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sof-mach.c +@@ -128,31 +128,31 @@ static int acp_sof_probe(struct platform_device *pdev) + static const struct platform_device_id board_ids[] = { + { + .name = "rt5682-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data, + }, + { + .name = "rt5682-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_max_data, + }, + { + .name = "rt5682s-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data, + }, + { + .name = "rt5682s-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data, + }, + { + .name = "nau8825-max", +- .driver_data = (kernel_ulong_t)&sof_nau8825_data ++ .driver_data = (kernel_ulong_t)&sof_nau8825_data, + }, + { + .name = "rt5682s-hs-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data, + }, + { + .name = "nau8821-max", +- .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data ++ .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data, + }, + { } + }; +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -659,6 +659,56 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_l1u0_tas2783x2_l1u8b[] = { + {} + }; + ++static const struct snd_soc_acpi_adr_device rt712_vb_l1u0_adr[] = { ++ { ++ .adr = 0x000130025D071201ull, ++ /* ++ * On this platform speakers are provided by two TAS2783 amps, ++ * so the AIF2 amp path is left unused: jack + DMIC only. ++ */ ++ .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), ++ .endpoints = jack_dmic_endpoints, ++ .name_prefix = "rt712" ++ } ++}; ++ ++/* ++ * Unique ID 0xC drives the left speaker and 0x9 the right one. The order of the ++ * entries matters as much as the endpoints: asoc_sdw_parse_sdw_endpoints() ++ * appends them to the dailink in array order and sdw_compute_slave_ports() ++ * hands out payload block offsets in that same order, so the first entry is the ++ * one that receives channel 0. ++ */ ++static const struct snd_soc_acpi_adr_device tas2783x2_l0u9c_adr[] = { ++ { ++ .adr = 0x00003C0102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_l_endpoint, ++ .name_prefix = "tas2783-1", ++ }, ++ { ++ .adr = 0x0000390102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_r_endpoint, ++ .name_prefix = "tas2783-2", ++ }, ++}; ++ ++/* HP OmniBook X Flip 14-kc0xxx (board 8EA1) */ ++static const struct snd_soc_acpi_link_adr acp70_tas2783x2_l0u9c_rt712_vb_l1u0[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783x2_l0u9c_adr), ++ .adr_d = tas2783x2_l0u9c_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_l1u0_adr), ++ .adr_d = rt712_vb_l1u0_adr, ++ }, ++ {} ++}; ++ + static const struct snd_soc_acpi_endpoint rt721_endpoints[] = { + { /* Jack Playback/Capture Endpoint (AIF1) */ + .num = 0, +@@ -704,6 +754,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { + .links = acp70_tas2783_2, + .drv_name = "amd_sdw", + }, ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783x2_l0u9c_rt712_vb_l1u0, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0) | BIT(1), + .links = acp70_rt1320_l0_rt722_l1, +diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h +--- a/sound/soc/amd/mach-config.h ++++ b/sound/soc/amd/mach-config.h +@@ -28,6 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; ++extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[]; + + struct config_entry { + u32 flags; +diff --git a/sound/soc/amd/raven/acp3x-i2s.c b/sound/soc/amd/raven/acp3x-i2s.c +--- a/sound/soc/amd/raven/acp3x-i2s.c ++++ b/sound/soc/amd/raven/acp3x-i2s.c +@@ -250,11 +250,17 @@ static int acp3x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp3x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp3x_i2s_dai_ops = { + .hw_params = acp3x_i2s_hwparams, + .trigger = acp3x_i2s_trigger, + .set_fmt = acp3x_i2s_set_fmt, + .set_tdm_slot = acp3x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp3x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp3x_dai_component = { +diff --git a/sound/soc/amd/vangogh/acp5x-i2s.c b/sound/soc/amd/vangogh/acp5x-i2s.c +--- a/sound/soc/amd/vangogh/acp5x-i2s.c ++++ b/sound/soc/amd/vangogh/acp5x-i2s.c +@@ -337,11 +337,17 @@ static int acp5x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp5x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = { + .hw_params = acp5x_i2s_hwparams, + .trigger = acp5x_i2s_trigger, + .set_fmt = acp5x_i2s_set_fmt, + .set_tdm_slot = acp5x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp5x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp5x_dai_component = { +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -795,12 +795,12 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + } + }, + { +- .driver_data = &acp6x_card, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), +- DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), +- } +- }, ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c +--- a/sound/soc/apple/mca.c ++++ b/sound/soc/apple/mca.c +@@ -133,12 +133,17 @@ struct mca_cluster { + struct clk *clk_parent; + struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1]; + +- bool port_started[SNDRV_PCM_STREAM_LAST + 1]; +- int port_driver; /* The cluster driving this cluster's port */ ++ bool clk_provider; ++ ++ bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1]; ++ int port_clk_driver; /* The cluster driving this cluster's port */ + + bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1]; + struct device_link *pd_link; + ++ /* In case of clock consumer FE */ ++ int syncgen_in_use; ++ + unsigned int bclk_ratio; + + /* Masks etc. picked up via the set_tdm_slot method */ +@@ -157,7 +162,7 @@ struct mca_data { + struct reset_control *rstc; + struct device_link *pd_link; + +- /* Mutex for accessing port_driver of foreign clusters */ ++ /* Mutex for accessing port_clk_driver of foreign clusters */ + struct mutex port_mutex; + + int nclusters; +@@ -220,6 +225,12 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, + FIELD_PREP(SERDES_CONF_SYNC_SEL, 0)); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, cl->no + 1)); ++ /* ++ * ADMAC gets started right after this. This delay seems ++ * to be needed for that to be reliable, e.g. ensure the ++ * clock is stable? ++ */ ++ udelay(100); + break; + default: + break; +@@ -256,11 +267,29 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd, + return 0; + } + ++static int mca_fe_get_portmask(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dpcm *dpcm; ++ int mask = 0; ++ ++ for_each_dpcm_be(fe, substream->stream, dpcm) { ++ int no = mca_dai_to_cluster(snd_soc_rtd_to_cpu(dpcm->be, 0))->no; ++ ++ mask |= 1 << no; ++ } ++ ++ return mask; ++} ++ + static int mca_fe_enable_clocks(struct mca_cluster *cl) + { + struct mca_data *mca = cl->host; + int ret; + ++ if (!cl->clk_provider) ++ return -EINVAL; ++ + ret = clk_prepare_enable(cl->clk_parent); + if (ret) { + dev_err(mca->dev, +@@ -311,7 +340,7 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + for (i = 0; i < mca->nclusters; i++) { + be_cl = &mca->clusters[i]; + +- if (be_cl->port_driver != cl->no) ++ if (be_cl->port_clk_driver != cl->no) + continue; + + for_each_pcm_streams(stream) { +@@ -323,36 +352,6 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + return false; + } + +-static int mca_be_prepare(struct snd_pcm_substream *substream, +- struct snd_soc_dai *dai) +-{ +- struct mca_cluster *cl = mca_dai_to_cluster(dai); +- struct mca_data *mca = cl->host; +- struct mca_cluster *fe_cl; +- int ret; +- +- if (cl->port_driver < 0) +- return -EINVAL; +- +- fe_cl = &mca->clusters[cl->port_driver]; +- +- /* +- * Typically the CODECs we are paired with will require clocks +- * to be present at time of unmute with the 'mute_stream' op +- * or at time of DAPM widget power-up. We need to enable clocks +- * here at the latest (frontend prepare would be too late). +- */ +- if (!mca_fe_clocks_in_use(fe_cl)) { +- ret = mca_fe_enable_clocks(fe_cl); +- if (ret < 0) +- return ret; +- } +- +- cl->clocks_in_use[substream->stream] = true; +- +- return 0; +-} +- + static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +@@ -360,7 +359,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct mca_data *mca = cl->host; + struct mca_cluster *fe_cl; + +- if (cl->port_driver < 0) ++ if (cl->port_clk_driver < 0) + return -EINVAL; + + /* +@@ -368,7 +367,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + * belong to the same PCM, accesses should have been + * synchronized at ASoC level. + */ +- fe_cl = &mca->clusters[cl->port_driver]; ++ fe_cl = &mca->clusters[cl->port_clk_driver]; + if (!mca_fe_clocks_in_use(fe_cl)) + return 0; /* Nothing to do */ + +@@ -380,6 +379,56 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int mca_fe_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ /* Turn on the cluster power domain if not already in use */ ++ if (!cl->syncgen_in_use) { ++ int port = ffs(mca_fe_get_portmask(substream)); ++ ++ cl->pd_link = device_link_add(mca->dev, cl->pd_dev, ++ DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | ++ DL_FLAG_RPM_ACTIVE); ++ if (!cl->pd_link) { ++ dev_err(mca->dev, ++ "cluster %d: unable to prop-up power domain\n", cl->no); ++ return -EINVAL; ++ } ++ ++ mca_modify(cl, REG_SYNCGEN_MCLK_SEL, SYNCGEN_MCLK_SEL, BIT(port)); ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, ++ SYNCGEN_STATUS_EN); ++ } ++ cl->syncgen_in_use |= 1 << substream->stream; ++ ++ return 0; ++} ++ ++static int mca_fe_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ cl->syncgen_in_use &= ~(1 << substream->stream); ++ if (cl->syncgen_in_use) ++ return 0; ++ ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); ++ if (cl->pd_link) ++ device_link_del(cl->pd_link); ++ ++ return 0; ++} ++ + static unsigned int mca_crop_mask(unsigned int mask, int nchans) + { + while (hweight32(mask) > nchans) +@@ -390,7 +439,7 @@ static unsigned int mca_crop_mask(unsigned int mask, int nchans) + + static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + unsigned int mask, int slots, int nchans, +- int slot_width, bool is_tx, int port) ++ int slot_width, bool is_tx, int portmask) + { + __iomem void *serdes_base = cl->base + serdes_unit; + u32 serdes_conf, serdes_conf_mask; +@@ -449,7 +498,7 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + serdes_base + REG_RX_SERDES_SLOTMASK); + writel_relaxed(~((u32)mca_crop_mask(mask, nchans)), + serdes_base + REG_RX_SERDES_SLOTMASK + 0x4); +- writel_relaxed(1 << port, ++ writel_relaxed(portmask, + serdes_base + REG_RX_SERDES_PORT); + } + +@@ -505,9 +554,18 @@ static int mca_fe_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + u32 serdes_conf = 0; + u32 bitstart; + +- if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != +- SND_SOC_DAIFMT_BP_FP) ++ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { ++ case SND_SOC_DAIFMT_BP_FP: ++ cl->clk_provider = true; ++ break; ++ ++ case SND_SOC_DAIFMT_BC_FC: ++ cl->clk_provider = false; ++ break; ++ ++ default: + goto err; ++ } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: +@@ -564,24 +622,6 @@ static int mca_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) + return 0; + } + +-static int mca_fe_get_port(struct snd_pcm_substream *substream) +-{ +- struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *be; +- struct snd_soc_dpcm *dpcm; +- +- be = NULL; +- for_each_dpcm_be(fe, substream->stream, dpcm) { +- be = dpcm->be; +- break; +- } +- +- if (!be) +- return -EINVAL; +- +- return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no; +-} +- + static int mca_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +@@ -595,7 +635,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + unsigned long bclk_ratio; + unsigned int tdm_slots, tdm_slot_width, tdm_mask; + u32 regval, pad; +- int ret, port, nchans_ceiled; ++ int ret, portmask, nchans_ceiled; + + if (!cl->tdm_slot_width) { + /* +@@ -644,13 +684,13 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + tdm_mask = (1 << tdm_slots) - 1; + } + +- port = mca_fe_get_port(substream); +- if (port < 0) +- return port; ++ portmask = mca_fe_get_portmask(substream); ++ if (!portmask) ++ return -EINVAL; + + ret = mca_configure_serdes(cl, is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF, + tdm_mask, tdm_slots, params_channels(params), +- tdm_slot_width, is_tx, port); ++ tdm_slot_width, is_tx, portmask); + if (ret) + return ret; + +@@ -699,6 +739,19 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 mca_fe_selectable_formats[] = ++{ ++ /* pattern 1 */ ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF, ++ ++ /* pattern 2 */ ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF ++}; ++ + static const struct snd_soc_dai_ops mca_fe_ops = { + .startup = mca_fe_startup, + .set_fmt = mca_fe_set_fmt, +@@ -706,66 +759,128 @@ static const struct snd_soc_dai_ops mca_fe_ops = { + .set_tdm_slot = mca_fe_set_tdm_slot, + .hw_params = mca_fe_hw_params, + .trigger = mca_fe_trigger, ++ .prepare = mca_fe_prepare, ++ .hw_free = mca_fe_hw_free, ++ .auto_selectable_formats = mca_fe_selectable_formats, ++ .num_auto_selectable_formats = ARRAY_SIZE(mca_fe_selectable_formats), + }; + +-static bool mca_be_started(struct mca_cluster *cl) ++/* ++ * Is there a FE attached which will be feeding this port's clocks? ++ */ ++static bool mca_be_clk_started(struct mca_cluster *cl) + { + int stream; + + for_each_pcm_streams(stream) +- if (cl->port_started[stream]) ++ if (cl->port_clk_started[stream]) + return true; + return false; + } + ++static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be, ++ int stream) ++{ ++ struct snd_soc_pcm_runtime *fe = NULL; ++ struct snd_soc_dpcm *dpcm; ++ ++ for_each_dpcm_fe(be, stream, dpcm) { ++ if (fe && dpcm->fe != fe) { ++ dev_err(be->dev, "many FE per one BE unsupported\n"); ++ return NULL; ++ } ++ ++ fe = dpcm->fe; ++ } ++ ++ return fe; ++} ++ ++static int mca_be_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ struct mca_cluster *fe_cl, *fe_clk_cl; ++ int ret; ++ ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (cl->port_clk_driver < 0) ++ return 0; ++ ++ fe_clk_cl = &mca->clusters[cl->port_clk_driver]; ++ ++ /* ++ * Typically the CODECs we are paired with will require clocks ++ * to be present at time of unmute with the 'mute_stream' op ++ * or at time of DAPM widget power-up. We need to enable clocks ++ * here at the latest (frontend prepare would be too late). ++ */ ++ if (!mca_fe_clocks_in_use(fe_clk_cl)) { ++ ret = mca_fe_enable_clocks(fe_clk_cl); ++ if (ret < 0) ++ return ret; ++ } ++ ++ cl->clocks_in_use[substream->stream] = true; ++ ++ return 0; ++} ++ + static int mca_be_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *fe; ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; +- struct snd_soc_dpcm *dpcm; +- +- fe = NULL; +- +- for_each_dpcm_fe(be, substream->stream, dpcm) { +- if (fe && dpcm->fe != fe) { +- dev_err(mca->dev, "many FE per one BE unsupported\n"); +- return -EINVAL; +- } +- +- fe = dpcm->fe; +- } + + if (!fe) + return -EINVAL; +- + fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), ++ cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, ++ PORT_ENABLES_TX_DATA); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (mca_be_clk_started(cl)) { + /* + * Port is already started in the other direction. + * Make sure there isn't a conflict with another cluster +- * driving the port. ++ * driving the port clocks. + */ +- if (cl->port_driver != fe_cl->no) ++ if (cl->port_clk_driver != fe_cl->no) + return -EINVAL; + +- cl->port_started[substream->stream] = true; ++ cl->port_clk_started[substream->stream] = true; + return 0; + } + +- writel_relaxed(PORT_ENABLES_CLOCKS | PORT_ENABLES_TX_DATA, +- cl->base + REG_PORT_ENABLES); + writel_relaxed(FIELD_PREP(PORT_CLOCK_SEL, fe_cl->no + 1), + cl->base + REG_PORT_CLOCK_SEL); ++ + writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), + cl->base + REG_PORT_DATA_SEL); ++ ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, ++ PORT_ENABLES_CLOCKS); ++ + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = fe_cl->no; +- cl->port_started[substream->stream] = true; ++ cl->port_clk_driver = fe_cl->no; ++ cl->port_clk_started[substream->stream] = true; + + return 0; + } +@@ -773,20 +888,34 @@ static int mca_be_startup(struct snd_pcm_substream *substream, + static void mca_be_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; + +- cl->port_started[substream->stream] = false; ++ if (!fe) ++ return; ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (!mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0); ++ writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return; ++ ++ cl->port_clk_started[substream->stream] = false; ++ if (!mca_be_clk_started(cl)) { + /* + * Were we the last direction to shutdown? +- * Turn off the lights. ++ * Turn off the lights (clocks). + */ +- writel_relaxed(0, cl->base + REG_PORT_ENABLES); +- writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, 0); ++ writel_relaxed(0, cl->base + REG_PORT_CLOCK_SEL); + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + } + } + +@@ -1092,7 +1221,7 @@ static int apple_mca_probe(struct platform_device *pdev) + cl->host = mca; + cl->no = i; + cl->base = base + CLUSTER_STRIDE * i; +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + cl->clk_parent = of_clk_get(pdev->dev.of_node, i); + if (IS_ERR(cl->clk_parent)) { + dev_err(&pdev->dev, "unable to obtain clock %d: %ld\n", +diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c +--- a/sound/soc/atmel/atmel-i2s.c ++++ b/sound/soc/atmel/atmel-i2s.c +@@ -540,12 +540,16 @@ static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 atmel_i2s_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S; ++ + static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .probe = atmel_i2s_dai_probe, + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, ++ .auto_selectable_formats = &atmel_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_i2s_dai = { +diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c +--- a/sound/soc/atmel/atmel_ssc_dai.c ++++ b/sound/soc/atmel/atmel_ssc_dai.c +@@ -825,6 +825,11 @@ static int atmel_ssc_resume(struct snd_soc_component *component) + #define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + ++static const u64 atmel_ssc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .startup = atmel_ssc_startup, + .shutdown = atmel_ssc_shutdown, +@@ -833,6 +838,8 @@ static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .hw_params = atmel_ssc_hw_params, + .set_fmt = atmel_ssc_set_dai_fmt, + .set_clkdiv = atmel_ssc_set_dai_clkdiv, ++ .auto_selectable_formats = &atmel_ssc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_ssc_dai = { +diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c +--- a/sound/soc/atmel/atmel_wm8904.c ++++ b/sound/soc/atmel/atmel_wm8904.c +@@ -82,32 +82,33 @@ static struct snd_soc_card atmel_asoc_wm8904_card = { + + static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int ret; + + if (!np) { +- dev_err(&pdev->dev, "only device tree supported\n"); ++ dev_err(dev, "only device tree supported\n"); + return -EINVAL; + } + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse card name\n"); ++ dev_err(dev, "failed to parse card name\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio routing\n"); ++ dev_err(dev, "failed to parse audio routing\n"); + return ret; + } + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { +- dev_err(&pdev->dev, "failed to get dai and pcm info\n"); ++ dev_err(dev, "failed to get dai and pcm info\n"); + ret = -EINVAL; + return ret; + } +@@ -117,7 +118,7 @@ static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { +- dev_err(&pdev->dev, "failed to get codec info\n"); ++ dev_err(dev, "failed to get codec info\n"); + ret = -EINVAL; + return ret; + } +diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c +--- a/sound/soc/atmel/mchp-i2s-mcc.c ++++ b/sound/soc/atmel/mchp-i2s-mcc.c +@@ -912,6 +912,13 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 mchp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_GATED | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .probe = mchp_i2s_mcc_dai_probe, + .set_sysclk = mchp_i2s_mcc_set_sysclk, +@@ -922,6 +929,8 @@ static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, ++ .auto_selectable_formats = &mchp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 +diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c +--- a/sound/soc/atmel/mchp-pdmc.c ++++ b/sound/soc/atmel/mchp-pdmc.c +@@ -741,6 +741,8 @@ static int mchp_pdmc_pcm_new(struct snd_soc_pcm_runtime *rtd, + return ret; + } + ++static const u64 mchp_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_PDM; ++ + static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .probe = mchp_pdmc_dai_probe, + .set_fmt = mchp_pdmc_set_fmt, +@@ -748,6 +750,8 @@ static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .hw_params = mchp_pdmc_hw_params, + .trigger = mchp_pdmc_trigger, + .pcm_new = &mchp_pdmc_pcm_new, ++ .auto_selectable_formats = &mchp_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver mchp_pdmc_dai = { +diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c +--- a/sound/soc/au1x/db1200.c ++++ b/sound/soc/au1x/db1200.c +@@ -42,7 +42,7 @@ static const struct platform_device_id db1200_pids[] = { + .name = "db1550-i2s", + .driver_data = 5, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, db1200_pids); + +diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c +--- a/sound/soc/au1x/i2sc.c ++++ b/sound/soc/au1x/i2sc.c +@@ -202,11 +202,22 @@ static int au1xi2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xi2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xi2s_dai_ops = { + .startup = au1xi2s_startup, + .trigger = au1xi2s_trigger, + .hw_params = au1xi2s_hw_params, + .set_fmt = au1xi2s_set_fmt, ++ .auto_selectable_formats = &au1xi2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver au1xi2s_dai_driver = { +diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c +--- a/sound/soc/au1x/psc-ac97.c ++++ b/sound/soc/au1x/psc-ac97.c +@@ -210,9 +210,7 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, + { + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + unsigned long r, ro, stat; +- int chans, t, stype = substream->stream; +- +- chans = params_channels(params); ++ int t, stype = substream->stream; + + r = ro = __raw_readl(AC97_CFG(pscdata)); + stat = __raw_readl(AC97_STAT(pscdata)); +diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c +--- a/sound/soc/au1x/psc-i2s.c ++++ b/sound/soc/au1x/psc-i2s.c +@@ -262,11 +262,22 @@ static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xpsc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = { + .startup = au1xpsc_i2s_startup, + .trigger = au1xpsc_i2s_trigger, + .hw_params = au1xpsc_i2s_hw_params, + .set_fmt = au1xpsc_i2s_set_fmt, ++ .auto_selectable_formats = &au1xpsc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = { +diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c +--- a/sound/soc/bcm/bcm2835-i2s.c ++++ b/sound/soc/bcm/bcm2835-i2s.c +@@ -748,6 +748,17 @@ static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 bcm2835_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .probe = bcm2835_i2s_dai_probe, + .startup = bcm2835_i2s_startup, +@@ -758,6 +769,8 @@ static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .set_fmt = bcm2835_i2s_set_dai_fmt, + .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio, + .set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot, ++ .auto_selectable_formats = &bcm2835_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver bcm2835_i2s_dai = { +diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c +--- a/sound/soc/bcm/cygnus-ssp.c ++++ b/sound/soc/bcm/cygnus-ssp.c +@@ -1133,6 +1133,11 @@ static int cygnus_ssp_resume(struct snd_soc_component *component) + #define cygnus_ssp_resume NULL + #endif + ++static const u64 cygnus_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, +@@ -1141,6 +1146,8 @@ static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .set_fmt = cygnus_ssp_set_fmt, + .set_sysclk = cygnus_ssp_set_sysclk, + .set_tdm_slot = cygnus_set_dai_tdm_slot, ++ .auto_selectable_formats = &cygnus_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops cygnus_spdif_dai_ops = { +diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c +--- a/sound/soc/cirrus/ep93xx-i2s.c ++++ b/sound/soc/cirrus/ep93xx-i2s.c +@@ -401,6 +401,15 @@ static int ep93xx_i2s_resume(struct snd_soc_component *component) + #define ep93xx_i2s_resume NULL + #endif + ++static const u64 ep93xx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .probe = ep93xx_i2s_dai_probe, + .startup = ep93xx_i2s_startup, +@@ -408,6 +417,8 @@ static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, ++ .auto_selectable_formats = &ep93xx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -238,6 +238,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_RT715_SDCA_SDW + imply SND_SOC_RT721_SDCA_SDW + imply SND_SOC_RT722_SDCA_SDW ++ imply SND_SOC_RT766_SDCA_SDW + imply SND_SOC_RT1308_SDW + imply SND_SOC_RT1316_SDW + imply SND_SOC_RT1318 +@@ -367,6 +368,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_WSA881X + imply SND_SOC_WSA883X + imply SND_SOC_WSA884X ++ imply SND_SOC_WSA885X + imply SND_SOC_ZL38060 + help + Normally ASoC codec drivers are only built if a machine driver which +@@ -615,8 +617,8 @@ config SND_SOC_AK4613 + depends on I2C + + config SND_SOC_AK4619 +- tristate "AKM AK4619 CODEC" +- depends on I2C ++ tristate "AKM AK4619 CODEC" ++ depends on I2C + + config SND_SOC_AK4642 + tristate "AKM AK4642 CODEC" +@@ -894,6 +896,7 @@ config SND_SOC_CS35L56_SDW + tristate "Cirrus Logic CS35L56 CODEC (SDW)" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE ++ select IRQ_DOMAIN + select SND_SOC_CS35L56 + select SND_SOC_CS35L56_SHARED + help +@@ -1206,16 +1209,16 @@ config SND_SOC_JZ4725B_CODEC + will be called snd-soc-jz4725b-codec. + + config SND_SOC_JZ4760_CODEC +- depends on MACH_INGENIC || COMPILE_TEST +- depends on OF +- select REGMAP +- tristate "Ingenic JZ4760 internal CODEC" +- help +- Enable support for the internal CODEC found in the JZ4760 SoC +- from Ingenic. ++ depends on MACH_INGENIC || COMPILE_TEST ++ depends on OF ++ select REGMAP ++ tristate "Ingenic JZ4760 internal CODEC" ++ help ++ Enable support for the internal CODEC found in the JZ4760 SoC ++ from Ingenic. + +- This driver can also be built as a module. If so, the module +- will be called snd-soc-jz4760-codec. ++ This driver can also be built as a module. If so, the module ++ will be called snd-soc-jz4760-codec. + + config SND_SOC_JZ4770_CODEC + depends on MACH_INGENIC || COMPILE_TEST +@@ -1893,7 +1896,7 @@ config SND_SOC_RT5670 + depends on I2C + + config SND_SOC_RT5677 +- tristate ++ tristate "Realtek RT5677 Codec" + depends on I2C + select REGMAP_I2C + select REGMAP_IRQ +@@ -1983,6 +1986,15 @@ config SND_SOC_RT715_SDCA_SDW + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + ++config SND_SOC_RT766_SDCA_SDW ++ tristate "Realtek RT766 SDCA Codec - SDW" ++ depends on SOUNDWIRE ++ depends on SND_SOC_SDCA ++ select SND_SOC_SDCA_HID ++ select SND_SOC_SDCA_IRQ ++ select REGMAP_SOUNDWIRE ++ select REGMAP_SOUNDWIRE_MBQ ++ + config SND_SOC_RT9120 + tristate "Richtek RT9120 Stereo Class-D Amplifier" + depends on I2C +@@ -2302,8 +2314,8 @@ config SND_SOC_TLV320ADC3XXX + depends on I2C + depends on GPIOLIB + help +- Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 +- ADCs. ++ Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 ++ ADCs. + + config SND_SOC_TLV320AIC23 + tristate +@@ -2776,6 +2788,16 @@ config SND_SOC_WSA884X + This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D + Smart Speaker Amplifier. + ++config SND_SOC_WSA885X ++ tristate "WSA885X Codec" ++ depends on I2C ++ select REGMAP_I2C ++ help ++ This enables support for Qualcomm WSA885X Stereo Smart Speaker ++ Amplifier. The codec driver programs the amplifier register ++ map and exposes the DAI and mixer controls used by Qualcomm ++ audio machine drivers. ++ + config SND_SOC_ZL38060 + tristate "Microsemi ZL38060 Connected Home Audio Processor" + depends on SPI_MASTER +@@ -2899,28 +2921,32 @@ config SND_SOC_TPA6130A2 + depends on I2C + + config SND_SOC_LPASS_MACRO_COMMON +- tristate ++ tristate + + config SND_SOC_LPASS_WSA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm WSA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_VA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm VA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_RX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm RX Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_TX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -285,6 +285,7 @@ snd-soc-rt715-y := rt715.o rt715-sdw.o + snd-soc-rt715-sdca-y := rt715-sdca.o rt715-sdca-sdw.o + snd-soc-rt721-sdca-y := rt721-sdca.o rt721-sdca-sdw.o + snd-soc-rt722-sdca-y := rt722-sdca.o rt722-sdca-sdw.o ++snd-soc-rt766-sdca-y := rt766-sdca.o rt766-sdca-sdw.o + snd-soc-rt9120-y := rt9120.o + snd-soc-rt9123-y := rt9123.o + snd-soc-rt9123p-y := rt9123p.o +@@ -427,6 +428,7 @@ snd-soc-wm-hubs-y := wm_hubs.o + snd-soc-wsa881x-y := wsa881x.o + snd-soc-wsa883x-y := wsa883x.o + snd-soc-wsa884x-y := wsa884x.o ++snd-soc-wsa885x-y := wsa885x.o + snd-soc-zl38060-y := zl38060.o + # Amp + snd-soc-max9877-y := max9877.o +@@ -726,6 +728,7 @@ obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o + obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o + obj-$(CONFIG_SND_SOC_RT721_SDCA_SDW) += snd-soc-rt721-sdca.o + obj-$(CONFIG_SND_SOC_RT722_SDCA_SDW) += snd-soc-rt722-sdca.o ++obj-$(CONFIG_SND_SOC_RT766_SDCA_SDW) += snd-soc-rt766-sdca.o + obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o + obj-$(CONFIG_SND_SOC_RT9123) += snd-soc-rt9123.o + obj-$(CONFIG_SND_SOC_RT9123P) += snd-soc-rt9123p.o +@@ -878,6 +881,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o + obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o + obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o + obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o ++obj-$(CONFIG_SND_SOC_WSA885X) += snd-soc-wsa885x.o + obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o + + # Amp +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,14 +14,17 @@ + * for ST-Ericsson. + */ + ++#include + #include + #include + #include + #include + #include + #include ++#include + #include + #include ++#include + #include + #include + #include +@@ -51,9 +54,32 @@ + /* Macrocell register definitions */ + #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ + ++/* Nr of FIR/IIR-coeff banks in ANC-block */ ++#define AB8500_NR_OF_ANC_COEFF_BANKS 2 ++ ++/* Minimum duration to keep ANC IIR Init bit high or ++low before proceeding with the configuration sequence */ ++#define AB8500_ANC_SM_DELAY 2000 ++ ++/* Sidetone states */ ++static const char * const enum_sid_state[] = { ++ "Unconfigured", ++ "Apply FIR", ++ "FIR is configured", ++}; ++enum sid_state { ++ SID_UNCONFIGURED = 0, ++ SID_APPLY_FIR = 1, ++ SID_FIR_CONFIGURED = 2, ++}; ++ + /* Private data for AB8500 device-driver */ + struct ab8500_codec_drvdata { + struct regmap *regmap; ++ struct mutex ctrl_lock; ++ ++ /* Sidetone */ ++ enum sid_state sid_status; + }; + + static inline const char *amic_micbias_str(enum amic_micbias micbias) +@@ -616,6 +642,9 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + NULL, 0), + /* Acoustical Noise Cancellation path */ + ++ SND_SOC_DAPM_INPUT("ANC Configure Input"), ++ SND_SOC_DAPM_OUTPUT("ANC Configure Output"), ++ + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), +@@ -658,6 +687,10 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + ++ /* ANC Configure */ ++ {"ANC Configure Input", NULL, "Main Supply"}, ++ {"ANC Configure Output", NULL, "ANC Configure Input"}, ++ + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, +@@ -939,6 +972,75 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, + }; + ++/* ++ * Control-events ++ */ ++ ++static int sid_status_control_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); ++ ++ guard(mutex)(&drvdata->ctrl_lock); ++ ucontrol->value.enumerated.item[0] = drvdata->sid_status; ++ ++ return 0; ++} ++ ++/* Write sidetone FIR-coefficients configuration sequence */ ++static int sid_status_control_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); ++ unsigned int param, sidconf; ++ int status = 1; ++ ++ dev_dbg(component->dev, "%s: Enter\n", __func__); ++ ++ if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { ++ dev_err(component->dev, ++ "%s: ERROR: This control supports '%s' only!\n", ++ __func__, enum_sid_state[SID_APPLY_FIR]); ++ return -EIO; ++ } ++ ++ guard(mutex)(&drvdata->ctrl_lock); ++ ++ sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); ++ if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { ++ if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { ++ dev_err(component->dev, "%s: Sidetone busy while off!\n", ++ __func__); ++ status = -EPERM; ++ } else { ++ status = -EBUSY; ++ } ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ return status; ++ } ++ ++ snd_soc_component_write(component, AB8500_SIDFIRADR, 0); ++ ++ for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { ++ snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); ++ snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); ++ } ++ ++ snd_soc_component_update_bits(component, AB8500_SIDFIRADR, ++ BIT(AB8500_SIDFIRADR_FIRSIDSET), ++ BIT(AB8500_SIDFIRADR_FIRSIDSET)); ++ snd_soc_component_update_bits(component, AB8500_SIDFIRADR, ++ BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); ++ ++ drvdata->sid_status = SID_FIR_CONFIGURED; ++ ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ ++ return status; ++} ++ + /* + * Controls - Non-DAPM ASoC + */ +@@ -1222,6 +1324,9 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); + ++/* Sidetone */ ++static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); ++ + /* ANC */ + + static struct snd_kcontrol_new ab8500_ctrls[] = { +@@ -1512,6 +1617,8 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), + + /* Sidetone */ ++ SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, ++ sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), + }; +@@ -2041,8 +2148,10 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + + static int ab8500_codec_probe(struct snd_soc_component *component) + { ++ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + int status; + +@@ -2075,6 +2184,10 @@ static int ab8500_codec_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + ++ snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); ++ ++ mutex_init(&drvdata->ctrl_lock); ++ + return status; + } + +@@ -2103,6 +2216,7 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; ++ drvdata->sid_status = SID_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, +diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c +--- a/sound/soc/codecs/ad1836.c ++++ b/sound/soc/codecs/ad1836.c +@@ -193,9 +193,15 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 ad1836_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad1836_dai_ops = { + .hw_params = ad1836_hw_params, + .set_fmt = ad1836_set_dai_fmt, ++ .auto_selectable_formats = &ad1836_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define AD183X_DAI(_name, num_dacs, num_adcs) \ +diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c +--- a/sound/soc/codecs/ad193x.c ++++ b/sound/soc/codecs/ad193x.c +@@ -394,6 +394,14 @@ static int ad193x_startup(struct snd_pcm_substream *substream, + &constr); + } + ++static const u64 ad193x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad193x_dai_ops = { + .startup = ad193x_startup, + .hw_params = ad193x_hw_params, +@@ -401,6 +409,8 @@ static const struct snd_soc_dai_ops ad193x_dai_ops = { + .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, + .set_fmt = ad193x_set_dai_fmt, ++ .auto_selectable_formats = &ad193x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/adau7118-hw.c b/sound/soc/codecs/adau7118-hw.c +--- a/sound/soc/codecs/adau7118-hw.c ++++ b/sound/soc/codecs/adau7118-hw.c +@@ -22,7 +22,7 @@ static const struct of_device_id adau7118_of_match[] = { + MODULE_DEVICE_TABLE(of, adau7118_of_match); + + static const struct platform_device_id adau7118_id[] = { +- { .name = "adau7118" }, ++ { .name = "adau7118" }, + { } + }; + MODULE_DEVICE_TABLE(platform, adau7118_id); +diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c +--- a/sound/soc/codecs/adav80x.c ++++ b/sound/soc/codecs/adav80x.c +@@ -743,11 +743,19 @@ static void adav80x_dai_shutdown(struct snd_pcm_substream *substream, + adav80x->rate = 0; + } + ++static const u64 adav80x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops adav80x_dai_ops = { + .set_fmt = adav80x_set_dai_fmt, + .hw_params = adav80x_hw_params, + .startup = adav80x_dai_startup, + .shutdown = adav80x_dai_shutdown, ++ .auto_selectable_formats = &adav80x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ +diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c +--- a/sound/soc/codecs/ak4613.c ++++ b/sound/soc/codecs/ak4613.c +@@ -95,6 +95,7 @@ + * see + * AK4613_ENABLE_TDM_TEST + */ ++#include + #include + #include + #include +@@ -384,7 +385,7 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + priv->cnt--; + if (priv->cnt < 0) { + dev_err(dev, "unexpected counter error\n"); +@@ -392,7 +393,6 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + } + if (!priv->cnt) + priv->ctrl1 = 0; +- mutex_unlock(&priv->lock); + } + + static void ak4613_hw_constraints(struct ak4613_priv *priv, +@@ -507,10 +507,9 @@ static int ak4613_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + ak4613_hw_constraints(priv, substream); + priv->cnt++; +- mutex_unlock(&priv->lock); + + return 0; + } +@@ -599,45 +598,47 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + */ + ret = -EINVAL; + +- mutex_lock(&priv->lock); +- if (priv->cnt > 1) { +- /* +- * If it was already working, use current priv->ctrl1 +- */ +- ret = 0; +- } else { +- /* +- * It is not yet working, +- */ +- unsigned int channel = params_channels(params); +- u8 tdm; ++ scoped_guard(mutex, &priv->lock) { ++ if (priv->cnt > 1) { ++ /* ++ * If it was already working, use current priv->ctrl1 ++ */ ++ ret = 0; ++ } else { ++ /* ++ * It is not yet working, ++ */ ++ unsigned int channel = params_channels(params); ++ u8 tdm; + +- /* STEREO or TDM */ +- if (channel == 2) +- tdm = AK4613_CONFIG_MODE_STEREO; +- else +- tdm = AK4613_CONFIG_GET(priv, MODE); ++ /* STEREO or TDM */ ++ if (channel == 2) ++ tdm = AK4613_CONFIG_MODE_STEREO; ++ else ++ tdm = AK4613_CONFIG_GET(priv, MODE); + +- for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { +- const struct ak4613_interface *iface = ak4613_iface + i; ++ for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { ++ const struct ak4613_interface *iface = ak4613_iface + i; + +- if ((iface->fmt == fmt) && (iface->width == width)) { +- /* +- * Ctrl1 +- * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | +- * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| +- * < tdm > < iface->dif > +- */ +- priv->ctrl1 = (tdm << 6) | (iface->dif << 3); +- ret = 0; +- break; ++ if (iface->fmt == fmt && iface->width == width) { ++ /* ++ * Ctrl1 ++ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | ++ * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| ++ * < tdm > < iface->dif > ++ */ ++ priv->ctrl1 = (tdm << 6) | (iface->dif << 3); ++ ret = 0; ++ break; ++ } + } + } + } +- mutex_unlock(&priv->lock); + +- if (ret < 0) +- goto hw_params_end; ++ if (ret < 0) { ++ dev_warn(dev, "unsupported data width/format combination\n"); ++ return ret; ++ } + + snd_soc_component_update_bits(component, CTRL1, FMT_MASK, priv->ctrl1); + snd_soc_component_update_bits(component, CTRL2, DFS_MASK, ctrl2); +@@ -645,10 +646,6 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + snd_soc_component_update_bits(component, ICTRL, ICTRL_MASK, priv->ic); + snd_soc_component_update_bits(component, OCTRL, OCTRL_MASK, priv->oc); + +-hw_params_end: +- if (ret < 0) +- dev_warn(dev, "unsupported data width/format combination\n"); +- + return ret; + } + +diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c +--- a/sound/soc/codecs/arizona-jack.c ++++ b/sound/soc/codecs/arizona-jack.c +@@ -5,6 +5,7 @@ + * Copyright (C) 2012-2014 Wolfson Microelectronics plc + */ + ++#include + #include + #include + #include +@@ -707,15 +708,13 @@ static void arizona_micd_timeout_work(struct work_struct *work) + struct arizona_priv, + micd_timeout_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + dev_dbg(info->arizona->dev, "MICD timed out, reporting HP\n"); + + info->detecting = false; + + arizona_identify_headphone(info); +- +- mutex_unlock(&info->lock); + } + + static int arizona_micd_adc_read(struct arizona_priv *info) +@@ -921,12 +920,11 @@ static void arizona_micd_detect(struct work_struct *work) + + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + /* If the cable was removed while measuring ignore the result */ + if (!(info->jack->status & SND_JACK_MECHANICAL)) { + dev_dbg(arizona->dev, "Ignoring MICDET for removed cable\n"); +- mutex_unlock(&info->lock); + return; + } + +@@ -936,7 +934,6 @@ static void arizona_micd_detect(struct work_struct *work) + arizona_button_reading(info); + + pm_runtime_mark_last_busy(arizona->dev); +- mutex_unlock(&info->lock); + } + + static irqreturn_t arizona_micdet(int irq, void *data) +@@ -948,10 +945,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) + cancel_delayed_work_sync(&info->micd_detect_work); + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); +- if (!info->detecting) +- debounce = 0; +- mutex_unlock(&info->lock); ++ scoped_guard(mutex, &info->lock) { ++ if (!info->detecting) ++ debounce = 0; ++ } + + if (debounce) + queue_delayed_work(system_power_efficient_wq, +@@ -969,9 +966,8 @@ static void arizona_hpdet_work(struct work_struct *work) + struct arizona_priv, + hpdet_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + arizona_start_hpdet_acc_id(info); +- mutex_unlock(&info->lock); + } + + static int arizona_hpdet_wait(struct arizona_priv *info) +@@ -1018,9 +1014,9 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work); + cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work); + +- pm_runtime_get_sync(arizona->dev); ++ guard(pm_runtime_active_auto)(arizona->dev); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + if (info->micd_clamp) { + mask = ARIZONA_MICD_CLAMP_STS; +@@ -1036,8 +1032,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); + if (ret) { + dev_err(arizona->dev, "Failed to read jackdet status: %d\n", ret); +- mutex_unlock(&info->lock); +- pm_runtime_put_autosuspend(arizona->dev); + return IRQ_NONE; + } + +@@ -1056,62 +1050,61 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + &info->micd_timeout_work, + msecs_to_jiffies(micd_timeout)); + } ++ } else { ++ info->last_jackdet = val; + +- goto out; +- } +- info->last_jackdet = val; ++ if (info->last_jackdet == present) { ++ dev_dbg(arizona->dev, "Detected jack\n"); ++ snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); + +- if (info->last_jackdet == present) { +- dev_dbg(arizona->dev, "Detected jack\n"); +- snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); ++ info->detecting = true; ++ info->mic = false; ++ info->jack_flips = 0; + +- info->detecting = true; +- info->mic = false; +- info->jack_flips = 0; ++ if (!arizona->pdata.hpdet_acc_id) { ++ arizona_start_mic(info); ++ } else { ++ queue_delayed_work(system_power_efficient_wq, ++ &info->hpdet_work, ++ msecs_to_jiffies(HPDET_DEBOUNCE)); ++ } + +- if (!arizona->pdata.hpdet_acc_id) { +- arizona_start_mic(info); ++ if (info->micd_clamp || !arizona->pdata.jd_invert) ++ regmap_update_bits(arizona->regmap, ++ ARIZONA_JACK_DETECT_DEBOUNCE, ++ ARIZONA_MICD_CLAMP_DB | ++ ARIZONA_JD1_DB, 0); + } else { +- queue_delayed_work(system_power_efficient_wq, +- &info->hpdet_work, +- msecs_to_jiffies(HPDET_DEBOUNCE)); +- } ++ dev_dbg(arizona->dev, "Detected jack removal\n"); ++ ++ arizona_stop_mic(info); ++ ++ info->num_hpdet_res = 0; ++ for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) ++ info->hpdet_res[i] = 0; ++ info->mic = false; ++ info->hpdet_done = false; ++ info->hpdet_retried = false; ++ ++ snd_soc_jack_report(info->jack, 0, ++ ARIZONA_JACK_MASK | info->micd_button_mask); ++ ++ /* ++ * If the jack was removed during a headphone detection we ++ * need to wait for the headphone detection to finish, as ++ * it can not be aborted. We don't want to be able to start ++ * a new headphone detection from a fresh insert until this ++ * one is finished. ++ */ ++ arizona_hpdet_wait(info); + +- if (info->micd_clamp || !arizona->pdata.jd_invert) + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | +- ARIZONA_JD1_DB, 0); +- } else { +- dev_dbg(arizona->dev, "Detected jack removal\n"); +- +- arizona_stop_mic(info); +- +- info->num_hpdet_res = 0; +- for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) +- info->hpdet_res[i] = 0; +- info->mic = false; +- info->hpdet_done = false; +- info->hpdet_retried = false; +- +- snd_soc_jack_report(info->jack, 0, ARIZONA_JACK_MASK | info->micd_button_mask); +- +- /* +- * If the jack was removed during a headphone detection we +- * need to wait for the headphone detection to finish, as +- * it can not be aborted. We don't want to be able to start +- * a new headphone detection from a fresh insert until this +- * one is finished. +- */ +- arizona_hpdet_wait(info); +- +- regmap_update_bits(arizona->regmap, +- ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ } + } + +-out: + /* Clear trig_sts to make sure DCVDD is not forced up */ + regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, + ARIZONA_MICD_CLAMP_FALL_TRIG_STS | +@@ -1119,10 +1112,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ARIZONA_JD1_FALL_TRIG_STS | + ARIZONA_JD1_RISE_TRIG_STS); + +- mutex_unlock(&info->lock); +- +- pm_runtime_put_autosuspend(arizona->dev); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c +--- a/sound/soc/codecs/arizona.c ++++ b/sound/soc/codecs/arizona.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -1158,17 +1159,16 @@ int arizona_dvfs_up(struct snd_soc_component *component, unsigned int flags) + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + if (!priv->dvfs_cached && !priv->dvfs_reqs) { + ret = arizona_dvfs_enable(component); + if (ret) +- goto err; ++ return ret; + } + + priv->dvfs_reqs |= flags; +-err: +- mutex_unlock(&priv->dvfs_lock); ++ + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_up); +@@ -1179,7 +1179,7 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + unsigned int old_reqs; + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + old_reqs = priv->dvfs_reqs; + priv->dvfs_reqs &= ~flags; +@@ -1187,7 +1187,6 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + if (!priv->dvfs_cached && old_reqs && !priv->dvfs_reqs) + ret = arizona_dvfs_disable(component); + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_down); +@@ -1199,7 +1198,7 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +@@ -1222,7 +1221,6 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_sysclk_ev); +@@ -1657,13 +1655,11 @@ static void arizona_wm5102_set_dac_comp(struct snd_soc_component *component, + { 0x80, 0x0 }, + }; + +- mutex_lock(&arizona->dac_comp_lock); +- +- dac_comp[1].def = arizona->dac_comp_coeff; +- if (rate >= 176400) +- dac_comp[2].def = arizona->dac_comp_enabled; +- +- mutex_unlock(&arizona->dac_comp_lock); ++ scoped_guard(mutex, &arizona->dac_comp_lock) { ++ dac_comp[1].def = arizona->dac_comp_coeff; ++ if (rate >= 176400) ++ dac_comp[2].def = arizona->dac_comp_enabled; ++ } + + regmap_multi_reg_write(arizona->regmap, + dac_comp, +diff --git a/sound/soc/codecs/aw87390.c b/sound/soc/codecs/aw87390.c +--- a/sound/soc/codecs/aw87390.c ++++ b/sound/soc/codecs/aw87390.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -225,11 +226,10 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + struct aw87390 *aw87390 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + ret = aw87390_dev_set_profile_index(aw87390->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change\n"); +- mutex_unlock(&aw87390->lock); + return 0; + } + +@@ -238,8 +238,6 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + aw87390_power_on(aw87390->aw_pa); + } + +- mutex_unlock(&aw87390->lock); +- + return 1; + } + +@@ -250,7 +248,7 @@ static const struct snd_kcontrol_new aw87390_controls[] = { + + static int aw87390_request_firmware_file(struct aw87390 *aw87390) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + int ret; + + aw87390->aw_pa->fw_status = AW87390_DEV_FW_FAILED; +@@ -265,14 +263,11 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + + aw87390->aw_cfg = devm_kzalloc(aw87390->aw_pa->dev, + struct_size(aw87390->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw87390->aw_cfg) { +- release_firmware(cont); ++ if (!aw87390->aw_cfg) + return -ENOMEM; +- } + + aw87390->aw_cfg->len = cont->size; + memcpy(aw87390->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) { +@@ -280,14 +275,12 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + return ret; + } + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + + ret = aw88395_dev_cfg_load(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) + dev_err(aw87390->aw_pa->dev, "aw_dev acf parse failed\n"); + +- mutex_unlock(&aw87390->lock); +- + return ret; + } + +diff --git a/sound/soc/codecs/aw88081.c b/sound/soc/codecs/aw88081.c +--- a/sound/soc/codecs/aw88081.c ++++ b/sound/soc/codecs/aw88081.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -763,9 +764,8 @@ static void aw88081_startup_work(struct work_struct *work) + struct aw88081 *aw88081 = + container_of(work, struct aw88081, start_work.work); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + aw88081_start_pa(aw88081); +- mutex_unlock(&aw88081->lock); + } + + static void aw88081_start(struct aw88081 *aw88081, bool sync_start) +@@ -942,11 +942,10 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + ret = aw88081_dev_set_profile_index(aw88081->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88081->lock); + return 0; + } + +@@ -955,8 +954,6 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + aw88081_start(aw88081, AW88081_SYNC_START); + } + +- mutex_unlock(&aw88081->lock); +- + return 1; + } + +@@ -1136,7 +1133,7 @@ static int aw88081_dev_init(struct aw88081 *aw88081, struct aw_container *aw_cfg + + static int aw88081_request_firmware_file(struct aw88081 *aw88081) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -1150,26 +1147,21 @@ static int aw88081_request_firmware_file(struct aw88081 *aw88081) + AW88081_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88081->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); + + aw88081->aw_cfg = aw_cfg; + +- release_firmware(cont); +- + ret = aw88395_dev_load_acf_check(aw88081->aw_pa, aw88081->aw_cfg); + if (ret) + return ret; + +- mutex_lock(&aw88081->lock); +- ret = aw88081_dev_init(aw88081, aw88081->aw_cfg); +- mutex_unlock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + +- return ret; ++ return aw88081_dev_init(aw88081, aw88081->aw_cfg); + } + + static int aw88081_playback_event(struct snd_soc_dapm_widget *w, +@@ -1178,7 +1170,7 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88081 *aw88081 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88081_start(aw88081, AW88081_ASYNC_START); +@@ -1189,7 +1181,6 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88081->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88166.c b/sound/soc/codecs/aw88166.c +--- a/sound/soc/codecs/aw88166.c ++++ b/sound/soc/codecs/aw88166.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -1173,9 +1174,8 @@ static void aw88166_startup_work(struct work_struct *work) + struct aw88166 *aw88166 = + container_of(work, struct aw88166, start_work.work); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + aw88166_start_pa(aw88166); +- mutex_unlock(&aw88166->lock); + } + + static void aw88166_start(struct aw88166 *aw88166, bool sync_start) +@@ -1413,11 +1413,10 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + ret = aw88166_dev_set_profile_index(aw88166->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88166->lock); + return 0; + } + +@@ -1426,8 +1425,6 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + aw88166_start(aw88166, AW88166_SYNC_START); + } + +- mutex_unlock(&aw88166->lock); +- + return 1; + } + +@@ -1573,7 +1570,7 @@ static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg + + static int aw88166_request_firmware_file(struct aw88166 *aw88166) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + const char *fw_name; + int ret; + +@@ -1593,13 +1590,11 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + + aw88166->aw_cfg = devm_kzalloc(aw88166->aw_pa->dev, + struct_size(aw88166->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88166->aw_cfg) { +- release_firmware(cont); ++ if (!aw88166->aw_cfg) + return -ENOMEM; +- } ++ + aw88166->aw_cfg->len = (int)cont->size; + memcpy(aw88166->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88166->aw_pa, aw88166->aw_cfg); + if (ret) { +@@ -1607,12 +1602,12 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + return ret; + } + +- mutex_lock(&aw88166->lock); +- /* aw device init */ +- ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); +- if (ret) +- dev_err(aw88166->aw_pa->dev, "dev init failed\n"); +- mutex_unlock(&aw88166->lock); ++ scoped_guard(mutex, &aw88166->lock) { ++ /* aw device init */ ++ ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); ++ if (ret) ++ dev_err(aw88166->aw_pa->dev, "dev init failed\n"); ++ } + + return ret; + } +@@ -1639,7 +1634,7 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88166_start(aw88166, AW88166_ASYNC_START); +@@ -1650,7 +1645,6 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88166->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c +--- a/sound/soc/codecs/aw88261.c ++++ b/sound/soc/codecs/aw88261.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -960,11 +961,10 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + ret = aw88261_dev_set_profile_index(aw88261->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88261->lock); + return 0; + } + +@@ -973,8 +973,6 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + aw88261_start(aw88261); + } + +- mutex_unlock(&aw88261->lock); +- + return 1; + } + +@@ -1038,7 +1036,7 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88261_start(aw88261); +@@ -1049,7 +1047,6 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88261->lock); + + return 0; + } +@@ -1152,7 +1149,7 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg + + static int aw88261_request_firmware_file(struct aw88261 *aw88261) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + const char *fw_name; + int ret; +@@ -1172,13 +1169,11 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + fw_name, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88261->aw_cfg = aw_cfg; + +@@ -1188,12 +1183,12 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + return ret; + } + +- mutex_lock(&aw88261->lock); +- /* aw device init */ +- ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); +- if (ret) +- dev_err(aw88261->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88261->lock); ++ scoped_guard(mutex, &aw88261->lock) { ++ /* aw device init */ ++ ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); ++ if (ret) ++ dev_err(aw88261->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395.c b/sound/soc/codecs/aw88395/aw88395.c +--- a/sound/soc/codecs/aw88395/aw88395.c ++++ b/sound/soc/codecs/aw88395/aw88395.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -51,9 +52,8 @@ static void aw88395_startup_work(struct work_struct *work) + struct aw88395 *aw88395 = + container_of(work, struct aw88395, start_work.work); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + aw88395_start_pa(aw88395); +- mutex_unlock(&aw88395->lock); + } + + static void aw88395_start(struct aw88395 *aw88395, bool sync_start) +@@ -224,11 +224,10 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + ret = aw88395_dev_set_profile_index(aw88395->aw_pa, ucontrol->value.integer.value[0]); + if (ret < 0) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88395->lock); + return 0; + } + +@@ -237,8 +236,6 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + aw88395_start(aw88395, AW88395_SYNC_START); + } + +- mutex_unlock(&aw88395->lock); +- + return 1; + } + +@@ -366,7 +363,7 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88395 *aw88395 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88395_start(aw88395, AW88395_ASYNC_START); +@@ -377,7 +374,6 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88395->lock); + + return 0; + } +@@ -461,7 +457,7 @@ static void aw88395_hw_reset(struct aw88395 *aw88395) + + static int aw88395_request_firmware_file(struct aw88395 *aw88395) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -477,13 +473,11 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + AW88395_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88395->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88395->aw_cfg = aw_cfg; + +@@ -495,12 +489,12 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + + dev_dbg(aw88395->aw_pa->dev, "%s : bin load success\n", __func__); + +- mutex_lock(&aw88395->lock); +- /* aw device init */ +- ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); +- if (ret < 0) +- dev_err(aw88395->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88395->lock); ++ scoped_guard(mutex, &aw88395->lock) { ++ /* aw device init */ ++ ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); ++ if (ret < 0) ++ dev_err(aw88395->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395_device.c b/sound/soc/codecs/aw88395/aw88395_device.c +--- a/sound/soc/codecs/aw88395/aw88395_device.c ++++ b/sound/soc/codecs/aw88395/aw88395_device.c +@@ -8,6 +8,7 @@ + // Author: Ben Yi + // + ++#include + #include + #include + #include +@@ -70,7 +71,7 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_write_16bit(aw_dev, dsp_addr, dsp_data); +@@ -93,7 +94,6 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -156,7 +156,7 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_read_16bit(aw_dev, dsp_addr, dsp_data); +@@ -179,7 +179,6 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -1110,42 +1109,36 @@ static int aw_dev_dsp_update_container(struct aw_device *aw_dev, + #ifdef AW88395_DSP_I2C_WRITES + u32 tmp_len; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + + for (i = 0; i < len; i += AW88395_MAX_RAM_WRITE_BYTE_SIZE) { + tmp_len = min(len - i, AW88395_MAX_RAM_WRITE_BYTE_SIZE); + ret = regmap_raw_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + &data[i], tmp_len); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #else + __be16 reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* i2c write */ + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + for (i = 0; i < len; i += 2) { + reg_val = cpu_to_be16p((u16 *)(data + i)); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + (u16)reg_val); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #endif + + return 0; +- +-error_operation: +- mutex_unlock(&aw_dev->dsp_lock); +- return ret; + } + + static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +@@ -1231,14 +1224,14 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + { + unsigned int reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* check the odd bits of reg 0x40 */ + regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, AW88395_DSP_ODD_NUM_BIT_TEST); + regmap_read(aw_dev->regmap, AW88395_DSPMADD_REG, ®_val); + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 odd bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check the even bits of reg 0x40 */ +@@ -1247,7 +1240,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 even bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_fw_base_addr */ +@@ -1256,7 +1249,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp fw addr failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_cfg_base_addr */ +@@ -1265,15 +1258,10 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } +- mutex_unlock(&aw_dev->dsp_lock); + + return 0; +- +-error: +- mutex_unlock(&aw_dev->dsp_lock); +- return -EPERM; + } + + int aw88395_dev_fw_update(struct aw_device *aw_dev, bool up_dsp_fw_en, bool force_up_en) +diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c +--- a/sound/soc/codecs/aw88399-lib.c ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -1282,7 +1282,7 @@ static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg + + int aw88399_request_firmware_file(struct aw88399 *aw88399) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + int ret; + + aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +@@ -1298,13 +1298,11 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399) + + aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, + struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); ++ if (!aw88399->aw_cfg) + return -ENOMEM; +- } ++ + aw88399->aw_cfg->len = (int)cont->size; + memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); + if (ret) { +diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c +--- a/sound/soc/codecs/aw88399.c ++++ b/sound/soc/codecs/aw88399.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -497,11 +498,10 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + ret = aw88399_dev_set_profile_index(aw88399->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88399->lock); + return 0; + } + +@@ -510,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + aw88399_start(aw88399, AW88399_SYNC_START); + } + +- mutex_unlock(&aw88399->lock); +- + return 1; + } + +@@ -690,7 +688,7 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88399_start(aw88399, AW88399_ASYNC_START); +@@ -701,7 +699,6 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88399->lock); + + return 0; + } +diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c +--- a/sound/soc/codecs/bt-sco.c ++++ b/sound/soc/codecs/bt-sco.c +@@ -85,13 +85,9 @@ static int bt_sco_probe(struct platform_device *pdev) + } + + static const struct platform_device_id bt_sco_driver_ids[] = { +- { +- .name = "dfbmcs320", +- }, +- { +- .name = "bt-sco", +- }, +- {}, ++ { .name = "dfbmcs320" }, ++ { .name = "bt-sco" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids); + +diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c +--- a/sound/soc/codecs/cpcap.c ++++ b/sound/soc/codecs/cpcap.c +@@ -26,14 +26,6 @@ + /* Register 9 - CPCAP_REG_INTS2 --- Interrupt Sense 2 */ + #define CPCAP_BIT_PTT_S 11 /* Push To Talk */ + +-/* Register 512 CPCAP_REG_VAUDIOC --- Audio Regulator and Bias Voltage */ +-#define CPCAP_BIT_AUDIO_LOW_PWR 6 +-#define CPCAP_BIT_AUD_LOWPWR_SPEED 5 +-#define CPCAP_BIT_VAUDIOPRISTBY 4 +-#define CPCAP_BIT_VAUDIO_MODE1 2 +-#define CPCAP_BIT_VAUDIO_MODE0 1 +-#define CPCAP_BIT_V_AUDIO_EN 0 +- + /* Register 513 CPCAP_REG_CC --- CODEC */ + #define CPCAP_BIT_CDC_CLK2 15 + #define CPCAP_BIT_CDC_CLK1 14 +@@ -239,7 +231,6 @@ struct cpcap_reg_info { + }; + + static const struct cpcap_reg_info cpcap_default_regs[] = { +- { CPCAP_REG_VAUDIOC, 0x003F, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, +@@ -1265,6 +1256,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component = dai->component; + struct device *dev = component->dev; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1298,7 +1290,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + return err; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, rtd->dai_link->dai_fmt); + } + + static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, +@@ -1391,121 +1383,8 @@ static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + return 0; + } + +- +-/* +- * Configure codec for voice call if requested. +- * +- * We can configure most with snd_soc_dai_set_sysclk(), snd_soc_dai_set_fmt() +- * and snd_soc_dai_set_tdm_slot(). This function configures the rest of the +- * cpcap related hardware as CPU is not involved in the voice call. +- */ +-static int cpcap_voice_call(struct cpcap_audio *cpcap, struct snd_soc_dai *dai, +- bool voice_call) +-{ +- int mask, err; +- +- /* Modem to codec VAUDIO_MODE1 */ +- mask = BIT(CPCAP_BIT_VAUDIO_MODE1); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Clear MIC1_MUX for call */ +- mask = BIT(CPCAP_BIT_MIC1_MUX); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? 0 : mask); +- if (err) +- return err; +- +- /* Set MIC2_MUX for call */ +- mask = BIT(CPCAP_BIT_MB_ON1L) | BIT(CPCAP_BIT_MB_ON1R) | +- BIT(CPCAP_BIT_MIC2_MUX) | BIT(CPCAP_BIT_MIC2_PGA_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable LDSP for call */ +- mask = BIT(CPCAP_BIT_A2_LDSP_L_EN) | BIT(CPCAP_BIT_A2_LDSP_R_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable CPCAP_BIT_PGA_CDC_EN for call */ +- mask = BIT(CPCAP_BIT_PGA_CDC_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Unmute voice for call */ +- if (dai) { +- err = snd_soc_dai_digital_mute(dai, !voice_call, +- SNDRV_PCM_STREAM_PLAYBACK); +- if (err) +- return err; +- } +- +- /* Set modem to codec mic CDC and HPF for call */ +- mask = BIT(CPCAP_BIT_MIC2_CDC_EN) | BIT(CPCAP_BIT_CDC_EN_RX) | +- BIT(CPCAP_BIT_AUDOHPF_1) | BIT(CPCAP_BIT_AUDOHPF_0) | +- BIT(CPCAP_BIT_AUDIHPF_1) | BIT(CPCAP_BIT_AUDIHPF_0); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable modem to codec CDC for call*/ +- mask = BIT(CPCAP_BIT_CDC_CLK_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- mask, voice_call ? mask : 0); +- +- return err; +-} +- +-static int cpcap_voice_set_tdm_slot(struct snd_soc_dai *dai, +- unsigned int tx_mask, unsigned int rx_mask, +- int slots, int slot_width) +-{ +- struct snd_soc_component *component = dai->component; +- struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +- int err, ts_mask, mask; +- bool voice_call; +- +- /* +- * Primitive test for voice call, probably needs more checks +- * later on for 16-bit calls detected, Bluetooth headset etc. +- */ +- if (tx_mask == 0 && rx_mask == 1 && slot_width == 8) +- voice_call = true; +- else +- voice_call = false; +- +- ts_mask = 0x7 << CPCAP_BIT_MIC2_TIMESLOT0; +- ts_mask |= 0x7 << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- mask = (tx_mask & 0x7) << CPCAP_BIT_MIC2_TIMESLOT0; +- mask |= (rx_mask & 0x7) << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- ts_mask, mask); +- if (err) +- return err; +- +- err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, slot_width * 1000); +- if (err) +- return err; +- +- err = cpcap_voice_call(cpcap, dai, voice_call); +- if (err) +- return err; +- +- return 0; +-} +- +-static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute, int direction) ++static int cpcap_voice_set_mute(struct snd_soc_dai *dai, ++ int mute, int direction) + { + struct snd_soc_component *component = dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1526,7 +1405,6 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { + .hw_params = cpcap_voice_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, +- .set_tdm_slot = cpcap_voice_set_tdm_slot, + .mute_stream = cpcap_voice_set_mute, + .no_capture_mute = 1, + }; +diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c +--- a/sound/soc/codecs/cros_ec_codec.c ++++ b/sound/soc/codecs/cros_ec_codec.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -319,10 +320,18 @@ static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + (uint8_t *)&p, sizeof(p), NULL, 0); + } + ++static const u64 i2s_rx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops i2s_rx_dai_ops = { + .hw_params = i2s_rx_hw_params, + .set_fmt = i2s_rx_set_fmt, + .set_bclk_ratio = i2s_rx_set_bclk_ratio, ++ .auto_selectable_formats = &i2s_rx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static int i2s_rx_event(struct snd_soc_dapm_widget *w, +@@ -608,10 +617,10 @@ static void wov_copy_work(struct work_struct *w) + container_of(w, struct cros_ec_codec_priv, wov_copy_work.work); + int ret; + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + if (!priv->wov_substream) { + dev_warn(priv->dev, "no pcm substream\n"); +- goto leave; ++ return; + } + + if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) +@@ -624,8 +633,6 @@ static void wov_copy_work(struct work_struct *w) + msecs_to_jiffies(10)); + else if (ret) + dev_err(priv->dev, "failed to read audio data\n"); +-leave: +- mutex_unlock(&priv->wov_dma_lock); + } + + static int wov_enable_get(struct snd_kcontrol *kcontrol, +@@ -895,12 +902,11 @@ static int wov_pcm_hw_params(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + priv->wov_substream = substream; + priv->wov_rp = priv->wov_wp = 0; + priv->wov_dma_offset = 0; + priv->wov_burst_read = true; +- mutex_unlock(&priv->wov_dma_lock); + + return 0; + } +@@ -911,10 +917,10 @@ static int wov_pcm_hw_free(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); +- wov_queue_dequeue(priv, wov_queue_size(priv)); +- priv->wov_substream = NULL; +- mutex_unlock(&priv->wov_dma_lock); ++ scoped_guard(mutex, &priv->wov_dma_lock) { ++ wov_queue_dequeue(priv, wov_queue_size(priv)); ++ priv->wov_substream = NULL; ++ } + + cancel_delayed_work_sync(&priv->wov_copy_work); + +diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c +--- a/sound/soc/codecs/cs-amp-lib.c ++++ b/sound/soc/codecs/cs-amp-lib.c +@@ -7,6 +7,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -83,10 +84,12 @@ static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, + KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val); + + if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { +- mutex_lock(&dsp->pwr_lock); +- cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); +- ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); +- mutex_unlock(&dsp->pwr_lock); ++ scoped_guard(mutex, &dsp->pwr_lock) { ++ cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, ++ controls->mem_region, ++ controls->alg_id); ++ ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); ++ } + + if (ret < 0) { + dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); +diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c +--- a/sound/soc/codecs/cs35l32.c ++++ b/sound/soc/codecs/cs35l32.c +@@ -538,7 +538,15 @@ static int cs35l32_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); + + regcache_cache_only(cs35l32->regmap, false); +- regcache_sync(cs35l32->regmap); ++ ret = regcache_sync(cs35l32->regmap); ++ if (ret) { ++ regcache_cache_only(cs35l32->regmap, true); ++ regcache_mark_dirty(cs35l32->regmap); ++ gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); ++ regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), ++ cs35l32->supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -231,7 +231,7 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + * a soft reset. + */ + if (cs35l56->base.init_done) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + out: + pm_runtime_put_autosuspend(cs35l56->base.dev); +@@ -240,47 +240,17 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral, + struct sdw_slave_intr_status *status) + { +- struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +- +- /* SoundWire core holds our pm_runtime when calling this function. */ +- +- dev_dbg(cs35l56->base.dev, "int control_port=%#x\n", status->control_port); +- +- if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0) +- return 0; +- + /* +- * Prevent bus manager suspending and possibly issuing a +- * bus-reset before the queued work has run. ++ * The IRQ itself was handled through the regmap_irq handler, this is ++ * just clearing up the additional Cirrus SoundWire registers that are ++ * not covered by the SoundWire framework or the IRQ handler itself. + */ +- pm_runtime_get_noresume(cs35l56->base.dev); +- +- /* +- * Mask and clear until it has been handled. +- * None of the interrupts are time-critical so use the +- * power-efficient queue. +- */ +- cs35l56_mask_soundwire_interrupts(peripheral); +- queue_work(system_power_efficient_wq, &cs35l56->sdw_irq_work); ++ sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + + return 0; + } + +-static void cs35l56_sdw_irq_work(struct work_struct *work) +-{ +- struct cs35l56_private *cs35l56 = container_of(work, +- struct cs35l56_private, +- sdw_irq_work); +- +- cs35l56_irq(-1, &cs35l56->base); +- +- /* unmask interrupts */ +- if (!cs35l56->sdw_irq_no_unmask) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); +- +- pm_runtime_put_autosuspend(cs35l56->base.dev); +-} +- + static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +@@ -302,6 +272,7 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); + prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); + prop->paging_support = true; ++ prop->use_domain_irq = true; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | SDW_SCP_INT1_IMPL_DEF; + +@@ -406,7 +377,7 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev) + if (ret) + return ret; + +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + return 0; + } +@@ -418,21 +389,12 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + if (!cs35l56->base.init_done) + return 0; + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ /* runtime_resume unmasks the interrupt */ ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } + +-static int __maybe_unused cs35l56_sdw_system_resume(struct device *dev) +-{ +- struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); +- +- cs35l56->sdw_irq_no_unmask = false; +- /* runtime_resume re-enables the interrupt */ +- +- return cs35l56_system_resume(dev); +-} +- + static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_device_id *id) + { + struct device *dev = &peripheral->dev; +@@ -447,7 +409,6 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + cs35l56->base.dev = dev; + cs35l56->sdw_peripheral = peripheral; + cs35l56->sdw_link_num = peripheral->bus->link_id; +- INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work); + + dev_set_drvdata(dev, cs35l56); + +@@ -457,6 +418,7 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + regmap_config = &cs35l56_regmap_sdw; + break; + case 0x3563: ++ case 0x3562: + regmap_config = &cs35l63_regmap_sdw; + break; + default: +@@ -483,21 +445,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + /* Start in cache-only until device is enumerated */ + regcache_cache_only(cs35l56->base.regmap, true); + +- return cs35l56_common_probe(cs35l56, -EINVAL); ++ return cs35l56_common_probe(cs35l56, peripheral->irq); + } + + static void cs35l56_sdw_remove(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + cs35l56_remove(cs35l56); + } + + static const struct dev_pm_ops cs35l56_sdw_pm = { + SET_RUNTIME_PM_OPS(cs35l56_sdw_runtime_suspend, cs35l56_sdw_runtime_resume, NULL) +- SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_sdw_system_resume) ++ SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_system_resume) + LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early) + /* NOIRQ stage not needed, SoundWire doesn't use a hard IRQ */ + }; +@@ -505,6 +467,7 @@ static const struct dev_pm_ops cs35l56_sdw_pm = { + static const struct sdw_device_id cs35l56_sdw_id[] = { + SDW_SLAVE_ENTRY(0x01FA, 0x3556, 0x3556), + SDW_SLAVE_ENTRY(0x01FA, 0x3557, 0x3557), ++ SDW_SLAVE_ENTRY(0x01FA, 0x3562, 0x3562), + SDW_SLAVE_ENTRY(0x01FA, 0x3563, 0x3563), + {}, + }; +diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c +--- a/sound/soc/codecs/cs35l56-shared.c ++++ b/sound/soc/codecs/cs35l56-shared.c +@@ -96,6 +96,7 @@ int cs35l56_set_patch(struct cs35l56_base *cs35l56_base) + ARRAY_SIZE(cs35l56_patch_fw)); + break; + case 0x63: ++ case 0x62: + ret = regmap_register_patch(cs35l56_base->regmap, cs35l63_patch_fw, + ARRAY_SIZE(cs35l63_patch_fw)); + break; +@@ -389,6 +390,7 @@ static void cs35l56_set_fw_reg_table(struct cs35l56_base *cs35l56_base) + } + break; + case 0x63: ++ case 0x62: + cs35l56_base->fw_reg = &cs35l63_fw_reg; + break; + } +@@ -595,6 +597,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + break; + case 0x63: ++ case 0x62: + regmap_multi_reg_write_bypassed(cs35l56_base->regmap, + cs35l63_system_reset_seq, + ARRAY_SIZE(cs35l63_system_reset_seq)); +@@ -613,26 +616,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, "SND_SOC_CS35L56_SHARED"); + +-int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) +-{ +- int ret; +- +- if (irq < 1) +- return 0; +- +- ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, +- IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, +- "cs35l56", cs35l56_base); +- if (!ret) +- cs35l56_base->irq = irq; +- else +- dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); +- +- return ret; +-} +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); +- +-irqreturn_t cs35l56_irq(int irq, void *data) ++static irqreturn_t cs35l56_irq(int irq, void *data) + { + struct cs35l56_base *cs35l56_base = data; + unsigned int status1 = 0, status8 = 0, status20 = 0; +@@ -640,23 +624,22 @@ irqreturn_t cs35l56_irq(int irq, void *data) + unsigned int val; + int rv; + +- irqreturn_t ret = IRQ_NONE; +- + if (!cs35l56_base->init_done) + return IRQ_NONE; + +- mutex_lock(&cs35l56_base->irq_lock); ++ guard(mutex)(&cs35l56_base->irq_lock); + +- rv = pm_runtime_resume_and_get(cs35l56_base->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED(cs35l56_base->dev, pm); ++ rv = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (rv < 0) { + dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv); +- goto err_unlock; ++ return IRQ_NONE; + } + + regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val); + if ((val & CS35L56_IRQ1_STS_MASK) == 0) { + dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n"); +- goto err; ++ return IRQ_NONE; + } + + /* Ack interrupts */ +@@ -680,7 +663,7 @@ irqreturn_t cs35l56_irq(int irq, void *data) + + /* Check to see if unmasked bits are active */ + if (!status1 && !status8 && !status20) +- goto err; ++ return IRQ_NONE; + + if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Amp short error\n"); +@@ -688,16 +671,27 @@ irqreturn_t cs35l56_irq(int irq, void *data) + if (status8 & CS35L56_TEMP_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Overtemp error\n"); + +- ret = IRQ_HANDLED; ++ return IRQ_HANDLED; ++} + +-err: +- pm_runtime_put(cs35l56_base->dev); +-err_unlock: +- mutex_unlock(&cs35l56_base->irq_lock); ++int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) ++{ ++ int ret; ++ ++ if (irq < 1) ++ return 0; ++ ++ ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, ++ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, ++ "cs35l56", cs35l56_base); ++ if (!ret) ++ cs35l56_base->irq = irq; ++ else ++ dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); + + return ret; + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq, "SND_SOC_CS35L56_SHARED"); ++EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); + + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base) + { +@@ -816,14 +810,10 @@ int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_sou + if (!cs35l56_base->init_done) + return 0; + +- if (!cs35l56_base->can_hibernate) +- goto out_sync; +- +- /* Must be done before releasing cache-only */ +- if (!is_soundwire) ++ /* Hibernate wake must be done before releasing cache-only */ ++ if (cs35l56_base->can_hibernate && !is_soundwire) + cs35l56_issue_wake_event(cs35l56_base); + +-out_sync: + ret = cs35l56_wait_for_firmware_boot(cs35l56_base); + if (ret) { + dev_err(cs35l56_base->dev, "Hibernate wake failed: %d\n", ret); +@@ -1470,6 +1460,7 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base) + cs35l56_base->calibration_controls = &cs35l56_calibration_controls; + break; + case 0x35A630: ++ case 0x35A620: + cs35l56_base->calibration_controls = &cs35l63_calibration_controls; + devid = devid >> 4; + break; +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -18,11 +18,9 @@ + #include + #include + #include +-#include + #include + #include + #include +-#include + #include + #include + #include +@@ -39,55 +37,49 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + +-/* +- * snd_soc_register_component() can call component_probe() on all instances +- * in a card, so deferred registration must be protected across all instances. +- */ +-static DEFINE_MUTEX(cs35l56_component_register_lock); +-static bool cs35l56_shutting_down; +- +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* ++ * Mask unconditionally. ++ * + * The read of GEN_INT_STAT_1 is required as per the SoundWire spec + * for interrupt status bits to clear. + * GEN_INT_MASK_1 masks the _inputs_ to GEN_INT_STAT1. + */ +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); +- sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); ++ sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_mask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, CS35L56_SDW_INT_MASK_CODEC_IRQ); ++ if (!cs35l56->base.irq) ++ return; ++ ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, ++ CS35L56_SDW_INT_MASK_CODEC_IRQ); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_unmask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) + { + if (!cs35l56->sdw_peripheral) + return; + +- cs35l56->sdw_irq_no_unmask = true; +- flush_work(&cs35l56->sdw_irq_work); +- +- /* Mask interrupts and flush in case sdw_irq_work was queued again */ +- cs35l56_mask_soundwire_interrupts(cs35l56->sdw_peripheral); +- flush_work(&cs35l56->sdw_irq_work); ++ cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->base.irq) ++ disable_irq(cs35l56->base.irq); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_disable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) + { +- if (!cs35l56->sdw_peripheral) ++ if (!cs35l56->sdw_peripheral || !cs35l56->base.irq) + return; + +- cs35l56->sdw_irq_no_unmask = false; +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ enable_irq(cs35l56->base.irq); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_enable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + + static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +@@ -837,11 +829,7 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing + { + int ret; + +- /* +- * Disable SoundWire interrupts to prevent race with IRQ work. +- * Setting sdw_irq_no_unmask prevents the handler re-enabling +- * the SoundWire interrupt. +- */ ++ /* Disable SoundWire interrupts to prevent race with IRQ handler thread */ + cs35l56_disable_sdw_interrupts(cs35l56); + + ret = cs35l56_firmware_shutdown(&cs35l56->base); +@@ -1405,6 +1393,7 @@ static int _cs35l56_component_probe(struct snd_soc_component *component) + ARRAY_SIZE(cs35l56_controls)); + break; + case 0x63: ++ case 0x62: + ret = snd_soc_add_component_controls(component, cs35l63_controls, + ARRAY_SIZE(cs35l63_controls)); + break; +@@ -1968,11 +1957,6 @@ static void cs35l56_component_register_work(struct work_struct *work) + component_register_work); + int ret; + +- guard(mutex)(&cs35l56_component_register_lock); +- +- if (cs35l56_shutting_down) +- return; +- + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { +@@ -2233,37 +2217,6 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { + }; + #endif + +-static int cs35l56_reboot_notify(struct notifier_block *nb, +- unsigned long action, void *data) +-{ +- guard(mutex)(&cs35l56_component_register_lock); +- cs35l56_shutting_down = true; +- +- return NOTIFY_DONE; +-} +- +-static struct notifier_block cs35l56_reboot_notifier = { +- .notifier_call = cs35l56_reboot_notify, +-}; +- +-static int __init cs35l56_modinit(void) +-{ +- /* +- * Use reboot notifier to prevent race between shutdown and +- * snd_soc_register_component(). Driver shutdown() callback would +- * run too late, after device_shutdown() is already walking the +- * device list that component registration can modify. +- */ +- return register_reboot_notifier(&cs35l56_reboot_notifier); +-} +-module_init(cs35l56_modinit); +- +-static void __exit cs35l56_modexit(void) +-{ +- unregister_reboot_notifier(&cs35l56_reboot_notifier); +-} +-module_exit(cs35l56_modexit); +- + MODULE_DESCRIPTION("ASoC CS35L56 driver"); + MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); + MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -32,7 +32,6 @@ struct sdw_slave; + struct cs35l56_private { + struct wm_adsp dsp; /* must be first member */ + struct cs35l56_base base; +- struct work_struct component_register_work; + struct work_struct dsp_work; + struct workqueue_struct *dsp_wq; + struct snd_soc_component *component; +@@ -40,11 +39,8 @@ struct cs35l56_private { + struct sdw_slave *sdw_peripheral; + struct regmap *sdw_bus_regmap; + const char *fallback_fw_suffix; +- struct work_struct sdw_irq_work; +- bool sdw_irq_no_unmask; + bool soft_resetting; + bool sdw_attached; +- bool component_registered; + struct completion init_completion; + + int speaker_id; +@@ -67,10 +63,8 @@ static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_b + + extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; + +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56); +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56); + + int cs35l56_system_suspend(struct device *dev); + int cs35l56_system_suspend_late(struct device *dev); +@@ -78,7 +72,6 @@ int cs35l56_system_suspend_no_irq(struct device *dev); + int cs35l56_system_resume_no_irq(struct device *dev); + int cs35l56_system_resume_early(struct device *dev); + int cs35l56_system_resume(struct device *dev); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); + int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); + int cs35l56_init(struct cs35l56_private *cs35l56); +diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c +--- a/sound/soc/codecs/cs40l50-codec.c ++++ b/sound/soc/codecs/cs40l50-codec.c +@@ -288,8 +288,8 @@ static int cs40l50_codec_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id cs40l50_id[] = { +- { "cs40l50-codec", }, +- {} ++ { .name = "cs40l50-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs40l50_id); + +diff --git a/sound/soc/codecs/cs42l42-sdw.c b/sound/soc/codecs/cs42l42-sdw.c +--- a/sound/soc/codecs/cs42l42-sdw.c ++++ b/sound/soc/codecs/cs42l42-sdw.c +@@ -481,10 +481,20 @@ static int cs42l42_sdw_runtime_resume(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + + /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ ret = regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ if (ret) ++ goto err_sync; ++ ++ ret = regcache_sync(cs42l42->regmap); ++ if (ret) ++ goto err_sync; + + return 0; ++ ++err_sync: ++ regcache_cache_only(cs42l42->regmap, true); ++ regcache_mark_dirty(cs42l42->regmap); ++ return ret; + } + + static int cs42l42_sdw_resume(struct device *dev) +diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c +--- a/sound/soc/codecs/cs42l42.c ++++ b/sound/soc/codecs/cs42l42.c +@@ -9,6 +9,7 @@ + * Author: Michael White + */ + ++#include + #include + #include + #include +@@ -565,7 +566,7 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l42->irq_lock); ++ guard(mutex)(&cs42l42->irq_lock); + cs42l42->jack = jk; + + if (jk) { +@@ -581,7 +582,6 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + break; + } + } +- mutex_unlock(&cs42l42->irq_lock); + + return 0; + } +@@ -1668,13 +1668,10 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + unsigned int current_button_status; + unsigned int i; + +- pm_runtime_get_sync(cs42l42->dev); +- mutex_lock(&cs42l42->irq_lock); +- if (cs42l42->suspended || !cs42l42->init_done) { +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); ++ guard(pm_runtime_active_auto)(cs42l42->dev); ++ guard(mutex)(&cs42l42->irq_lock); ++ if (cs42l42->suspended || !cs42l42->init_done) + return IRQ_NONE; +- } + + /* Read sticky registers to clear interurpt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { +@@ -1774,9 +1771,6 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); +- + return IRQ_HANDLED; + } + EXPORT_SYMBOL_NS_GPL(cs42l42_irq_thread, "SND_SOC_CS42L42_CORE"); +@@ -2163,23 +2157,23 @@ int cs42l42_suspend(struct device *dev) + * future interrupts. This ensures a safe disable if the interrupt + * is shared. + */ +- mutex_lock(&cs42l42->irq_lock); +- cs42l42->suspended = true; ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ cs42l42->suspended = true; + +- /* Save register values that will be overwritten by shutdown sequence */ +- for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { +- regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); +- save_regs[i] = (u8)reg; ++ /* Save register values that will be overwritten by shutdown sequence */ ++ for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { ++ regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); ++ save_regs[i] = (u8)reg; ++ } ++ ++ /* Shutdown codec */ ++ regmap_multi_reg_write(cs42l42->regmap, ++ cs42l42_shutdown_seq, ++ ARRAY_SIZE(cs42l42_shutdown_seq)); ++ ++ /* All interrupt sources are now disabled */ + } + +- /* Shutdown codec */ +- regmap_multi_reg_write(cs42l42->regmap, +- cs42l42_shutdown_seq, +- ARRAY_SIZE(cs42l42_shutdown_seq)); +- +- /* All interrupt sources are now disabled */ +- mutex_unlock(&cs42l42->irq_lock); +- + /* Wait for power-down complete */ + msleep(CS42L42_PDN_DONE_TIME_MS); + ret = regmap_read_poll_timeout(cs42l42->regmap, +@@ -2250,13 +2244,13 @@ void cs42l42_resume_restore(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + regcache_mark_dirty(cs42l42->regmap); + +- mutex_lock(&cs42l42->irq_lock); +- /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ ++ regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ regcache_sync(cs42l42->regmap); + +- cs42l42->suspended = false; +- mutex_unlock(&cs42l42->irq_lock); ++ cs42l42->suspended = false; ++ } + + dev_dbg(dev, "System resumed\n"); + } +diff --git a/sound/soc/codecs/cs42l43-jack.c b/sound/soc/codecs/cs42l43-jack.c +--- a/sound/soc/codecs/cs42l43-jack.c ++++ b/sound/soc/codecs/cs42l43-jack.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + + #include ++#include + #include + #include + #include +@@ -67,6 +68,21 @@ static int cs42l43_find_index(struct cs42l43_codec *priv, const char * const pro + return -EINVAL; + } + ++static void cs42l43_apply_accdet_config(struct cs42l43_codec *priv, ++ unsigned int autocontrol, ++ unsigned int pdncntl) ++{ ++ struct cs42l43 *cs42l43 = priv->core; ++ ++ regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, ++ CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | ++ CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); ++ regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, ++ CS42L43_RING_SENSE_EN_MASK, pdncntl); ++ ++ dev_dbg(priv->dev, "Successfully configured accessory detect\n"); ++} ++ + int cs42l43_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *d) + { +@@ -80,31 +96,34 @@ int cs42l43_set_jack(struct snd_soc_component *component, + + dev_dbg(priv->dev, "Configure accessory detect\n"); + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for jack config: %d\n", ret); + return ret; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + priv->jack_hp = jack; + +- if (!jack) +- goto done; ++ if (!jack) { ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); ++ return 0; ++ } + + ret = device_property_count_u32(cs42l43->dev, "cirrus,buttons-ohms"); + if (ret != -EINVAL) { + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,buttons-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + + if (ret > CS42L43_N_BUTTONS) { + ret = -EINVAL; + dev_err(priv->dev, "Property cirrus,buttons-ohms too many entries\n"); +- goto error; ++ return ret; + } + + ret = device_property_read_u32_array(cs42l43->dev, "cirrus,buttons-ohms", +@@ -112,7 +131,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,button-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + } else { + priv->buttons[0] = 70; +@@ -124,7 +143,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + ret = cs42l43_find_index(priv, "cirrus,detect-us", 50000, &priv->detect_us, + cs42l43_accdet_us, ARRAY_SIZE(cs42l43_accdet_us)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_AUTO_HSDET_TIME_SHIFT; + +@@ -134,7 +153,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_ramp_ms, cs42l43_accdet_ramp_ms, + ARRAY_SIZE(cs42l43_accdet_ramp_ms)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_HSBIAS_RAMP_SHIFT; + +@@ -142,7 +161,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_sense_ua, cs42l43_accdet_bias_sense, + ARRAY_SIZE(cs42l43_accdet_bias_sense)); + if (ret < 0) +- goto error; ++ return ret; + + if (priv->bias_sense_ua) + autocontrol |= ret << CS42L43_HSBIAS_SENSE_TRIP_SHIFT; +@@ -154,7 +173,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_debounce_ms); + if (ret < 0 && ret != -EINVAL) { + dev_err(priv->dev, "Property cirrus,tip-debounce-ms malformed: %d\n", ret); +- goto error; ++ return ret; + } + + /* This tip sense invert is set normally, as TIPSENSE_INV already inverted */ +@@ -170,7 +189,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_fall_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_FALLING_DB_TIME_SHIFT; + +@@ -178,7 +197,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_rise_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_RISING_DB_TIME_SHIFT; + +@@ -199,7 +218,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_FALLING_DB_TIME_SHIFT; + +@@ -207,7 +226,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_RISING_DB_TIME_SHIFT; + pdncntl |= CS42L43_RING_SENSE_EN_MASK; +@@ -228,23 +247,9 @@ int cs42l43_set_jack(struct snd_soc_component *component, + CS42L43_HSBIAS_RAMP_MASK | CS42L43_HSDET_MODE_MASK | + CS42L43_AUTO_HSDET_TIME_MASK, hs2); + +-done: +- ret = 0; ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); + +- regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, +- CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | +- CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); +- regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, +- CS42L43_RING_SENSE_EN_MASK, pdncntl); +- +- dev_dbg(priv->dev, "Successfully configured accessory detect\n"); +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return ret; ++ return 0; + } + + static void cs42l43_start_hs_bias(struct cs42l43_codec *priv, bool type_detect) +@@ -370,22 +375,22 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + { + struct cs42l43_codec *priv = data; + struct cs42l43 *cs42l43 = priv->core; +- irqreturn_t iret = IRQ_NONE; + unsigned int buttons = 0; + unsigned int val = 0; + int i, ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button press: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button press IRQ\n"); +- goto error; ++ return IRQ_NONE; + } + + // Wait for 2 full cycles of comb filter to ensure good reading +@@ -396,12 +401,12 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + /* Bail if jack removed, the button is irrelevant and likely invalid */ + if (!cs42l43_jack_present(priv)) { + dev_dbg(priv->dev, "Button ignored due to removal\n"); +- goto error; ++ return IRQ_NONE; + } + + if (val & CS42L43_HSBIAS_CLAMP_STS_MASK) { + dev_dbg(priv->dev, "Button ignored due to bias sense\n"); +- goto error; ++ return IRQ_NONE; + } + + val = (val & CS42L43_HSDET_DC_STS_MASK) >> CS42L43_HSDET_DC_STS_SHIFT; +@@ -424,45 +429,32 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + + snd_soc_jack_report(priv->jack_hp, buttons, CS42L43_JACK_BUTTONS); + +- iret = IRQ_HANDLED; +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + irqreturn_t cs42l43_button_release(int irq, void *data) + { + struct cs42l43_codec *priv = data; +- irqreturn_t iret = IRQ_NONE; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button release: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (priv->button_detect_running) { +- dev_dbg(priv->dev, "Button release IRQ\n"); +- +- snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); +- +- iret = IRQ_HANDLED; +- } else { ++ if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button release IRQ\n"); ++ return IRQ_NONE; + } + +- mutex_unlock(&priv->jack_lock); ++ dev_dbg(priv->dev, "Button release IRQ\n"); ++ snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); + +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + void cs42l43_bias_sense_timeout(struct work_struct *work) +@@ -472,13 +464,14 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for bias sense: %d\n", ret); + return; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (cs42l43_jack_present(priv) && priv->button_detect_running) { + dev_dbg(priv->dev, "Bias sense timeout out, restore bias\n"); +@@ -491,10 +484,6 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK, + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK); + } +- +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); + } + + static const struct reg_sequence cs42l43_3pole_patch[] = { +@@ -896,9 +885,8 @@ int cs42l43_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + ucontrol->value.integer.value[0] = priv->jack_override; +- mutex_unlock(&priv->jack_lock); + + return 0; + } +@@ -914,17 +902,13 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + if (override >= e->items) + return -EINVAL; + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (!cs42l43_jack_present(priv)) { +- mutex_unlock(&priv->jack_lock); ++ if (!cs42l43_jack_present(priv)) + return -EBUSY; +- } + +- if (override == priv->jack_override) { +- mutex_unlock(&priv->jack_lock); ++ if (override == priv->jack_override) + return 0; +- } + + priv->jack_override = override; + +@@ -984,7 +968,5 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + cs42l43_jack_override_modes[override].report); + } + +- mutex_unlock(&priv->jack_lock); +- + return 1; + } +diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c +--- a/sound/soc/codecs/cs42l43.c ++++ b/sound/soc/codecs/cs42l43.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1207,14 +1208,12 @@ static void cs42l43_spk_vu_sync(struct cs42l43_codec *priv) + { + struct cs42l43 *cs42l43 = priv->core; + +- mutex_lock(&priv->spk_vu_lock); ++ guard(mutex)(&priv->spk_vu_lock); + + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, CS42L43_AMP1_2_VU_MASK); + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, 0); +- +- mutex_unlock(&priv->spk_vu_lock); + } + + static int cs42l43_shutter_get(struct cs42l43_codec *priv, unsigned int shift) +@@ -1601,7 +1600,7 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- mutex_lock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1626,8 +1625,6 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&cs42l43->pll_lock); +- + return ret; + } + +@@ -2565,13 +2562,10 @@ static int cs42l43_set_sysclk(struct snd_soc_component *component, int clk_id, + { + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + struct cs42l43 *cs42l43 = priv->core; +- int ret; + +- mutex_lock(&cs42l43->pll_lock); +- ret = cs42l43_set_pll(priv, src, freq); +- mutex_unlock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + +- return ret; ++ return cs42l43_set_pll(priv, src, freq); + } + + static int cs42l43_component_probe(struct snd_soc_component *component) +@@ -2941,8 +2935,8 @@ static const struct dev_pm_ops cs42l43_codec_pm_ops = { + }; + + static const struct platform_device_id cs42l43_codec_id_table[] = { +- { "cs42l43-codec", }, +- {} ++ { .name = "cs42l43-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs42l43_codec_id_table); + +diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c +--- a/sound/soc/codecs/cs42l84.c ++++ b/sound/soc/codecs/cs42l84.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -281,10 +282,9 @@ static int cs42l84_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + cs42l84->jack = jk; + snd_soc_jack_report(jk, cs42l84->hs_type, SND_JACK_HEADSET); +- mutex_unlock(&cs42l84->irq_lock); + + return 0; + } +@@ -831,7 +831,7 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + u8 current_ring_state; + int i; + +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + /* Read sticky registers to clear interrupt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { + regmap_read(cs42l84->regmap, irq_params_table[i].status_addr, +@@ -902,8 +902,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + break; + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +@@ -919,8 +917,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c +--- a/sound/soc/codecs/cs43130.c ++++ b/sound/soc/codecs/cs43130.c +@@ -6,6 +6,7 @@ + * + * Authors: Li Xu + */ ++#include + #include + #include + #include +@@ -818,26 +819,26 @@ static int cs43130_dsd_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; +@@ -881,26 +882,26 @@ static int cs43130_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + case CS43130_XSP_DOP_DAI: +@@ -988,14 +989,13 @@ static int cs43130_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&cs43130->clk_mutex); ++ guard(mutex)(&cs43130->clk_mutex); + cs43130->clk_req--; + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + cs43130_pcm_dsd_mix(false, cs43130->regmap); + } +- mutex_unlock(&cs43130->clk_mutex); + + return 0; + } +diff --git a/sound/soc/codecs/cs4349.c b/sound/soc/codecs/cs4349.c +--- a/sound/soc/codecs/cs4349.c ++++ b/sound/soc/codecs/cs4349.c +@@ -340,7 +340,13 @@ static int cs4349_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs4349->reset_gpio, 1); + + regcache_cache_only(cs4349->regmap, false); +- regcache_sync(cs4349->regmap); ++ ret = regcache_sync(cs4349->regmap); ++ if (ret) { ++ regcache_cache_only(cs4349->regmap, true); ++ regcache_mark_dirty(cs4349->regmap); ++ gpiod_set_value_cansleep(cs4349->reset_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs47l15.c b/sound/soc/codecs/cs47l15.c +--- a/sound/soc/codecs/cs47l15.c ++++ b/sound/soc/codecs/cs47l15.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1285,9 +1286,8 @@ static int cs47l15_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1317,9 +1317,8 @@ static void cs47l15_component_remove(struct snd_soc_component *component) + struct cs47l15 *cs47l15 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l15->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l15->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs47l35.c b/sound/soc/codecs/cs47l35.c +--- a/sound/soc/codecs/cs47l35.c ++++ b/sound/soc/codecs/cs47l35.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1566,9 +1567,8 @@ static int cs47l35_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1600,9 +1600,8 @@ static void cs47l35_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l35->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L35_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l35->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l85.c b/sound/soc/codecs/cs47l85.c +--- a/sound/soc/codecs/cs47l85.c ++++ b/sound/soc/codecs/cs47l85.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2504,9 +2505,8 @@ static int cs47l85_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2537,9 +2537,8 @@ static void cs47l85_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l85->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L85_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l85->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l90.c b/sound/soc/codecs/cs47l90.c +--- a/sound/soc/codecs/cs47l90.c ++++ b/sound/soc/codecs/cs47l90.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2423,9 +2424,8 @@ static int cs47l90_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2456,9 +2456,8 @@ static void cs47l90_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l90->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L90_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l90->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l92.c b/sound/soc/codecs/cs47l92.c +--- a/sound/soc/codecs/cs47l92.c ++++ b/sound/soc/codecs/cs47l92.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1892,9 +1893,8 @@ static int cs47l92_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1922,9 +1922,8 @@ static void cs47l92_component_remove(struct snd_soc_component *component) + struct cs47l92 *cs47l92 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l92->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l92->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs48l32.c b/sound/soc/codecs/cs48l32.c +--- a/sound/soc/codecs/cs48l32.c ++++ b/sound/soc/codecs/cs48l32.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,13 @@ static int cs48l32_rate_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v + int ret; + + /* Prevent any mixer mux changes while we do this */ +- mutex_lock(&cs48l32_codec->rate_lock); ++ guard(mutex)(&cs48l32_codec->rate_lock); + + /* The write must be guarded by a number of SYSCLK cycles */ + cs48l32_spin_sysclk(cs48l32_codec); + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + cs48l32_spin_sysclk(cs48l32_codec); + +- mutex_unlock(&cs48l32_codec->rate_lock); +- + return ret; + } + +@@ -2242,7 +2241,6 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + struct cs48l32_dai_priv *dai_priv = &cs48l32_codec->dai[dai->id - 1]; + unsigned int base = dai->driver->base; + unsigned int current_asp_rate, target_asp_rate; +- bool change_rate_domain = false; + int ret; + + if (clk_id == dai_priv->clk) +@@ -2284,19 +2282,15 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + + if ((current_asp_rate & CS48L32_ASP_RATE_MASK) != + (target_asp_rate & CS48L32_ASP_RATE_MASK)) { +- change_rate_domain = true; +- +- mutex_lock(&cs48l32_codec->rate_lock); + /* Guard the rate change with SYSCLK cycles */ +- cs48l32_spin_sysclk(cs48l32_codec); +- } +- +- snd_soc_component_update_bits(component, base + CS48L32_ASP_CONTROL1, +- CS48L32_ASP_RATE_MASK, target_asp_rate); +- +- if (change_rate_domain) { +- cs48l32_spin_sysclk(cs48l32_codec); +- mutex_unlock(&cs48l32_codec->rate_lock); ++ scoped_guard(mutex, &cs48l32_codec->rate_lock) { ++ cs48l32_spin_sysclk(cs48l32_codec); ++ snd_soc_component_update_bits(component, ++ base + CS48L32_ASP_CONTROL1, ++ CS48L32_ASP_RATE_MASK, ++ target_asp_rate); ++ cs48l32_spin_sysclk(cs48l32_codec); ++ } + } + } + +diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c +--- a/sound/soc/codecs/cx2072x.c ++++ b/sound/soc/codecs/cx2072x.c +@@ -9,6 +9,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -1408,7 +1409,7 @@ static int cx2072x_jack_status_check(void *data) + unsigned int type = 0; + int state = 0; + +- mutex_lock(&cx2072x->lock); ++ guard(mutex)(&cx2072x->lock); + + regmap_read(cx2072x->regmap, CX2072X_PORTA_PIN_SENSE, &jack); + jack = jack >> 24; +@@ -1434,8 +1435,6 @@ static int cx2072x_jack_status_check(void *data) + /* clear interrupt */ + regmap_write(cx2072x->regmap, CX2072X_UM_INTERRUPT_CRTL_E, 0x12 << 24); + +- mutex_unlock(&cx2072x->lock); +- + dev_dbg(codec->dev, "CX2072X_HSDETECT type=0x%X,Jack state = %x\n", + type, state); + return state; +diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c +--- a/sound/soc/codecs/da7213.c ++++ b/sound/soc/codecs/da7213.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -216,13 +217,10 @@ static int da7213_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -230,13 +228,10 @@ static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -244,13 +239,10 @@ static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -258,13 +250,10 @@ static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -465,9 +454,8 @@ static int da7213_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7213->ctrl_lock); ++ scoped_guard(mutex, &da7213->ctrl_lock) ++ ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -499,12 +487,11 @@ static int da7213_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + ret = regmap_raw_read(da7213->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7213->regmap, reg, + &val_new, sizeof(val_new)); +- mutex_unlock(&da7213->ctrl_lock); + + if (ret < 0) + return ret; +diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c +--- a/sound/soc/codecs/da7219.c ++++ b/sound/soc/codecs/da7219.c +@@ -8,6 +8,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -256,13 +257,10 @@ static int da7219_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -270,13 +268,10 @@ static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -284,13 +279,10 @@ static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -298,13 +290,10 @@ static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -422,9 +411,8 @@ static int da7219_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7219->ctrl_lock); ++ scoped_guard(mutex, &da7219->ctrl_lock) ++ ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -456,12 +444,11 @@ static int da7219_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + ret = regmap_raw_read(da7219->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7219->regmap, reg, +- &val_new, sizeof(val_new)); +- mutex_unlock(&da7219->ctrl_lock); ++ &val_new, sizeof(val_new)); + + if (ret < 0) + return ret; +@@ -1167,15 +1154,12 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- if ((da7219->clk_src == clk_id) && (da7219->mclk_rate == freq)) { +- mutex_unlock(&da7219->pll_lock); ++ if (da7219->clk_src == clk_id && da7219->mclk_rate == freq) + return 0; +- } + +- if ((freq < 2000000) || (freq > 54000000)) { +- mutex_unlock(&da7219->pll_lock); ++ if (freq < 2000000 || freq > 54000000) { + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; +@@ -1193,7 +1177,6 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); +- mutex_unlock(&da7219->pll_lock); + return -EINVAL; + } + +@@ -1203,17 +1186,13 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + freq = clk_round_rate(da7219->mclk, freq); + ret = clk_set_rate(da7219->mclk, freq); + if (ret) { +- dev_err(codec_dai->dev, "Failed to set clock rate %d\n", +- freq); +- mutex_unlock(&da7219->pll_lock); ++ dev_err(codec_dai->dev, "Failed to set clock rate %d\n", freq); + return ret; + } + } + + da7219->mclk_rate = freq; + +- mutex_unlock(&da7219->pll_lock); +- + return 0; + } + +@@ -1296,13 +1275,10 @@ static int da7219_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + { + struct snd_soc_component *component = codec_dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->pll_lock); +- ret = da7219_set_pll(component, source, fout); +- mutex_unlock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- return ret; ++ return da7219_set_pll(component, source, fout); + } + + static int da7219_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c +--- a/sound/soc/codecs/es8316.c ++++ b/sound/soc/codecs/es8316.c +@@ -9,11 +9,13 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -30,6 +32,13 @@ static const unsigned int supported_mclk_lrck_ratios[] = { + 256, 384, 400, 500, 512, 768, 1024 + }; + ++static const char * const es8316_supply_names[] = { ++ "avdd", ++ "cpvdd", ++ "dvdd", ++ "pvdd", ++}; ++ + struct es8316_priv { + struct mutex lock; + struct clk *mclk; +@@ -620,15 +629,15 @@ static irqreturn_t es8316_irq(int irq, void *data) + struct snd_soc_component *comp = es8316->component; + unsigned int flags; + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags); + if (flags == 0x00) +- goto out; /* Powered-down / reset */ ++ return IRQ_HANDLED; /* Powered-down / reset */ + + /* Catch spurious IRQ before set_jack is called */ + if (!es8316->jack) +- goto out; ++ return IRQ_HANDLED; + + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; +@@ -681,8 +690,6 @@ static irqreturn_t es8316_irq(int irq, void *data) + } + } + +-out: +- mutex_unlock(&es8316->lock); + return IRQ_HANDLED; + } + +@@ -699,18 +706,16 @@ static void es8316_enable_jack_detect(struct snd_soc_component *component, + es8316->jd_inverted = device_property_read_bool(component->dev, + "everest,jack-detect-inverted"); + +- mutex_lock(&es8316->lock); ++ scoped_guard(mutex, &es8316->lock) { ++ es8316->jack = jack; + +- es8316->jack = jack; ++ if (es8316->jack->status & SND_JACK_MICROPHONE) ++ es8316_enable_micbias_for_mic_gnd_short_detect(component); + +- if (es8316->jack->status & SND_JACK_MICROPHONE) +- es8316_enable_micbias_for_mic_gnd_short_detect(component); +- +- snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, +- ES8316_GPIO_ENABLE_INTERRUPT, +- ES8316_GPIO_ENABLE_INTERRUPT); +- +- mutex_unlock(&es8316->lock); ++ snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, ++ ES8316_GPIO_ENABLE_INTERRUPT, ++ ES8316_GPIO_ENABLE_INTERRUPT); ++ } + + /* Enable irq and sync initial jack state */ + enable_irq(es8316->irq); +@@ -726,7 +731,7 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + + disable_irq(es8316->irq); + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, + ES8316_GPIO_ENABLE_INTERRUPT, 0); +@@ -737,8 +742,6 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + } + + es8316->jack = NULL; +- +- mutex_unlock(&es8316->lock); + } + + static int es8316_set_jack(struct snd_soc_component *component, +@@ -871,6 +874,11 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) + + i2c_set_clientdata(i2c_client, es8316); + ++ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), ++ es8316_supply_names); ++ if (ret) ++ return dev_err_probe(dev, ret, "unable to enable supplies\n"); ++ + es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap); + if (IS_ERR(es8316->regmap)) + return PTR_ERR(es8316->regmap); +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -6,6 +6,7 @@ + // Authors: David Yang + // + ++#include + #include + #include + #include +@@ -790,7 +791,7 @@ static void es8326_jack_button_handler(struct work_struct *work) + if (!(es8326->jack->status & SND_JACK_HEADSET)) /* Jack unplugged */ + return; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + switch (iface) { + case 0x93: +@@ -845,7 +846,6 @@ static void es8326_jack_button_handler(struct work_struct *work) + } + es8326_disable_micbias(es8326->component); + } +- mutex_unlock(&es8326->lock); + } + + static void es8326_jack_detect_handler(struct work_struct *work) +@@ -855,7 +855,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + struct snd_soc_component *comp = es8326->component; + unsigned int iface; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + dev_dbg(comp->dev, "gpio flag %#04x", iface); + +@@ -873,7 +873,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + regmap_update_bits(es8326->regmap, ES8326_HPDET_TYPE, + ES8326_HP_DET_JACK_POL, (es8326->jd_inverted ? + ~es8326->jack_pol : es8326->jack_pol)); +- goto exit; ++ return; + } + + if ((iface & ES8326_HPINSERT_FLAG) == 0) { +@@ -930,7 +930,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + queue_delayed_work(system_dfl_wq, &es8326->jack_detect_work, + msecs_to_jiffies(400)); + es8326->hp = 1; +- goto exit; ++ return; + } + if (es8326->jack->status & SND_JACK_HEADSET) { + /* detect button */ +@@ -939,7 +939,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + (ES8326_INT_SRC_PIN9 | ES8326_INT_SRC_BUTTON)); + es8326_enable_micbias(es8326->component); + queue_delayed_work(system_dfl_wq, &es8326->button_press_work, 10); +- goto exit; ++ return; + } + if ((iface & ES8326_HPBUTTON_FLAG) == 0x01) { + dev_dbg(comp->dev, "Headphone detected\n"); +@@ -958,8 +958,6 @@ static void es8326_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + } +-exit: +- mutex_unlock(&es8326->lock); + } + + static irqreturn_t es8326_irq(int irq, void *dev_id) +@@ -1200,13 +1198,12 @@ static void es8326_enable_jack_detect(struct snd_soc_component *component, + { + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&es8326->lock); +- if (es8326->jd_inverted) +- snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, +- ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); +- es8326->jack = jack; +- +- mutex_unlock(&es8326->lock); ++ scoped_guard(mutex, &es8326->lock) { ++ if (es8326->jd_inverted) ++ snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, ++ ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); ++ es8326->jack = jack; ++ } + es8326_irq(es8326->irq, es8326); + } + +@@ -1219,13 +1216,12 @@ static void es8326_disable_jack_detect(struct snd_soc_component *component) + return; /* Already disabled (or never enabled) */ + cancel_delayed_work_sync(&es8326->jack_detect_work); + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + if (es8326->jack->status & SND_JACK_MICROPHONE) { + es8326_disable_micbias(component); + snd_soc_jack_report(es8326->jack, 0, SND_JACK_HEADSET); + } + es8326->jack = NULL; +- mutex_unlock(&es8326->lock); + } + + static int es8326_set_jack(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c +--- a/sound/soc/codecs/es8328.c ++++ b/sound/soc/codecs/es8328.c +@@ -405,6 +405,11 @@ static const struct snd_soc_dapm_route es8328_dapm_routes[] = { + + { "Mic Bias", NULL, "Mic Bias Gen" }, + ++ { "LINPUT1", NULL, "Mic Bias" }, ++ { "RINPUT1", NULL, "Mic Bias" }, ++ { "LINPUT2", NULL, "Mic Bias" }, ++ { "RINPUT2", NULL, "Mic Bias" }, ++ + { "Left Mixer", NULL, "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -36,6 +36,10 @@ struct es8389_private { + unsigned int sysclk; + int mastermode; + ++ u8 hpfl; ++ u8 hpfr; ++ u32 hpf_freq; ++ u32 capture_rate; + u8 mclk_src; + u8 vddd; + int version; +@@ -50,10 +54,29 @@ static const char * const es8389_core_supplies[] = { + static bool es8389_volatile_register(struct device *dev, + unsigned int reg) + { +- if ((reg <= 0xff)) +- return true; +- else ++ switch (reg) { ++ case ES8389_ADCL_VOL: ++ case ES8389_ADCR_VOL: ++ case ES8389_MIC1_GAIN: ++ case ES8389_MIC2_GAIN: ++ case ES8389_DACL_VOL: ++ case ES8389_DACR_VOL: ++ case ES8389_ALC_ON: ++ case ES8389_ALC_CTL: ++ case ES8389_ALC_TARGET: ++ case ES8389_ALC_GAIN: ++ case ES8389_ADC_MUTE: ++ case ES8389_OSR_VOL: ++ case ES8389_DAC_INV: ++ case ES8389_MIX_VOL: ++ case ES8389_DAC_MIX: ++ case ES8389_ADC_RESET: ++ case ES8389_ADC_MODE: ++ case ES8389_DMIC_EN: + return false; ++ default: ++ return true; ++ } + } + + static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -9550, 50, 0); +@@ -63,6 +86,92 @@ static const DECLARE_TLV_DB_SCALE(mix_vol_tlv, -9500, 100, 0); + static const DECLARE_TLV_DB_SCALE(alc_target_tlv, -3200, 200, 0); + static const DECLARE_TLV_DB_SCALE(alc_max_level, -3200, 200, 0); + ++static const u32 hpf_table[10][10] = { ++ {1020, 754, 624, 559, 527, 511, 502, 498, 497, 496}, ++ {754, 495, 368, 306, 274, 259, 251, 247, 246, 244}, ++ {624, 368, 243, 182, 151, 136, 128, 124, 123, 121}, ++ {559, 306, 182, 120, 90, 75, 68, 63, 62, 60}, ++ {527, 274, 151, 90, 60, 45, 38, 33, 32, 31}, ++ {511, 259, 136, 75, 45, 30, 23, 19, 18, 17}, ++ {502, 251, 128, 68, 38, 23, 16, 13, 11, 11}, ++ {498, 247, 124, 63, 33, 19, 13, 10, 8, 8}, ++ {497, 246, 123, 62, 32, 18, 11, 8, 8, 0}, ++ {496, 244, 121, 60, 31, 17, 11, 8, 0, 0} ++}; ++ ++static bool find_best_hpf_freq(u32 target_hz, u8 *hpf1, u8 *hpf2, u32 *out) ++{ ++ int best_row = -1, best_col = -1; ++ u32 min_diff = U32_MAX; ++ u32 f, diff; ++ int i, j; ++ ++ if (target_hz > 1020) ++ return false; ++ ++ for (i = 0; i < 10; i++) { ++ for (j = i; j < 10; j++) { ++ f = hpf_table[i][j]; ++ ++ diff = (target_hz > f) ? (target_hz - f) : (f - target_hz); ++ if (diff < min_diff) { ++ min_diff = diff; ++ best_row = i; ++ best_col = j; ++ *out = f; ++ } ++ } ++ } ++ ++ *hpf1 = best_col + ES8389_HPF_OFFSET; ++ *hpf2 = best_row + ES8389_HPF_OFFSET; ++ ++ return true; ++} ++ ++static int es8389_hpf_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = es8389->hpf_freq; ++ return 0; ++} ++ ++static int es8389_hpf_set(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ u32 freq; ++ bool hpf; ++ ++ if (es8389->hpf_freq == ucontrol->value.integer.value[0]) ++ return 0; ++ ++ if (es8389->capture_rate) { ++ freq = (ucontrol->value.integer.value[0] * 48000) / es8389->capture_rate; ++ ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) ++ return -EBUSY; ++ ++ if (es8389->hpf_freq != ucontrol->value.integer.value[0]) ++ dev_dbg(component->dev, "At the %u Hz sampling rate, %ld Hz could not be obtained." ++ "the frequency has been set to the closest value, %u Hz\n", ++ es8389->capture_rate, ucontrol->value.integer.value[0], es8389->hpf_freq); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); ++ } else { ++ es8389->hpf_freq = ucontrol->value.integer.value[0]; ++ dev_dbg(component->dev, "PCM_STREAM_CAPTURE is not active.retain the input frequency\n"); ++ } ++ ++ return 1; ++} ++ + static int es8389_dmic_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -143,6 +252,16 @@ static const struct soc_enum alc_ramprate = + static const struct soc_enum alc_winsize = + SOC_ENUM_SINGLE(ES8389_ALC_CTL, 0, 16, winsize); + ++static const char *const es8389_adcl_mux_txt[] = { ++ "Normal", ++ "ADC2 channel to ADC1 channel", ++}; ++ ++static const char *const es8389_adcr_mux_txt[] = { ++ "Normal", ++ "ADC1 channel to ADC2 channel", ++}; ++ + static const char *const es8389_outl_mux_txt[] = { + "Normal", + "DAC2 channel to DAC1 channel", +@@ -170,6 +289,20 @@ static const unsigned int es8389_pga_values[] = { + 1, 5, 6 + }; + ++static const struct soc_enum es8389_adcl_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 5, ++ ARRAY_SIZE(es8389_adcl_mux_txt), es8389_adcl_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcl_mux_controls = ++ SOC_DAPM_ENUM("INPUTL MUX", es8389_adcl_mux_enum); ++ ++static const struct soc_enum es8389_adcr_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 4, ++ ARRAY_SIZE(es8389_adcr_mux_txt), es8389_adcr_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcr_mux_controls = ++ SOC_DAPM_ENUM("INPUTR MUX", es8389_adcr_mux_enum); ++ + static const struct soc_enum es8389_outl_mux_enum = + SOC_ENUM_SINGLE(ES8389_DAC_MIX, 5, + ARRAY_SIZE(es8389_outl_mux_txt), es8389_outl_mux_txt); +@@ -240,6 +373,8 @@ static const struct snd_kcontrol_new es8389_snd_controls[] = { + SOC_DOUBLE("ADC OSR Volume ON Switch", ES8389_ADC_MUTE, 6, 7, 1, 0), + SOC_SINGLE_TLV("ADC OSR Volume", ES8389_OSR_VOL, 0, 0xFF, 0, adc_vol_tlv), + SOC_DOUBLE("ADC OUTPUT Invert Switch", ES8389_ADC_HPF2, 5, 6, 1, 0), ++ SOC_SINGLE_EXT("ADC HPF Freq Select", SND_SOC_NOPM, 0, 1020, 0, ++ es8389_hpf_get, es8389_hpf_set), + + SOC_SINGLE_TLV("DACL Playback Volume", ES8389_DACL_VOL, 0, 0xFF, 0, dac_vol_tlv), + SOC_SINGLE_TLV("DACR Playback Volume", ES8389_DACR_VOL, 0, 0xFF, 0, dac_vol_tlv), +@@ -298,6 +433,8 @@ static const struct snd_soc_dapm_widget es8389_dapm_widgets[] = { + &es8389_adc_mixer_controls[0], + ARRAY_SIZE(es8389_adc_mixer_controls)), + SND_SOC_DAPM_MUX("ADC MUX", SND_SOC_NOPM, 0, 0, &es8389_dmic_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_adcl_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_adcr_mux_controls), + + SND_SOC_DAPM_MUX("OUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_outl_mux_controls), + SND_SOC_DAPM_MUX("OUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_outr_mux_controls), +@@ -311,10 +448,15 @@ static const struct snd_soc_dapm_route es8389_dapm_routes[] = { + {"ADCL", NULL, "PGAL"}, + {"ADCR", NULL, "PGAR"}, + ++ {"INPUTL MUX", "Normal", "ADCL"}, ++ {"INPUTL MUX", "ADC2 channel to ADC1 channel", "ADCR"}, ++ {"INPUTR MUX", "Normal", "ADCR"}, ++ {"INPUTR MUX", "ADC1 channel to ADC2 channel", "ADCL"}, ++ + {"ADC Mixer", "DACL ADCL Mixer", "DACL"}, + {"ADC Mixer", "DACR ADCR Mixer", "DACR"}, +- {"ADC Mixer", NULL, "ADCL"}, +- {"ADC Mixer", NULL, "ADCR"}, ++ {"ADC Mixer", NULL, "INPUTL MUX"}, ++ {"ADC Mixer", NULL, "INPUTR MUX"}, + + {"ADC MUX", "AMIC", "ADC Mixer"}, + {"ADC MUX", "DMIC", "DMIC"}, +@@ -415,52 +557,54 @@ static const struct _coeff_div coeff_div[] = { + {36, 576000, 16000, 0x00, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {48, 768000, 16000, 0x02, 0x57, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {50, 800000, 16000, 0x00, 0x7E, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {96, 1536000, 16000, 0x02, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {128, 2048000, 16000, 0x00, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {144, 2304000, 16000, 0x00, 0x51, 0x00, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {150, 2400000, 16000, 0x02, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {192, 3072000, 16000, 0x02, 0x65, 0x25, 0xE0, 0x00, 0xE1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {300, 4800000, 16000, 0x02, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {384, 6144000, 16000, 0x02, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {750, 12000000, 16000, 0x0E, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1152, 18432000, 16000, 0x08, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1200, 19200000, 16000, 0x0B, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1500, 24000000, 16000, 0x0E, 0x26, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1625, 26000000, 16000, 0x40, 0x6E, 0x05, 0xC8, 0x01, 0xC2, 0x90, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {800, 19200000, 24000, 0x07, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x1A, 0x49, 0x14, 2, 2}, + {375, 12000000, 32000, 0x0E, 0x2E, 0x05, 0xC8, 0x00, 0xC2, 0x80, 0x40, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 0}, +- {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, +- {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, ++ {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 2822400, 44100, 0x00, 0x51, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {48, 2304000, 48000, 0x02, 0x55, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {50, 2400000, 48000, 0x00, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 3072000, 48000, 0x00, 0x51, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {125, 6000000, 48000, 0x04, 0x6E, 0x05, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {250, 12000000, 48000, 0x04, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {800, 38400000, 48000, 0x18, 0x45, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {128, 11289600, 88200, 0x00, 0x50, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x32, 0x89, 0x25, 2, 2}, +- {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, ++ {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {96, 9216000, 96000, 0x02, 0x43, 0x00, 0xC0, 0x10, 0xC0, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {256, 24576000, 96000, 0x00, 0x40, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {128, 24576000, 192000, 0x00, 0x50, 0x00, 0xC0, 0x18, 0xC1, 0x81, 0xC0, 0x00, 0x8F, 0x7F, 0xBF, 0xC0, 0x3F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 2, 2}, ++ {64, 12288000, 192000, 0x00, 0x41, 0x00, 0xC0, 0x18, 0xC1, 0x80, 0x00, 0x00, 0x8F, 0x7F, 0xEF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 1, 0}, + }; + + static inline int get_coeff(u8 vddd, u8 dmic, int mclk, int rate) +@@ -564,6 +708,8 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + int coeff, ret; + u8 dmic_enable, state = 0; + unsigned int regv; ++ u32 freq; ++ bool hpf; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: +@@ -590,7 +736,7 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + + if (es8389->mclk_src == ES8389_SCLK_PIN) { + regmap_update_bits(es8389->regmap, ES8389_MASTER_CLK, +- ES8389_MCLK_SOURCE, es8389->mclk_src); ++ ES8389_MCLK_MASK, ES8389_MCLK_FROM_SCLK); + es8389->sysclk = params_channels(params) * params_width(params) * params_rate(params); + } + +@@ -641,6 +787,28 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + dev_warn(component->dev, "Clock coefficients do not match"); + } + ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ++ es8389->capture_rate = params_rate(params); ++ freq = (es8389->hpf_freq * 48000) / params_rate(params); ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) { ++ dev_err(component->dev, "The HPF frequency is invalid\n"); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ++ es8389->capture_rate = 0; ++ + return 0; + } + +@@ -721,8 +889,8 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8389->regmap, ES8389_DAC_FORMAT_MUTE, + 0x03, 0x00); + } else { +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x0a); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x0a); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); + regmap_update_bits(es8389->regmap, ES8389_ADC_FORMAT_MUTE, + 0x03, 0x00); + } +@@ -738,6 +906,7 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + + static const struct snd_soc_dai_ops es8389_ops = { + .hw_params = es8389_pcm_hw_params, ++ .hw_free = es8389_pcm_hw_free, + .set_fmt = es8389_set_dai_fmt, + .set_sysclk = es8389_set_dai_sysclk, + .set_tdm_slot = es8389_set_tdm_slot, +@@ -771,7 +940,7 @@ static void es8389_init(struct snd_soc_component *component) + + regmap_read(es8389->regmap, ES8389_MAX_REGISTER, ®); + es8389->version = reg; +- regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x00); ++ regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x56); + regmap_write(es8389->regmap, ES8389_RESET, 0x7E); + regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x38); + regmap_write(es8389->regmap, ES8389_ADC_HPF1, 0x64); +@@ -823,7 +992,7 @@ static void es8389_init(struct snd_soc_component *component) + regmap_write(es8389->regmap, ES8389_SCLK_DIV, 0x04); + regmap_write(es8389->regmap, ES8389_LRCK_DIV1, 0x01); + regmap_write(es8389->regmap, ES8389_LRCK_DIV2, 0x00); +- regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x00); ++ regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x10); + regmap_write(es8389->regmap, ES8389_ADC_OSR, 0x1F); + regmap_write(es8389->regmap, ES8389_ADC_DSP, 0x7F); + regmap_write(es8389->regmap, ES8389_ADC_MUTE, 0xC0); +@@ -861,13 +1030,13 @@ static int es8389_resume(struct snd_soc_component *component) + regcache_cache_only(es8389->regmap, false); + regcache_cache_bypass(es8389->regmap, true); + regmap_read(es8389->regmap, ES8389_RESET, ®v); +- regcache_cache_bypass(es8389->regmap, false); + + if (regv == 0xff) + es8389_init(component); + else + es8389_set_bias_level(component, SND_SOC_BIAS_ON); + ++ regcache_cache_bypass(es8389->regmap, false); + regcache_sync(es8389->regmap); + + return 0; +@@ -913,6 +1082,7 @@ static int es8389_probe(struct snd_soc_component *component) + return ret; + } + ++ es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); + es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + +diff --git a/sound/soc/codecs/es8389.h b/sound/soc/codecs/es8389.h +--- a/sound/soc/codecs/es8389.h ++++ b/sound/soc/codecs/es8389.h +@@ -106,6 +106,9 @@ + #define ES8389_MIC_SEL_MASK (7 << 4) + #define ES8389_MIC_DEFAULT (1 << 4) + ++#define ES8389_HPF_DEFAULT 16 ++#define ES8389_HPF_OFFSET 4 ++ + #define ES8389_MASTER_MODE_EN (1 << 0) + + #define ES8389_TDM_OFF (0 << 0) +@@ -116,9 +119,11 @@ + #define ES8389_TDM_SLOT (0x70 << 0) + #define ES8389_TDM_SHIFT 4 + +-#define ES8389_MCLK_SOURCE (1 << 6) +-#define ES8389_MCLK_PIN (1 << 6) +-#define ES8389_SCLK_PIN (0 << 6) ++#define ES8389_MCLK_MASK (3 << 6) ++#define ES8389_MCLK_FROM_SCLK (1 << 6) ++#define ES8389_MCLK_SOURCE ES8389_MCLK_PIN ++#define ES8389_MCLK_PIN 0 ++#define ES8389_SCLK_PIN 1 + + /* ES8389_FMT */ + #define ES8389_S24_LE (0 << 5) +diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c +--- a/sound/soc/codecs/es9356.c ++++ b/sound/soc/codecs/es9356.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -19,8 +20,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include +@@ -500,16 +499,19 @@ static int es9356_power_state(struct snd_soc_dai *dai, unsigned char ps, unsigne + } + + /* power state changes are not independent across functions */ +- mutex_lock(&es9356->pde_lock); +- ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps0:ps3); +- if (ret) { +- regmap_write(es9356->regmap, +- SDW_SDCA_CTL(func, pde_entity, ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ps?ps3:ps0); +- es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps3:ps0); +- } else +- dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ps?ps0:ps3); +- +- mutex_unlock(&es9356->pde_lock); ++ scoped_guard(mutex, &es9356->pde_lock) { ++ ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps0 : ps3); ++ if (ret) { ++ regmap_write(es9356->regmap, ++ SDW_SDCA_CTL(func, pde_entity, ++ ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ++ ps ? ps3 : ps0); ++ es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps3 : ps0); ++ } else { ++ dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ++ ps ? ps0 : ps3); ++ } ++ } + + if (rate) + regmap_write(es9356->regmap, +@@ -671,7 +673,7 @@ static int es9356_sdca_mbq_size(struct device *dev, unsigned int reg) + } + } + +-static struct regmap_sdw_mbq_cfg es9356_mbq_config = { ++static const struct regmap_sdw_mbq_cfg es9356_mbq_config = { + .mbq_size = es9356_sdca_mbq_size, + }; + +@@ -1091,9 +1093,8 @@ static int es9356_sdca_dev_system_suspend(struct device *dev) + { + struct es9356_sdw_priv *es9356 = dev_get_drvdata(dev); + +- mutex_lock(&es9356->disable_irq_lock); +- es9356->disable_irq = true; +- mutex_unlock(&es9356->disable_irq_lock); ++ scoped_guard(mutex, &es9356->disable_irq_lock) ++ es9356->disable_irq = true; + + return es9356_sdca_dev_suspend(dev); + } +diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c +--- a/sound/soc/codecs/fs-amp-lib.c ++++ b/sound/soc/codecs/fs-amp-lib.c +@@ -221,7 +221,7 @@ static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) + + int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + { +- const struct firmware *cont; ++ const struct firmware *cont __free(firmware) = NULL; + struct fs_fwm_header *hdr; + int ret; + +@@ -237,7 +237,6 @@ int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); + + hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); +- release_firmware(cont); + if (!hdr) + return -ENOMEM; + +diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c +--- a/sound/soc/codecs/fs210x.c ++++ b/sound/soc/codecs/fs210x.c +@@ -4,6 +4,7 @@ + // + // Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + ++#include + #include + #include + #include +@@ -771,9 +772,8 @@ static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) + return -EOPNOTSUPP; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_set_hw_params(fs210x); +- mutex_unlock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) ++ ret = fs210x_set_hw_params(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to set hw params: %d\n", ret); + +@@ -790,15 +790,11 @@ static int fs210x_dai_mute(struct snd_soc_dai *dai, int mute, int stream) + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); +@@ -817,15 +813,11 @@ static int fs210x_dai_trigger(struct snd_pcm_substream *substream, + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: +@@ -853,13 +845,11 @@ static void fs210x_start_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, start_work.work); + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + ret = fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); +- +- mutex_unlock(&fs210x->lock); + } + + static void fs210x_fault_check_work(struct work_struct *work) +@@ -870,15 +860,13 @@ static void fs210x_fault_check_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, fault_check_work.work); + +- mutex_lock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) ++ return; + +- if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return; ++ ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + } + +- ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); +- mutex_unlock(&fs210x->lock); + if (ret) + return; + +@@ -991,7 +979,7 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + if (fs210x->scene_id < 1) + return -EINVAL; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + /* + * FS210x has scene(s) as below: + * init scene: id = 0 +@@ -1000,7 +988,6 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + */ + index = fs210x->scene_id - 1; + ucontrol->value.integer.value[0] = index; +- mutex_unlock(&fs210x->lock); + + return 0; + } +@@ -1019,7 +1006,7 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + /* + * FS210x has scene(s) as below: +@@ -1029,17 +1016,14 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + */ + scene_id = ucontrol->value.integer.value[0] + 1; + scene_count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */ +- if (scene_id < 1 || scene_id > scene_count) { +- mutex_unlock(&fs210x->lock); ++ if (scene_id < 1 || scene_id > scene_count) + return -ERANGE; +- } + + if (scene_id != fs210x->scene_id) + is_changed = true; + + if (fs210x->is_suspended) { + fs210x->scene_id = scene_id; +- mutex_unlock(&fs210x->lock); + return is_changed; + } + +@@ -1047,8 +1031,6 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + +- mutex_unlock(&fs210x->lock); +- + if (!ret && is_changed) + return 1; + +@@ -1062,12 +1044,10 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + struct fs210x_priv *fs210x = snd_soc_component_get_drvdata(cmpnt); + int ret = 0; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- if (fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); ++ if (fs210x->is_suspended) + return 0; +- } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1088,8 +1068,6 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&fs210x->lock); +- + return ret; + } + +@@ -1220,11 +1198,9 @@ static int fs210x_probe(struct snd_soc_component *cmpnt) + if (ret) + return ret; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_init_chip(fs210x); +- mutex_unlock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- return ret; ++ return fs210x_init_chip(fs210x); + } + + static void fs210x_remove(struct snd_soc_component *cmpnt) +@@ -1251,15 +1227,15 @@ static int fs210x_suspend(struct snd_soc_component *cmpnt) + + regcache_cache_only(fs210x->regmap, true); + +- mutex_lock(&fs210x->lock); +- fs210x->cur_scene = NULL; +- fs210x->is_inited = false; +- fs210x->is_playing = false; +- fs210x->is_suspended = true; ++ scoped_guard(mutex, &fs210x->lock) { ++ fs210x->cur_scene = NULL; ++ fs210x->is_inited = false; ++ fs210x->is_playing = false; ++ fs210x->is_suspended = true; + +- gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ +- fsleep(30000); /* >= 30ms */ +- mutex_unlock(&fs210x->lock); ++ gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ ++ fsleep(30000); /* >= 30ms */ ++ } + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); +@@ -1288,14 +1264,11 @@ static int fs210x_resume(struct snd_soc_component *cmpnt) + return ret; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + fs210x->is_suspended = false; +- ret = fs210x_init_chip(fs210x); + +- mutex_unlock(&fs210x->lock); +- +- return ret; ++ return fs210x_init_chip(fs210x); + } + #else + #define fs210x_suspend NULL +diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c +--- a/sound/soc/codecs/hdac_hda.c ++++ b/sound/soc/codecs/hdac_hda.c +@@ -437,7 +437,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (loadable_patch[hda_pvt->dev_index] && *loadable_patch[hda_pvt->dev_index]) { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&hdev->dev, "Applying patch firmware '%s'\n", + loadable_patch[hda_pvt->dev_index]); +@@ -451,7 +451,6 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + dev_err(&hdev->dev, "%s: failed to load hda patch %d\n", __func__, ret); + goto error_no_pm; + } +- release_firmware(fw); + } + } + #endif +diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c +--- a/sound/soc/codecs/hdac_hdmi.c ++++ b/sound/soc/codecs/hdac_hdmi.c +@@ -10,6 +10,7 @@ + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + ++#include + #include + #include + #include +@@ -537,10 +538,11 @@ static struct hdac_hdmi_port *hdac_hdmi_get_port_from_cvt( + continue; + + list_for_each_entry(port, &pcm->port_list, head) { +- mutex_lock(&pcm->lock); +- ret = hdac_hdmi_query_port_connlist(hdev, +- port->pin, port); +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ ret = hdac_hdmi_query_port_connlist(hdev, ++ port->pin, ++ port); ++ } + if (ret < 0) + continue; + +@@ -640,11 +642,11 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream, + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); + + if (pcm) { +- mutex_lock(&pcm->lock); +- pcm->chmap_set = false; +- memset(pcm->chmap, 0, sizeof(pcm->chmap)); +- pcm->channels = 0; +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ pcm->chmap_set = false; ++ memset(pcm->chmap, 0, sizeof(pcm->chmap)); ++ pcm->channels = 0; ++ } + } + + if (dai_map->port) +@@ -922,7 +924,7 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + if (port == NULL) + return -EINVAL; + +- mutex_lock(&hdmi->pin_mutex); ++ guard(mutex)(&hdmi->pin_mutex); + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (list_empty(&pcm->port_list)) + continue; +@@ -945,12 +947,10 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + list_add_tail(&port->head, &pcm->port_list); + if (port->eld.monitor_present && port->eld.eld_valid) { + hdac_hdmi_jack_report_sync(pcm, port, true); +- mutex_unlock(&hdmi->pin_mutex); + return ret; + } + } + } +- mutex_unlock(&hdmi->pin_mutex); + + return ret; + } +@@ -1274,67 +1274,65 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, + * In case of non MST pin, get_eld info API expectes port + * to be -1. + */ +- mutex_lock(&hdmi->pin_mutex); +- port->eld.monitor_present = false; ++ scoped_guard(mutex, &hdmi->pin_mutex) { ++ port->eld.monitor_present = false; + +- if (pin->mst_capable) +- port_id = port->id; ++ if (pin->mst_capable) ++ port_id = port->id; + +- size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, +- &port->eld.monitor_present, +- port->eld.eld_buffer, +- ELD_MAX_SIZE); ++ size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, ++ &port->eld.monitor_present, ++ port->eld.eld_buffer, ++ ELD_MAX_SIZE); + +- if (size > 0) { +- size = min(size, ELD_MAX_SIZE); +- if (hdac_hdmi_parse_eld(hdev, port) < 0) +- size = -EINVAL; +- } +- +- eld_valid = port->eld.eld_valid; +- +- if (size > 0) { +- port->eld.eld_valid = true; +- port->eld.eld_size = size; +- } else { +- port->eld.eld_valid = false; +- port->eld.eld_size = 0; +- } +- +- eld_changed = (eld_valid != port->eld.eld_valid); +- +- pcm = hdac_hdmi_get_pcm(hdev, port); +- +- if (!port->eld.monitor_present || !port->eld.eld_valid) { +- +- dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", +- __func__, pin->nid, port->id); +- +- /* +- * PCMs are not registered during device probe, so don't +- * report jack here. It will be done in usermode mux +- * control select. +- */ +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, false); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ size = min(size, ELD_MAX_SIZE); ++ if (hdac_hdmi_parse_eld(hdev, port) < 0) ++ size = -EINVAL; + } + +- mutex_unlock(&hdmi->pin_mutex); +- return; +- } ++ eld_valid = port->eld.eld_valid; + +- if (port->eld.monitor_present && port->eld.eld_valid) { +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, true); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ port->eld.eld_valid = true; ++ port->eld.eld_size = size; ++ } else { ++ port->eld.eld_valid = false; ++ port->eld.eld_size = 0; + } + +- print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, +- port->eld.eld_buffer, port->eld.eld_size, false); ++ eld_changed = (eld_valid != port->eld.eld_valid); + ++ pcm = hdac_hdmi_get_pcm(hdev, port); ++ ++ if (!port->eld.monitor_present || !port->eld.eld_valid) { ++ ++ dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", ++ __func__, pin->nid, port->id); ++ ++ /* ++ * PCMs are not registered during device probe, so don't ++ * report jack here. It will be done in usermode mux ++ * control select. ++ */ ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, false); ++ schedule_work(&port->dapm_work); ++ } ++ ++ return; ++ } ++ ++ if (port->eld.monitor_present && port->eld.eld_valid) { ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, true); ++ schedule_work(&port->dapm_work); ++ } ++ ++ print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, ++ port->eld.eld_buffer, port->eld.eld_size, false); ++ } + } +- mutex_unlock(&hdmi->pin_mutex); + + if (eld_changed && pcm) + snd_ctl_notify(hdmi->card, +@@ -1795,13 +1793,12 @@ static void hdac_hdmi_set_chmap(struct hdac_device *hdev, int pcm_idx, + if (list_empty(&pcm->port_list)) + return; + +- mutex_lock(&pcm->lock); ++ guard(mutex)(&pcm->lock); + pcm->chmap_set = true; + memcpy(pcm->chmap, chmap, ARRAY_SIZE(pcm->chmap)); + list_for_each_entry(port, &pcm->port_list, head) + if (prepared) + hdac_hdmi_setup_audio_infoframe(hdev, pcm, port); +- mutex_unlock(&pcm->lock); + } + + static bool is_hdac_hdmi_pcm_attached(struct hdac_device *hdev, int pcm_idx) +@@ -1866,8 +1863,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + snd_hdac_ext_bus_link_get(hdev->bus, hlink); + + hdmi_priv = devm_kzalloc(&hdev->dev, sizeof(*hdmi_priv), GFP_KERNEL); +- if (hdmi_priv == NULL) ++ if (hdmi_priv == NULL) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENOMEM; ++ } + + snd_hdac_register_chmap_ops(hdev, &hdmi_priv->chmap); + hdmi_priv->chmap.ops.get_chmap = hdac_hdmi_get_chmap; +@@ -1876,8 +1875,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + hdmi_priv->chmap.ops.get_spk_alloc = hdac_hdmi_get_spk_alloc; + hdmi_priv->hdev = hdev; + +- if (!hdac_id) ++ if (!hdac_id) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENODEV; ++ } + + if (hdac_id->driver_data) + hdmi_priv->drv_data = +@@ -1902,6 +1903,8 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + if (ret < 0) { + dev_err(&hdev->dev, + "Failed in parse and map nid with err: %d\n", ret); ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); ++ snd_hdac_display_power(hdev->bus, hdev->addr, false); + return ret; + } + snd_hdac_refresh_widgets(hdev); +diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c +--- a/sound/soc/codecs/hdmi-codec.c ++++ b/sound/soc/codecs/hdmi-codec.c +@@ -4,6 +4,7 @@ + * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Jyri Sarha + */ ++#include + #include + #include + #include +@@ -456,31 +457,30 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + if (!((has_playback && tx) || (has_capture && !tx))) + return 0; + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + if (hcp->busy) { + dev_err(dai->dev, "Only one simultaneous stream supported!\n"); +- mutex_unlock(&hcp->lock); + return -EINVAL; + } + + if (hcp->hcd.ops->audio_startup) { + ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); + if (ret) +- goto err; ++ return ret; + } + + if (tx && hcp->hcd.ops->get_eld) { + ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, + hcp->eld, sizeof(hcp->eld)); + if (ret) +- goto err; ++ return ret; + + snd_parse_eld(dai->dev, &hcp->eld_parsed, + hcp->eld, sizeof(hcp->eld)); + + ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); + if (ret) +- goto err; ++ return ret; + + /* Select chmap supported */ + hdmi_codec_eld_chmap(hcp); +@@ -488,8 +488,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + + hcp->busy = true; + +-err: +- mutex_unlock(&hcp->lock); + return ret; + } + +@@ -507,9 +505,8 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + hcp->busy = false; +- mutex_unlock(&hcp->lock); + } + + static int hdmi_codec_fill_codec_params(struct snd_soc_dai *dai, +diff --git a/sound/soc/codecs/idt821034.c b/sound/soc/codecs/idt821034.c +--- a/sound/soc/codecs/idt821034.c ++++ b/sound/soc/codecs/idt821034.c +@@ -7,6 +7,7 @@ + // Author: Herve Codina + + #include ++#include + #include + #include + #include +@@ -413,12 +414,12 @@ static int idt821034_kctrl_gain_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); +- if (IDT821034_ID_IS_OUT(mc->reg)) +- val = idt821034->amps.ch[ch].amp_out.gain; +- else +- val = idt821034->amps.ch[ch].amp_in.gain; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ if (IDT821034_ID_IS_OUT(mc->reg)) ++ val = idt821034->amps.ch[ch].amp_out.gain; ++ else ++ val = idt821034->amps.ch[ch].amp_in.gain; ++ } + + ucontrol->value.integer.value[0] = val & mask; + if (invert) +@@ -456,7 +457,7 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(mc->reg)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -466,22 +467,18 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->gain == val) { +- ret = 0; +- goto end; +- } ++ if (amp->gain == val) ++ return 0; + + if (!amp->is_muted) { + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, val); + if (ret) +- goto end; ++ return ret; + } + + amp->gain = val; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, +@@ -495,11 +492,11 @@ static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(id); + +- mutex_lock(&idt821034->mutex); +- is_muted = IDT821034_ID_IS_OUT(id) ? +- idt821034->amps.ch[ch].amp_out.is_muted : +- idt821034->amps.ch[ch].amp_in.is_muted; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ is_muted = IDT821034_ID_IS_OUT(id) ? ++ idt821034->amps.ch[ch].amp_out.is_muted : ++ idt821034->amps.ch[ch].amp_in.is_muted; ++ } + + ucontrol->value.integer.value[0] = !is_muted; + +@@ -521,7 +518,7 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + ch = IDT821034_ID_GET_CHAN(id); + is_mute = !ucontrol->value.integer.value[0]; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(id)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -531,21 +528,17 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->is_muted == is_mute) { +- ret = 0; +- goto end; +- } ++ if (amp->is_muted == is_mute) ++ return 0; + + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, + is_mute ? 0 : amp->gain); + if (ret) +- goto end; ++ return ret; + + amp->is_muted = is_mute; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static const DECLARE_TLV_DB_LINEAR(idt821034_gain_in, -300, 1300); +@@ -623,24 +616,20 @@ static int idt821034_power_event(struct snd_soc_dapm_widget *w, + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component); + unsigned int id = w->shift; + u8 power, mask; +- int ret; + u8 ch; + + ch = IDT821034_ID_GET_CHAN(id); + mask = IDT821034_ID_IS_OUT(id) ? IDT821034_CONF_PWRUP_RX : IDT821034_CONF_PWRUP_TX; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + power = idt821034_get_channel_power(idt821034, ch); + if (SND_SOC_DAPM_EVENT_ON(event)) + power |= mask; + else + power &= ~mask; +- ret = idt821034_set_channel_power(idt821034, ch, power); + +- mutex_unlock(&idt821034->mutex); +- +- return ret; ++ return idt821034_set_channel_power(idt821034, ch, power); + } + + static const struct snd_soc_dapm_widget idt821034_dapm_widgets[] = { +@@ -717,9 +706,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_RX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_RX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set tx tdm slot failed (%d)\n", + ch, ret); +@@ -742,9 +731,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_TX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_TX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set rx tdm slot failed (%d)\n", + ch, ret); +@@ -769,9 +758,8 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -785,13 +773,10 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + default: + dev_err(dai->dev, "Unsupported DAI format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, +@@ -800,9 +785,8 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -816,13 +800,10 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + default: + dev_err(dai->dev, "Unsupported PCM format 0x%x\n", + params_format(params)); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static const unsigned int idt821034_sample_bits[] = {8}; +@@ -897,11 +878,11 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + ret = idt821034_set_codec_conf(idt821034, 0); + if (ret) +- goto end; ++ return ret; + + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + idt821034->amps.ch[i].amp_out.gain = IDT821034_GAIN_OUT_INIT_RAW; +@@ -909,24 +890,21 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_RX, + idt821034->amps.ch[i].amp_out.gain); + if (ret) +- goto end; ++ return ret; + + idt821034->amps.ch[i].amp_in.gain = IDT821034_GAIN_IN_INIT_RAW; + idt821034->amps.ch[i].amp_in.is_muted = false; + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_TX, + idt821034->amps.ch[i].amp_in.gain); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_set_channel_power(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + } + +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ return 0; + } + + static int idt821034_component_probe(struct snd_soc_component *component) +@@ -965,7 +943,7 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_raw = idt821034_get_written_slic_raw(idt821034, ch); + if (val) +@@ -974,8 +952,6 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + slic_raw &= ~mask; + ret = idt821034_write_slic_raw(idt821034, ch, slic_raw); + +- mutex_unlock(&idt821034->mutex); +- + if (ret) + dev_err(&idt821034->spi->dev, "set gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -991,9 +967,8 @@ static int idt821034_chip_gpio_get(struct gpio_chip *c, unsigned int offset) + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); +- ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); + if (ret) { + dev_err(&idt821034->spi->dev, "get gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -1015,9 +990,8 @@ static int idt821034_chip_get_direction(struct gpio_chip *c, unsigned int offset + struct idt821034 *idt821034 = gpiochip_get_data(c); + u8 slic_dir; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + slic_dir = idt821034_get_slic_conf(idt821034, ch); +- mutex_unlock(&idt821034->mutex); + + return slic_dir & mask ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; + } +@@ -1034,7 +1008,7 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + if (mask & ~(IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN)) + return -EPERM; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) | mask; + +@@ -1044,7 +1018,6 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1060,7 +1033,7 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + if (ret) + return ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) & ~mask; + +@@ -1070,7 +1043,6 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1079,24 +1051,22 @@ static int idt821034_reset_gpio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + /* IO0 and IO1 as input for all channels and output IO set to 0 */ + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + ret = idt821034_set_slic_conf(idt821034, i, + IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_write_slic_raw(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + + } +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 0; + } + + static int idt821034_gpio_init(struct idt821034 *idt821034) +diff --git a/sound/soc/codecs/lochnagar-sc.c b/sound/soc/codecs/lochnagar-sc.c +--- a/sound/soc/codecs/lochnagar-sc.c ++++ b/sound/soc/codecs/lochnagar-sc.c +@@ -137,15 +137,23 @@ static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBP_CFP); + } + ++static const u64 lochnagar_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops lochnagar_sc_line_ops = { + .startup = lochnagar_sc_line_startup, + .shutdown = lochnagar_sc_line_shutdown, + .set_fmt = lochnagar_sc_set_line_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { + .startup = lochnagar_sc_startup, + .set_fmt = lochnagar_sc_set_usb_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver lochnagar_sc_dai[] = { +diff --git a/sound/soc/codecs/lpass-macro-common.c b/sound/soc/codecs/lpass-macro-common.c +--- a/sound/soc/codecs/lpass-macro-common.c ++++ b/sound/soc/codecs/lpass-macro-common.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2022, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -71,21 +72,16 @@ EXPORT_SYMBOL_GPL(lpass_macro_pds_exit); + + void lpass_macro_set_codec_version(enum lpass_codec_version version) + { +- mutex_lock(&lpass_codec_mutex); ++ guard(mutex)(&lpass_codec_mutex); + lpass_codec_version = version; +- mutex_unlock(&lpass_codec_mutex); + } + EXPORT_SYMBOL_GPL(lpass_macro_set_codec_version); + + enum lpass_codec_version lpass_macro_get_codec_version(void) + { +- enum lpass_codec_version ver; ++ guard(mutex)(&lpass_codec_mutex); + +- mutex_lock(&lpass_codec_mutex); +- ver = lpass_codec_version; +- mutex_unlock(&lpass_codec_mutex); +- +- return ver; ++ return lpass_codec_version; + } + EXPORT_SYMBOL_GPL(lpass_macro_get_codec_version); + +diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c +--- a/sound/soc/codecs/lpass-rx-macro.c ++++ b/sound/soc/codecs/lpass-rx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2030,9 +2031,10 @@ static struct snd_soc_dai_driver rx_macro_dai[] = { + }, + }; + +-static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) ++static int rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + { + struct regmap *regmap = rx->regmap; ++ int ret; + + if (mclk_enable) { + if (rx->rx_mclk_users == 0) { +@@ -2047,14 +2049,16 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_FS_MCLK_CNT_EN_MASK, + CDC_RX_FS_MCLK_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + rx->rx_mclk_users++; + } else { + if (rx->rx_mclk_users <= 0) { + dev_err(rx->dev, "%s: clock already disabled\n", __func__); + rx->rx_mclk_users = 0; +- return; ++ return 0; + } + rx->rx_mclk_users--; + if (rx->rx_mclk_users == 0) { +@@ -2068,6 +2072,8 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_CLK_MCLK2_EN_MASK, 0x0); + } + } ++ ++ return 0; + } + + static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, +@@ -2079,11 +2085,9 @@ static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- rx_macro_mclk_enable(rx, true); +- break; ++ return rx_macro_mclk_enable(rx, true); + case SND_SOC_DAPM_POST_PMD: +- rx_macro_mclk_enable(rx, false); +- break; ++ return rx_macro_mclk_enable(rx, false); + default: + dev_err(component->dev, "%s: invalid DAPM event %d\n", __func__, event); + ret = -EINVAL; +@@ -3671,14 +3675,16 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct rx_macro *rx = to_rx_macro(hw); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_runtime_resume_and_get(rx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = rx_macro_mclk_enable(rx, true); + if (ret) { +- dev_err(rx->dev, "unable to prepare mclk\n"); ++ clk_disable_unprepare(rx->mclk); + return ret; + } + +- rx_macro_mclk_enable(rx, true); +- + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, + CDC_RX_SWR_CLK_EN_MASK, 1); + +@@ -3693,7 +3699,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_RX_SWR_CLK_EN_MASK, 0); + + rx_macro_mclk_enable(rx, false); +- clk_disable_unprepare(rx->mclk); ++ pm_runtime_put_autosuspend(rx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -3864,28 +3870,31 @@ static int rx_macro_probe(struct platform_device *pdev) + rx->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(rx->mclk, MCLK_FREQ); +- clk_set_rate(rx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(rx->macro); ++ ret = clk_set_rate(rx->mclk, MCLK_FREQ); + if (ret) + return ret; + +- ret = clk_prepare_enable(rx->dcodec); ++ ret = clk_set_rate(rx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(rx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(rx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ return ret; + + /* reset swr block */ + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, +@@ -3902,46 +3911,25 @@ static int rx_macro_probe(struct platform_device *pdev) + rx_macro_dai, + ARRAY_SIZE(rx_macro_dai)); + if (ret) +- goto err_clkout; +- +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = rx_macro_register_mclk_output(rx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(rx->fsgen); +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +-err_mclk: +- clk_disable_unprepare(rx->dcodec); +-err_dcodec: +- clk_disable_unprepare(rx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + + return ret; + } + +-static void rx_macro_remove(struct platform_device *pdev) +-{ +- struct rx_macro *rx = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(rx->mclk); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->macro); +- clk_disable_unprepare(rx->dcodec); +-} +- + static const struct of_device_id rx_macro_dt_match[] = { + { + .compatible = "qcom,sc7280-lpass-rx-macro", +@@ -3969,13 +3957,17 @@ MODULE_DEVICE_TABLE(of, rx_macro_dt_match); + static int rx_macro_runtime_suspend(struct device *dev) + { + struct rx_macro *rx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rx->regmap, true); +- regcache_mark_dirty(rx->regmap); + +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(rx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(rx->regmap); + + return 0; + } +@@ -3985,33 +3977,23 @@ static int rx_macro_runtime_resume(struct device *dev) + struct rx_macro *rx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); + return ret; + } + +- ret = clk_prepare_enable(rx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(rx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } + regcache_cache_only(rx->regmap, false); +- regcache_sync(rx->regmap); ++ ret = regcache_sync(rx->regmap); ++ if (ret) { ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops rx_macro_pm_ops = { +@@ -4026,7 +4008,6 @@ static struct platform_driver rx_macro_driver = { + .pm = pm_ptr(&rx_macro_pm_ops), + }, + .probe = rx_macro_probe, +- .remove = rx_macro_remove, + }; + + module_platform_driver(rx_macro_driver); +diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c +--- a/sound/soc/codecs/lpass-tx-macro.c ++++ b/sound/soc/codecs/lpass-tx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -614,6 +615,7 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + bool mclk_enable) + { + struct regmap *regmap = tx->regmap; ++ int ret; + + if (mclk_enable) { + if (tx->tx_mclk_users == 0) { +@@ -626,7 +628,9 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + CDC_TX_FS_CNT_EN_MASK, + CDC_TX_FS_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + tx->tx_mclk_users++; + } else { +@@ -739,11 +743,9 @@ static int tx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- tx_macro_mclk_enable(tx, true); +- break; ++ return tx_macro_mclk_enable(tx, true); + case SND_SOC_DAPM_POST_PMD: +- tx_macro_mclk_enable(tx, false); +- break; ++ return tx_macro_mclk_enable(tx, false); + default: + break; + } +@@ -2149,17 +2151,20 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct regmap *regmap = tx->regmap; + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_runtime_resume_and_get(tx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = tx_macro_mclk_enable(tx, true); + if (ret) { +- dev_err(tx->dev, "failed to enable mclk\n"); ++ pm_runtime_put_autosuspend(tx->dev); + return ret; + } + +- tx_macro_mclk_enable(tx, true); +- + regmap_update_bits(regmap, CDC_TX_CLK_RST_CTRL_SWR_CONTROL, + CDC_TX_SWR_CLK_EN_MASK, + CDC_TX_SWR_CLK_ENABLE); ++ + return 0; + } + +@@ -2172,7 +2177,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_TX_SWR_CLK_EN_MASK, 0x0); + + tx_macro_mclk_enable(tx, false); +- clk_disable_unprepare(tx->mclk); ++ pm_runtime_put_autosuspend(tx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -2315,28 +2320,31 @@ static int tx_macro_probe(struct platform_device *pdev) + tx->active_decimator[TX_MACRO_AIF3_CAP] = -1; + + /* set MCLK and NPL rates */ +- clk_set_rate(tx->mclk, MCLK_FREQ); +- clk_set_rate(tx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(tx->macro); ++ ret = clk_set_rate(tx->mclk, MCLK_FREQ); + if (ret) + goto err; + +- ret = clk_prepare_enable(tx->dcodec); ++ ret = clk_set_rate(tx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ goto err; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- ret = clk_prepare_enable(tx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(tx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ goto err; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err; + + + /* reset soundwire block */ +@@ -2356,30 +2364,21 @@ static int tx_macro_probe(struct platform_device *pdev) + tx_macro_dai, + ARRAY_SIZE(tx_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = tx_macro_register_mclk_output(tx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(tx->fsgen); +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +-err_mclk: +- clk_disable_unprepare(tx->dcodec); +-err_dcodec: +- clk_disable_unprepare(tx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + err: + lpass_macro_pds_exit(tx->pds); + +@@ -2390,25 +2389,23 @@ static void tx_macro_remove(struct platform_device *pdev) + { + struct tx_macro *tx = dev_get_drvdata(&pdev->dev); + +- clk_disable_unprepare(tx->macro); +- clk_disable_unprepare(tx->dcodec); +- clk_disable_unprepare(tx->mclk); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->fsgen); +- + lpass_macro_pds_exit(tx->pds); + } + + static int tx_macro_runtime_suspend(struct device *dev) + { + struct tx_macro *tx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(tx->regmap, true); +- regcache_mark_dirty(tx->regmap); + +- clk_disable_unprepare(tx->fsgen); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(tx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(tx->regmap); + + return 0; + } +@@ -2418,34 +2415,23 @@ static int tx_macro_runtime_resume(struct device *dev) + struct tx_macro *tx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); + return ret; + } + +- ret = clk_prepare_enable(tx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare npl\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(tx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- + regcache_cache_only(tx->regmap, false); +- regcache_sync(tx->regmap); ++ ret = regcache_sync(tx->regmap); ++ if (ret) { ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops tx_macro_pm_ops = { +diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c +--- a/sound/soc/codecs/lpass-va-macro.c ++++ b/sound/soc/codecs/lpass-va-macro.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1351,34 +1352,43 @@ static int fsgen_gate_enable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; +- int ret; ++ int ret, rpm_ret; + +- if (va->has_swr_master) { +- ret = clk_prepare_enable(va->mclk); +- if (ret) +- return ret; +- } ++ ret = pm_runtime_resume_and_get(va->dev); ++ if (ret < 0) ++ return ret; + + ret = va_macro_mclk_enable(va, true); ++ if (ret) { ++ rpm_ret = pm_runtime_put_autosuspend(va->dev); ++ if (rpm_ret < 0) ++ dev_warn(va->dev, ++ "runtime PM put failed in fsgen enable unwind: %d\n", ++ rpm_ret); ++ return ret; ++ } + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, CDC_VA_SWR_CLK_ENABLE); + +- return ret; ++ return 0; + } + + static void fsgen_gate_disable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; ++ int ret; + + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, 0x0); + + va_macro_mclk_enable(va, false); +- if (va->has_swr_master) +- clk_disable_unprepare(va->mclk); ++ ++ ret = pm_runtime_put_autosuspend(va->dev); ++ if (ret < 0) ++ dev_warn(va->dev, "runtime PM put failed in fsgen disable: %d\n", ret); + } + + static int fsgen_gate_is_enabled(struct clk_hw *hw) +@@ -1539,6 +1549,7 @@ static int va_macro_probe(struct platform_device *pdev) + void __iomem *base; + u32 sample_rate = 0; + int ret; ++ int rpm_ret; + + va = devm_kzalloc(dev, sizeof(*va), GFP_KERNEL); + if (!va) +@@ -1594,7 +1605,9 @@ static int va_macro_probe(struct platform_device *pdev) + va->has_npl_clk = data->has_npl_clk; + + /* mclk rate */ +- clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + + if (va->has_npl_clk) { + va->npl = devm_clk_get(dev, "npl"); +@@ -1603,25 +1616,29 @@ static int va_macro_probe(struct platform_device *pdev) + goto err; + } + +- clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + } + +- ret = clk_prepare_enable(va->macro); ++ ret = devm_pm_clk_create(dev); + if (ret) + goto err; + +- ret = clk_prepare_enable(va->dcodec); +- if (ret) +- goto err_dcodec; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(va->mclk); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) +- goto err_npl; ++ rpm_ret = pm_runtime_resume_and_get(dev); ++ if (rpm_ret < 0) { ++ ret = rpm_ret; ++ goto err; + } + + /** +@@ -1634,7 +1651,7 @@ static int va_macro_probe(struct platform_device *pdev) + /* read version from register */ + ret = va_macro_set_lpass_codec_version(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + } + + if (va->has_swr_master) { +@@ -1664,35 +1681,28 @@ static int va_macro_probe(struct platform_device *pdev) + va_macro_dais, + ARRAY_SIZE(va_macro_dais)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = va_macro_register_fsgen_output(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + + va->fsgen = devm_clk_hw_get_clk(dev, &va->hw, "fsgen"); + if (IS_ERR(va->fsgen)) { + ret = PTR_ERR(va->fsgen); +- goto err_clkout; ++ goto err_rpm_put; + } + ++ rpm_ret = pm_runtime_put_autosuspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", rpm_ret); ++ + return 0; + +-err_clkout: +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +-err_npl: +- clk_disable_unprepare(va->mclk); +-err_mclk: +- clk_disable_unprepare(va->dcodec); +-err_dcodec: +- clk_disable_unprepare(va->macro); ++err_rpm_put: ++ rpm_ret = pm_runtime_put_sync_suspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind: %d\n", rpm_ret); + err: + lpass_macro_pds_exit(va->pds); + +@@ -1703,53 +1713,52 @@ static void va_macro_remove(struct platform_device *pdev) + { + struct va_macro *va = dev_get_drvdata(&pdev->dev); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- clk_disable_unprepare(va->dcodec); +- clk_disable_unprepare(va->macro); +- + lpass_macro_pds_exit(va->pds); + } + + static int va_macro_runtime_suspend(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(va->regmap, true); ++ ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(va->regmap, false); ++ return ret; ++ } ++ + regcache_mark_dirty(va->regmap); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- + return 0; + } + + static int va_macro_runtime_resume(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(va->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(va->dev, "unable to prepare mclk\n"); ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); + return ret; + } + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) { +- clk_disable_unprepare(va->mclk); +- dev_err(va->dev, "unable to prepare npl\n"); +- return ret; +- } +- } +- + regcache_cache_only(va->regmap, false); +- regcache_sync(va->regmap); ++ ++ ret = regcache_sync(va->regmap); ++ if (ret) { ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(va->dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2529,15 +2530,13 @@ static const struct snd_soc_dapm_route wsa_audio_map[] = { + static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + { + struct regmap *regmap = wsa->regmap; ++ int ret; + + if (enable) { +- int ret; +- +- ret = clk_prepare_enable(wsa->mclk); +- if (ret) { +- dev_err(wsa->dev, "failed to enable mclk\n"); ++ ret = pm_runtime_resume_and_get(wsa->dev); ++ if (ret < 0) + return ret; +- } ++ + wsa_macro_mclk_enable(wsa, true); + + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2548,7 +2547,10 @@ static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + CDC_WSA_SWR_CLK_EN_MASK, 0); + wsa_macro_mclk_enable(wsa, false); +- clk_disable_unprepare(wsa->mclk); ++ ++ ret = pm_runtime_put_autosuspend(wsa->dev); ++ if (ret < 0) ++ dev_warn(wsa->dev, "runtime PM put failed: %d\n", ret); + } + + return 0; +@@ -2656,7 +2658,7 @@ static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) + init.num_parents = 1; + wsa->hw.init = &init; + hw = &wsa->hw; +- ret = clk_hw_register(wsa->dev, hw); ++ ret = devm_clk_hw_register(wsa->dev, hw); + if (ret) + return ret; + +@@ -2771,28 +2773,31 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); +- clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); +- +- ret = clk_prepare_enable(wsa->macro); ++ ret = clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err; ++ return ret; + +- ret = clk_prepare_enable(wsa->dcodec); ++ ret = clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(wsa->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ return ret; + + /* reset swr ip */ + regmap_update_bits(wsa->regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2809,56 +2814,37 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa_macro_dai, + ARRAY_SIZE(wsa_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = wsa_macro_register_mclk_output(wsa); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; +- +-err_clkout: +- clk_disable_unprepare(wsa->fsgen); +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +-err_mclk: +- clk_disable_unprepare(wsa->dcodec); +-err_dcodec: +- clk_disable_unprepare(wsa->macro); +-err: ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + return ret; +- +-} +- +-static void wsa_macro_remove(struct platform_device *pdev) +-{ +- struct wsa_macro *wsa = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(wsa->macro); +- clk_disable_unprepare(wsa->dcodec); +- clk_disable_unprepare(wsa->mclk); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->fsgen); + } + + static int wsa_macro_runtime_suspend(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(wsa->regmap, true); +- regcache_mark_dirty(wsa->regmap); + +- clk_disable_unprepare(wsa->fsgen); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(wsa->regmap); + + return 0; + } +@@ -2866,36 +2852,29 @@ static int wsa_macro_runtime_suspend(struct device *dev) + static int wsa_macro_runtime_resume(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ return ret; ++ } ++ regcache_cache_only(wsa->regmap, false); ++ ++ ret = regcache_sync(wsa->regmap); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); + return ret; + } + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(wsa->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- +- regcache_cache_only(wsa->regmap, false); +- regcache_sync(wsa->regmap); +- + return 0; +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +- +- return ret; + } + + static const struct dev_pm_ops wsa_macro_pm_ops = { +@@ -2929,7 +2908,6 @@ static struct platform_driver wsa_macro_driver = { + .pm = pm_ptr(&wsa_macro_pm_ops), + }, + .probe = wsa_macro_probe, +- .remove = wsa_macro_remove, + }; + + module_platform_driver(wsa_macro_driver); +diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c +--- a/sound/soc/codecs/madera.c ++++ b/sound/soc/codecs/madera.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -513,7 +514,7 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + * We can't rely on the DAPM mutex for locking because we need a lock + * that can safely be called in hw_params + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -532,8 +533,6 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + + madera_debug_dump_domain_groups(priv); + +- mutex_unlock(&priv->rate_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(madera_domain_clk_ev); +@@ -875,9 +874,8 @@ static int madera_adsp_rate_get(struct snd_kcontrol *kcontrol, + const int adsp_num = e->shift_l; + int item; + +- mutex_lock(&priv->rate_lock); +- cached_rate = priv->adsp_rate_cache[adsp_num]; +- mutex_unlock(&priv->rate_lock); ++ scoped_guard(mutex, &priv->rate_lock) ++ cached_rate = priv->adsp_rate_cache[adsp_num]; + + item = snd_soc_enum_val_to_item(e, cached_rate); + ucontrol->value.enumerated.item[0] = item; +@@ -893,7 +891,6 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + const int adsp_num = e->shift_l; + const unsigned int item = ucontrol->value.enumerated.item[0]; +- int ret = 0; + + if (item >= e->items) + return -EINVAL; +@@ -903,22 +900,20 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + * maintain consistent behaviour that rate domains cannot be changed + * while in use since this is a hardware requirement + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, priv->adsp[adsp_num].cs_dsp.base)) { + dev_warn(priv->madera->dev, + "Cannot change '%s' while in use by active audio paths\n", + kcontrol->id.name); +- ret = -EBUSY; ++ return -EBUSY; + } else if (priv->adsp_rate_cache[adsp_num] != e->values[item]) { + /* Volatile register so defer until the codec is powered up */ + priv->adsp_rate_cache[adsp_num] = e->values[item]; +- ret = 1; ++ return 1; + } + +- mutex_unlock(&priv->rate_lock); +- +- return ret; ++ return 0; + } + + static const struct soc_enum madera_adsp_rate_enum[] = { +@@ -1061,15 +1056,13 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + * Prevent the domain powering up while we're checking whether it's + * safe to change rate domain + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + val = snd_soc_component_read(component, e->reg); + val >>= e->shift_l; + val &= e->mask; +- if (snd_soc_enum_item_to_val(e, item) == val) { +- ret = 0; +- goto out; +- } ++ if (snd_soc_enum_item_to_val(e, item) == val) ++ return 0; + + if (!madera_can_change_grp_rate(priv, e->reg)) { + dev_warn(priv->madera->dev, +@@ -1082,8 +1075,6 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + madera_spin_sysclk(priv); + } +-out: +- mutex_unlock(&priv->rate_lock); + + return ret; + } +@@ -3041,12 +3032,11 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + if ((cur & MADERA_AIF1_RATE_MASK) == (tar & MADERA_AIF1_RATE_MASK)) + return 0; + +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, base + MADERA_AIF_RATE_CTRL)) { + madera_aif_warn(dai, "Cannot change rate while active\n"); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + + /* Guard the rate change with SYSCLK cycles */ +@@ -3055,9 +3045,6 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + MADERA_AIF1_RATE_MASK, tar); + madera_spin_sysclk(priv); + +-out: +- mutex_unlock(&priv->rate_lock); +- + return ret; + } + +@@ -3345,6 +3332,16 @@ static int madera_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + return 0; + } + ++static const u64 madera_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + const struct snd_soc_dai_ops madera_dai_ops = { + .startup = &madera_startup, + .set_fmt = &madera_set_fmt, +@@ -3352,6 +3349,8 @@ const struct snd_soc_dai_ops madera_dai_ops = { + .hw_params = &madera_hw_params, + .set_sysclk = &madera_dai_set_sysclk, + .set_tristate = &madera_set_tristate, ++ .auto_selectable_formats = &madera_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_GPL(madera_dai_ops); + +diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c +--- a/sound/soc/codecs/max98090.c ++++ b/sound/soc/codecs/max98090.c +@@ -2341,6 +2341,7 @@ static irqreturn_t max98090_interrupt(int irq, void *data) + * + * @component: MAX98090 component + * @jack: jack to report detection events on ++ * @data: can be used if codec driver need extra data for configuring jack + * + * Enable microphone detection via IRQ on the MAX98090. If GPIOs are + * being used to bring out signals to the processor then only platform +@@ -2646,12 +2647,18 @@ static void max98090_i2c_remove(struct i2c_client *client) + static int max98090_runtime_resume(struct device *dev) + { + struct max98090_priv *max98090 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98090->regmap, false); + + max98090_reset(max98090); + +- regcache_sync(max98090->regmap); ++ ret = regcache_sync(max98090->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98090->regmap, true); ++ regcache_mark_dirty(max98090->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c +--- a/sound/soc/codecs/max98095.c ++++ b/sound/soc/codecs/max98095.c +@@ -5,6 +5,7 @@ + * Copyright 2011 Maxim Integrated Products + */ + ++#include + #include + #include + #include +@@ -1532,15 +1533,17 @@ static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_eq_band(component, channel, 0, coef_set->band1); +- m98095_eq_band(component, channel, 1, coef_set->band2); +- m98095_eq_band(component, channel, 2, coef_set->band3); +- m98095_eq_band(component, channel, 3, coef_set->band4); +- m98095_eq_band(component, channel, 4, coef_set->band5); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_eq_band(component, channel, 0, coef_set->band1); ++ m98095_eq_band(component, channel, 1, coef_set->band2); ++ m98095_eq_band(component, channel, 2, coef_set->band3); ++ m98095_eq_band(component, channel, 3, coef_set->band4); ++ m98095_eq_band(component, channel, 4, coef_set->band5); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +@@ -1683,12 +1686,14 @@ static int max98095_put_bq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_biquad_band(component, channel, 0, coef_set->band1); +- m98095_biquad_band(component, channel, 1, coef_set->band2); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_biquad_band(component, channel, 0, coef_set->band1); ++ m98095_biquad_band(component, channel, 1, coef_set->band2); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -100,7 +100,12 @@ static int max98363_resume(struct device *dev) + return ret; + + regcache_cache_only(max98363->regmap, false); +- regcache_sync(max98363->regmap); ++ ret = regcache_sync(max98363->regmap); ++ if (ret) { ++ regcache_cache_only(max98363->regmap, true); ++ regcache_mark_dirty(max98363->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c +--- a/sound/soc/codecs/max98373-i2c.c ++++ b/sound/soc/codecs/max98373-i2c.c +@@ -488,10 +488,17 @@ static int max98373_suspend(struct device *dev) + static int max98373_resume(struct device *dev) + { + struct max98373_priv *max98373 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98373->regmap, false); + max98373_reset(max98373, dev); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/max98373-sdw.c b/sound/soc/codecs/max98373-sdw.c +--- a/sound/soc/codecs/max98373-sdw.c ++++ b/sound/soc/codecs/max98373-sdw.c +@@ -277,7 +277,12 @@ static int max98373_resume(struct device *dev) + } + + regcache_cache_only(max98373->regmap, false); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c +--- a/sound/soc/codecs/max98388.c ++++ b/sound/soc/codecs/max98388.c +@@ -863,10 +863,16 @@ static int max98388_suspend(struct device *dev) + static int max98388_resume(struct device *dev) + { + struct max98388_priv *max98388 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98388->regmap, false); + max98388_reset(max98388, dev); +- regcache_sync(max98388->regmap); ++ ret = regcache_sync(max98388->regmap); ++ if (ret) { ++ regcache_cache_only(max98388->regmap, true); ++ regcache_mark_dirty(max98388->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c +--- a/sound/soc/codecs/max98390.c ++++ b/sound/soc/codecs/max98390.c +@@ -788,7 +788,6 @@ static int max98390_dsm_init(struct snd_soc_component *component) + const char *vendor, *product; + struct max98390_priv *max98390 = + snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + char *dsm_param; + + vendor = dmi_get_system_info(DMI_SYS_VENDOR); +@@ -805,6 +804,8 @@ static int max98390_dsm_init(struct snd_soc_component *component) + snprintf(filename, sizeof(filename), "%s", + max98390->dsm_param_name); + } ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, component->dev); + if (ret) { + ret = request_firmware(&fw, "dsm_param.bin", component->dev); +@@ -812,7 +813,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + ret = request_firmware(&fw, "dsmparam.bin", + component->dev); + if (ret) +- goto err; ++ return ret; + } + } + +@@ -822,8 +823,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + if (fw->size < MAX98390_DSM_PARAM_MIN_SIZE) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + dsm_param = (char *)fw->data; + param_start_addr = (dsm_param[0] & 0xff) | (dsm_param[1] & 0xff) << 8; +@@ -833,8 +833,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + fw->size < param_size + MAX98390_DSM_PAYLOAD_OFFSET) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + regmap_write(max98390->regmap, MAX98390_R203A_AMP_EN, 0x80); + dsm_param += MAX98390_DSM_PAYLOAD_OFFSET; +@@ -842,10 +841,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + dsm_param, param_size); + regmap_write(max98390->regmap, MAX98390_R23E1_DSP_GLOBAL_EN, 0x01); + +-err_alloc: +- release_firmware(fw); +-err: +- return ret; ++ return 0; + } + + static void max98390_init_regs(struct snd_soc_component *component) +@@ -952,11 +948,17 @@ static int max98390_suspend(struct device *dev) + static int max98390_resume(struct device *dev) + { + struct max98390_priv *max98390 = dev_get_drvdata(dev); ++ int ret; + + dev_dbg(dev, "%s:Enter\n", __func__); + + regcache_cache_only(max98390->regmap, false); +- regcache_sync(max98390->regmap); ++ ret = regcache_sync(max98390->regmap); ++ if (ret) { ++ regcache_cache_only(max98390->regmap, true); ++ regcache_mark_dirty(max98390->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98396.c b/sound/soc/codecs/max98396.c +--- a/sound/soc/codecs/max98396.c ++++ b/sound/soc/codecs/max98396.c +@@ -1600,19 +1600,37 @@ static int max98396_resume(struct device *dev) + if (max98396->pvdd) { + ret = regulator_enable(max98396->pvdd); + if (ret < 0) +- return ret; ++ goto err_core_supplies; + } + + if (max98396->vbat) { + ret = regulator_enable(max98396->vbat); + if (ret < 0) +- return ret; ++ goto err_pvdd; + } + + regcache_cache_only(max98396->regmap, false); + max98396_reset(max98396, dev); +- regcache_sync(max98396->regmap); ++ ret = regcache_sync(max98396->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98396->regmap, true); ++ regcache_mark_dirty(max98396->regmap); ++ goto err_vbat; ++ } ++ + return 0; ++ ++err_vbat: ++ if (max98396->vbat) ++ regulator_disable(max98396->vbat); ++err_pvdd: ++ if (max98396->pvdd) ++ regulator_disable(max98396->pvdd); ++err_core_supplies: ++ regulator_bulk_disable(MAX98396_NUM_CORE_SUPPLIES, ++ max98396->core_supplies); ++ ++ return ret; + } + + static const struct dev_pm_ops max98396_pm = { +diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c +--- a/sound/soc/codecs/max98927.c ++++ b/sound/soc/codecs/max98927.c +@@ -742,11 +742,17 @@ static int max98927_suspend(struct device *dev) + static int max98927_resume(struct device *dev) + { + struct max98927_priv *max98927 = dev_get_drvdata(dev); ++ int ret; + + regmap_write(max98927->regmap, MAX98927_R0100_SOFT_RESET, + MAX98927_SOFT_RESET); + regcache_cache_only(max98927->regmap, false); +- regcache_sync(max98927->regmap); ++ ret = regcache_sync(max98927->regmap); ++ if (ret) { ++ regcache_cache_only(max98927->regmap, true); ++ regcache_mark_dirty(max98927->regmap); ++ return ret; ++ } + return 0; + } + +diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c +--- a/sound/soc/codecs/msm8916-wcd-analog.c ++++ b/sound/soc/codecs/msm8916-wcd-analog.c +@@ -217,9 +217,11 @@ + #define CDC_A_RX_HPH_BIAS_LDO_OCP (0xf195) + #define CDC_A_RX_HPH_BIAS_CNP (0xf196) + #define CDC_A_RX_HPH_CNP_EN (0xf197) ++#define CDC_A_RX_HPH_L_TEST (0xf19A) + #define CDC_A_RX_HPH_L_PA_DAC_CTL (0xf19B) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_MASK BIT(1) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_RESET BIT(1) ++#define CDC_A_RX_HPH_R_TEST (0xf19C) + #define CDC_A_RX_HPH_R_PA_DAC_CTL (0xf19D) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET BIT(1) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET_MASK BIT(1) +@@ -232,6 +234,8 @@ + #define RX_EAR_CTL_PA_SEL_MASK BIT(7) + #define RX_EAR_CTL_PA_SEL BIT(7) + ++#define CDC_A_RX_EAR_STATUS (0xf1A1) ++ + #define CDC_A_SPKR_DAC_CTL (0xf1B0) + #define SPKR_DAC_CTL_DAC_RESET_MASK BIT(4) + #define SPKR_DAC_CTL_DAC_RESET_NORMAL 0 +@@ -250,6 +254,7 @@ + SPKR_DRV_CAL_EN | SPKR_DRV_SETTLE_EN | \ + SPKR_DRV_FW_EN | SPKR_DRV_BOOST_SET | \ + SPKR_DRV_CMFB_SET | SPKR_DRV_GAIN_SET) ++#define CDC_A_SPKR_ANA_BIAS_SET (0xf1B3) + #define CDC_A_SPKR_OCP_CTL (0xf1B4) + #define CDC_A_SPKR_PWRSTG_CTL (0xf1B5) + #define SPKR_PWRSTG_CTL_DAC_EN_MASK BIT(0) +@@ -264,6 +269,7 @@ + + #define CDC_A_SPKR_DRV_DBG (0xf1B7) + #define CDC_A_CURRENT_LIMIT (0xf1C0) ++#define CDC_A_BYPASS_MODE (0xf1C2) + #define CDC_A_BOOST_EN_CTL (0xf1C3) + #define CDC_A_SLOPE_COMP_IP_ZERO (0xf1C4) + #define CDC_A_SEC_ACCESS (0xf1D0) +@@ -286,6 +292,11 @@ static const char * const supply_names[] = { + + #define MBHC_MAX_BUTTONS (5) + ++struct wcd_reg_seq { ++ const struct reg_default *seq; ++ int seq_size; ++}; ++ + struct pm8916_wcd_analog_priv { + u16 pmic_rev; + u16 codec_version; +@@ -696,6 +707,59 @@ static int pm8916_wcd_analog_enable_ear_pa(struct snd_soc_dapm_widget *w, + return 0; + } + ++static int pm8916_wcd_analog_enable_hphl_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ /* wait 20 ms after the digital codec has powered down */ ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ ++static int pm8916_wcd_analog_enable_hphr_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ + static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_RX_COM_OCP_CTL, 0xD1}, + {CDC_A_RX_COM_OCP_COUNT, 0xFF}, +@@ -715,9 +779,69 @@ static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_MASTER_BIAS_CTL, 0x30}, + }; + ++static const struct wcd_reg_seq pm8916_data = { ++ .seq = wcd_reg_defaults_2_0, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_2_0), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8950[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0x82}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8950_data = { ++ .seq = wcd_reg_defaults_pm8950, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8950), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8953[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0xa2}, ++ {CDC_A_BYPASS_MODE, 0x18}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_RX_EAR_STATUS, 0x10}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8953_data = { ++ .seq = wcd_reg_defaults_pm8953, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8953), ++}; ++ + static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + { + struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ const struct wcd_reg_seq *wcd_reg_init_data; + int err, reg; + + err = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +@@ -738,9 +862,11 @@ static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + snd_soc_component_write(component, CDC_D_PERPH_RESET_CTL4, 0x01); + snd_soc_component_write(component, CDC_A_PERPH_RESET_CTL4, 0x01); + +- for (reg = 0; reg < ARRAY_SIZE(wcd_reg_defaults_2_0); reg++) +- snd_soc_component_write(component, wcd_reg_defaults_2_0[reg].reg, +- wcd_reg_defaults_2_0[reg].def); ++ wcd_reg_init_data = of_device_get_match_data(component->dev); ++ ++ for (reg = 0; reg < wcd_reg_init_data->seq_size; reg++) ++ snd_soc_component_write(component, wcd_reg_init_data->seq[reg].reg, ++ wcd_reg_init_data->seq[reg].def); + + priv->component = component; + +@@ -883,11 +1009,15 @@ static const struct snd_soc_dapm_widget pm8916_wcd_analog_dapm_widgets[] = { + SND_SOC_DAPM_MUX("EAR_S", SND_SOC_NOPM, 0, 0, &ear_mux), + SND_SOC_DAPM_SUPPLY("EAR CP", CDC_A_NCP_EN, 4, 0, NULL, 0), + +- SND_SOC_DAPM_PGA("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphl_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHL", SND_SOC_NOPM, 0, 0, &hphl_mux), + SND_SOC_DAPM_MIXER("HPHL DAC", CDC_A_RX_HPH_L_PA_DAC_CTL, 3, 0, NULL, + 0), +- SND_SOC_DAPM_PGA("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphr_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHR", SND_SOC_NOPM, 0, 0, &hphr_mux), + SND_SOC_DAPM_MIXER("HPHR DAC", CDC_A_RX_HPH_R_PA_DAC_CTL, 3, 0, NULL, + 0), +@@ -1259,7 +1389,9 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) + } + + static const struct of_device_id pm8916_wcd_analog_spmi_match_table[] = { +- { .compatible = "qcom,pm8916-wcd-analog-codec", }, ++ { .compatible = "qcom,pm8916-wcd-analog-codec", .data = &pm8916_data }, ++ { .compatible = "qcom,pm8950-wcd-analog-codec", .data = &pm8950_data }, ++ { .compatible = "qcom,pm8953-wcd-analog-codec", .data = &pm8953_data }, + { } + }; + +diff --git a/sound/soc/codecs/mt6357.c b/sound/soc/codecs/mt6357.c +--- a/sound/soc/codecs/mt6357.c ++++ b/sound/soc/codecs/mt6357.c +@@ -1834,8 +1834,8 @@ static int mt6357_platform_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id mt6357_platform_ids[] = { +- {"mt6357-sound", 0}, +- { /* sentinel */ }, ++ { .name = "mt6357-sound" }, ++ { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(platform, mt6357_platform_ids); + +diff --git a/sound/soc/codecs/mt6359-accdet.c b/sound/soc/codecs/mt6359-accdet.c +--- a/sound/soc/codecs/mt6359-accdet.c ++++ b/sound/soc/codecs/mt6359-accdet.c +@@ -6,6 +6,7 @@ + // Author: Argus Lin + // + ++#include + #include + #include + #include +@@ -398,14 +399,13 @@ static void mt6359_accdet_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, accdet_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + priv->pre_accdet_status = priv->accdet_status; + check_jack_btn_type(priv); + + if (priv->jack_plugged && + priv->pre_accdet_status != priv->accdet_status) + mt6359_accdet_jack_report(priv); +- mutex_unlock(&priv->res_lock); + } + + static void mt6359_accdet_jd_work(struct work_struct *work) +@@ -416,7 +416,7 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, jd_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + if (priv->jd_sts == M_PLUG_IN) { + priv->jack_plugged = true; + +@@ -450,7 +450,6 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + + if (priv->caps & ACCDET_PMIC_EINT_IRQ) + recover_eint_setting(priv); +- mutex_unlock(&priv->res_lock); + } + + static irqreturn_t mt6359_accdet_irq(int irq, void *data) +@@ -459,7 +458,7 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + unsigned int irq_val = 0, val = 0, value = 0; + int ret; + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + regmap_read(priv->regmap, ACCDET_IRQ_ADDR, &irq_val); + + if (irq_val & ACCDET_IRQ_MASK_SFT) { +@@ -474,7 +473,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + 1000); + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -498,7 +496,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -521,7 +518,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -540,7 +536,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + + queue_work(priv->jd_workqueue, &priv->jd_work); + } +- mutex_unlock(&priv->res_lock); + + return IRQ_HANDLED; + } +diff --git a/sound/soc/codecs/ntpfw.c b/sound/soc/codecs/ntpfw.c +--- a/sound/soc/codecs/ntpfw.c ++++ b/sound/soc/codecs/ntpfw.c +@@ -89,7 +89,7 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + { + struct device *dev = &i2c->dev; + const struct ntpfw_chunk *chunk; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + const u8 *data; + size_t leftover; + int ret; +@@ -101,10 +101,8 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + return ret; + } + +- if (!ntpfw_verify(dev, fw->data, fw->size, magic)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify(dev, fw->data, fw->size, magic)) ++ return -EINVAL; + + data = fw->data + sizeof(struct ntpfw_header); + leftover = fw->size - sizeof(struct ntpfw_header); +@@ -112,23 +110,18 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + while (leftover) { + chunk = (struct ntpfw_chunk *)data; + +- if (!ntpfw_verify_chunk(dev, chunk, leftover)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify_chunk(dev, chunk, leftover)) ++ return -EINVAL; + + ret = ntpfw_send_chunk(i2c, chunk); + if (ret) +- goto done; ++ return ret; + + data += be16_to_cpu(chunk->length) + sizeof(*chunk); + leftover -= be16_to_cpu(chunk->length) + sizeof(*chunk); + } + +-done: +- release_firmware(fw); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(ntpfw_load); + +diff --git a/sound/soc/codecs/ntpfw.h b/sound/soc/codecs/ntpfw.h +--- a/sound/soc/codecs/ntpfw.h ++++ b/sound/soc/codecs/ntpfw.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0-only */ +-/** ++/* + * ntpfw.h - Firmware helper functions for Neofidelity codecs + * + * Copyright (c) 2024, SaluteDevices. All Rights Reserved. +@@ -13,10 +13,11 @@ + /** + * ntpfw_load - load firmware to amplifier over i2c interface. + * +- * @i2c Pointer to amplifier's I2C client. +- * @name Firmware file name. +- * @magic Magic number to validate firmware. +- * @return 0 or error code upon error. ++ * @i2c: Pointer to amplifier's I2C client. ++ * @name: Firmware file name. ++ * @magic: Magic number to validate firmware. ++ * ++ * Returns: 0 or error code upon error. + */ + int ntpfw_load(struct i2c_client *i2c, const char *name, const u32 magic); + +diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c +--- a/sound/soc/codecs/pcm1681.c ++++ b/sound/soc/codecs/pcm1681.c +@@ -199,10 +199,17 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, + return pcm1681_set_deemph(component); + } + ++static const u64 pcm1681_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1681_dai_ops = { + .set_fmt = pcm1681_set_dai_fmt, + .hw_params = pcm1681_hw_params, + .mute_stream = pcm1681_mute, ++ .auto_selectable_formats = &pcm1681_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm1754.c b/sound/soc/codecs/pcm1754.c +--- a/sound/soc/codecs/pcm1754.c ++++ b/sound/soc/codecs/pcm1754.c +@@ -78,10 +78,16 @@ static int pcm1754_mute_stream(struct snd_soc_dai *dai, int mute, int stream) + return 0; + } + ++static const u64 pcm1754_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm1754_dai_ops = { + .set_fmt = pcm1754_set_dai_fmt, + .hw_params = pcm1754_hw_params, + .mute_stream = pcm1754_mute_stream, ++ .auto_selectable_formats = &pcm1754_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver pcm1754_dai = { +diff --git a/sound/soc/codecs/pcm1789.c b/sound/soc/codecs/pcm1789.c +--- a/sound/soc/codecs/pcm1789.c ++++ b/sound/soc/codecs/pcm1789.c +@@ -164,11 +164,18 @@ static int pcm1789_trigger(struct snd_pcm_substream *substream, int cmd, + return ret; + } + ++static const u64 pcm1789_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1789_dai_ops = { + .set_fmt = pcm1789_set_dai_fmt, + .hw_params = pcm1789_hw_params, + .mute_stream = pcm1789_mute, + .trigger = pcm1789_trigger, ++ .auto_selectable_formats = &pcm1789_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c +--- a/sound/soc/codecs/pcm179x.c ++++ b/sound/soc/codecs/pcm179x.c +@@ -142,10 +142,16 @@ static int pcm179x_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm179x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm179x_dai_ops = { + .set_fmt = pcm179x_set_dai_fmt, + .hw_params = pcm179x_hw_params, + .mute_stream = pcm179x_mute, ++ .auto_selectable_formats = &pcm179x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm186x.c b/sound/soc/codecs/pcm186x.c +--- a/sound/soc/codecs/pcm186x.c ++++ b/sound/soc/codecs/pcm186x.c +@@ -473,11 +473,20 @@ static int pcm186x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + return 0; + } + ++static const u64 pcm186x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm186x_dai_ops = { + .set_sysclk = pcm186x_set_dai_sysclk, + .set_tdm_slot = pcm186x_set_tdm_slot, + .set_fmt = pcm186x_set_fmt, + .hw_params = pcm186x_hw_params, ++ .auto_selectable_formats = &pcm186x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver pcm1863_dai = { +diff --git a/sound/soc/codecs/pcm3060.c b/sound/soc/codecs/pcm3060.c +--- a/sound/soc/codecs/pcm3060.c ++++ b/sound/soc/codecs/pcm3060.c +@@ -164,10 +164,18 @@ static int pcm3060_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm3060_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm3060_dai_ops = { + .set_sysclk = pcm3060_set_sysclk, + .set_fmt = pcm3060_set_fmt, + .hw_params = pcm3060_hw_params, ++ .auto_selectable_formats = &pcm3060_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define PCM3060_DAI_RATES_ADC (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \ +diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c +--- a/sound/soc/codecs/pcm512x.c ++++ b/sound/soc/codecs/pcm512x.c +@@ -6,7 +6,7 @@ + * Copyright 2014 Linaro Ltd + */ + +- ++#include + #include + #include + #include +@@ -399,10 +399,9 @@ static int pcm512x_digital_playback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + ucontrol->value.integer.value[0] = !(pcm512x->mute & 0x4); + ucontrol->value.integer.value[1] = !(pcm512x->mute & 0x2); +- mutex_unlock(&pcm512x->mutex); + + return 0; + } +@@ -414,7 +413,7 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + int ret, changed = 0; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if ((pcm512x->mute & 0x4) == (ucontrol->value.integer.value[0] << 2)) { + pcm512x->mute ^= 0x4; +@@ -430,13 +429,10 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- mutex_unlock(&pcm512x->mutex); + return ret; + } + } + +- mutex_unlock(&pcm512x->mutex); +- + return changed; + } + +@@ -1465,7 +1461,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + int ret; + unsigned int mute_det; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if (mute) { + pcm512x->mute |= 0x1; +@@ -1475,7 +1471,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to set digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1488,7 +1484,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1499,18 +1495,24 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + 200, 10000); + } + +-unlock: +- mutex_unlock(&pcm512x->mutex); +- + return ret; + } + ++static const u64 pcm512x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops pcm512x_dai_ops = { + .startup = pcm512x_dai_startup, + .hw_params = pcm512x_hw_params, + .set_fmt = pcm512x_set_fmt, + .mute_stream = pcm512x_mute, + .set_bclk_ratio = pcm512x_set_bclk_ratio, ++ .auto_selectable_formats = &pcm512x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c +--- a/sound/soc/codecs/pcm6240.c ++++ b/sound/soc/codecs/pcm6240.c +@@ -12,6 +12,7 @@ + // Author: Shenghao Ding + // + ++#include + #include + #include + #include +@@ -605,7 +606,7 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + unsigned int reg = mc->reg; + unsigned int val; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + if (pcm_dev->chip_id == PCM1690) { + ret = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL, +@@ -613,18 +614,18 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read mode err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + val &= PCM1690_REG_MODE_CTRL_DAMS_MSK; + /* Set to wide-range mode, before using vol ctrl. */ + if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { + ucontrol->value.integer.value[0] = -25500; +- goto out; ++ return ret; + } + /* Set to fine mode, before using fine vol ctrl. */ + if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { + ucontrol->value.integer.value[0] = -12750; +- goto out; ++ return ret; + } + } + +@@ -632,15 +633,14 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + + val = (val >> shift) & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; + ucontrol->value.integer.value[0] = val; +-out: +- mutex_unlock(&pcm_dev->codec_lock); ++ + return ret; + } + +@@ -678,7 +678,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + unsigned int val, val_mask; + unsigned int reg = mc->reg; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + val = ucontrol->value.integer.value[0] & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; +@@ -702,7 +702,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + __func__, rc); + else + rc = 1; +- mutex_unlock(&pcm_dev->codec_lock); ++ + return rc; + } + +@@ -1577,10 +1577,10 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + { + struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(comp); + struct i2c_adapter *adap = pcm_dev->client->adapter; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret, i, j; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + pcm_dev->component = comp; + +@@ -1588,7 +1588,7 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + for (j = 0; j < 2; j++) { + ret = pcmdev_gain_ctrl_add(pcm_dev, i, j); + if (ret < 0) +- goto out; ++ return ret; + } + } + +@@ -1621,21 +1621,17 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + if (ret) { + dev_err(pcm_dev->dev, "%s: request %s err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } + + ret = pcmdev_regbin_ready(fw_entry, pcm_dev); + if (ret) { + dev_err(pcm_dev->dev, "%s: %s parse err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } +- ret = pcmdev_profile_ctrl_add(pcm_dev); +-out: +- release_firmware(fw_entry); + +- mutex_unlock(&pcm_dev->codec_lock); +- return ret; ++ return pcmdev_profile_ctrl_add(pcm_dev); + } + + +@@ -1645,9 +1641,8 @@ static void pcmdevice_comp_remove(struct snd_soc_component *codec) + + if (!pcm_dev) + return; +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_config_info_remove(pcm_dev); +- mutex_unlock(&pcm_dev->codec_lock); + } + + static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = { +@@ -1890,9 +1885,9 @@ static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream) + else + block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type); +- mutex_unlock(&pcm_dev->codec_lock); ++ + return 0; + } + +diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c +--- a/sound/soc/codecs/peb2466.c ++++ b/sound/soc/codecs/peb2466.c +@@ -6,6 +6,7 @@ + // + // Author: Herve Codina + ++#include + #include + #include + #include +@@ -1537,17 +1538,14 @@ static int peb2466_fw_parse(struct snd_soc_component *component, + + static int peb2466_load_coeffs(struct snd_soc_component *component, const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, component->dev); + if (ret) + return ret; + +- ret = peb2466_fw_parse(component, fw->data, fw->size); +- release_firmware(fw); +- +- return ret; ++ return peb2466_fw_parse(component, fw->data, fw->size); + } + + static int peb2466_component_probe(struct snd_soc_component *component) +@@ -1704,13 +1702,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + * So, a specific cache value is used. + */ + +- mutex_lock(&peb2466->gpio.lock); ++ guard(mutex)(&peb2466->gpio.lock); + + cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg); +- if (!cache) { +- ret = -EINVAL; +- goto end; +- } ++ if (!cache) ++ return -EINVAL; + + tmp = *cache; + tmp &= ~mask; +@@ -1718,14 +1714,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + + ret = regmap_write(peb2466->regmap, xr_reg, tmp); + if (ret) +- goto end; ++ return ret; + + *cache = tmp; +- ret = 0; + +-end: +- mutex_unlock(&peb2466->gpio.lock); +- return ret; ++ return 0; + } + + static int peb2466_chip_gpio_set(struct gpio_chip *c, unsigned int offset, +diff --git a/sound/soc/codecs/pm4125-sdw.c b/sound/soc/codecs/pm4125-sdw.c +--- a/sound/soc/codecs/pm4125-sdw.c ++++ b/sound/soc/codecs/pm4125-sdw.c +@@ -464,10 +464,16 @@ static int __maybe_unused pm4125_sdw_runtime_suspend(struct device *dev) + static int __maybe_unused pm4125_sdw_runtime_resume(struct device *dev) + { + struct pm4125_sdw_priv *priv = dev_get_drvdata(dev); ++ int ret; + + if (priv->regmap) { + regcache_cache_only(priv->regmap, false); +- regcache_sync(priv->regmap); ++ ret = regcache_sync(priv->regmap); ++ if (ret) { ++ regcache_cache_only(priv->regmap, true); ++ regcache_mark_dirty(priv->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c +--- a/sound/soc/codecs/pm4125.c ++++ b/sound/soc/codecs/pm4125.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1295,7 +1296,7 @@ static const struct irq_domain_ops pm4125_domain_ops = { + + static int pm4125_irq_init(struct pm4125_priv *pm4125, struct device *dev) + { +- pm4125->virq = irq_domain_add_linear(NULL, 1, &pm4125_domain_ops, NULL); ++ pm4125->virq = irq_domain_create_linear(NULL, 1, &pm4125_domain_ops, NULL); + if (!(pm4125->virq)) { + dev_err(dev, "%s: Failed to add IRQ domain\n", __func__); + return -EINVAL; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -784,7 +784,12 @@ static int rt1017_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1017->regmap, false); +- regcache_sync(rt1017->regmap); ++ ret = regcache_sync(rt1017->regmap); ++ if (ret) { ++ regcache_cache_only(rt1017->regmap, true); ++ regcache_mark_dirty(rt1017->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1316-sdw.c b/sound/soc/codecs/rt1316-sdw.c +--- a/sound/soc/codecs/rt1316-sdw.c ++++ b/sound/soc/codecs/rt1316-sdw.c +@@ -756,7 +756,12 @@ static int rt1316_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1316->regmap, false); +- regcache_sync(rt1316->regmap); ++ ret = regcache_sync(rt1316->regmap); ++ if (ret) { ++ regcache_cache_only(rt1316->regmap, true); ++ regcache_mark_dirty(rt1316->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -830,7 +830,12 @@ static int rt1318_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1318->regmap, false); +- regcache_sync(rt1318->regmap); ++ ret = regcache_sync(rt1318->regmap); ++ if (ret) { ++ regcache_cache_only(rt1318->regmap, true); ++ regcache_mark_dirty(rt1318->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -410,6 +410,402 @@ static const struct reg_sequence rt1321_blind_write[] = { + { 0x41001988, 0x03 }, + }; + ++static const struct reg_sequence rt1321_va1_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x0000d73c, 0xb7 }, ++ { 0x0000d73d, 0xd7 }, ++ { 0x0000d73e, 0x00 }, ++ { 0x0000d73f, 0x10 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2df80, 0x00 }, ++ { 0x3fc2df81, 0x00 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x0000d541, 0x40 }, ++ { 0x0000d486, 0x43 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x20009012, 0x00 }, ++ { 0x0000dd08, 0x17 }, ++ { 0x0000dd09, 0x0e }, ++ { 0x0000dd0a, 0x17 }, ++ { 0x0000dd0b, 0x0e }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000db00, 0x00 }, ++ { 0x0000db01, 0x00 }, ++ { 0x0000db02, 0x73 }, ++ { 0x0000db03, 0x00 }, ++ { 0x0000db04, 0x00 }, ++ { 0x0000db05, 0x00 }, ++ { 0x0000db06, 0x00 }, ++ { 0x0000db07, 0x00 }, ++ { 0x0000db08, 0x7f }, ++ { 0x0000db09, 0x00 }, ++ { 0x0000db1a, 0x00 }, ++ { 0x0000db1b, 0x00 }, ++ { 0x0000db19, 0x00 }, ++}; ++ ++static const struct reg_sequence rt1321_va2_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x10008000, 0x67 }, ++ { 0x10008001, 0x80 }, ++ { 0x10008002, 0x00 }, ++ { 0x10008003, 0x00 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x0000d486, 0x43 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfa3, 0x00 }, ++ { 0x3fc2dfa2, 0x00 }, ++ { 0x3fc2dfa1, 0x00 }, ++ { 0x3fc2dfa0, 0x00 }, ++ { 0x3fc2df80, 0x10 }, ++ { 0x3fc2df81, 0x20 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x3fc2df84, 0x50 }, ++ { 0x3fc2df85, 0x19 }, ++ { 0x3fc2df86, 0x00 }, ++ { 0x3fc2df87, 0x00 }, ++ { 0x3fc2df88, 0x52 }, ++ { 0x3fc2df89, 0x23 }, ++ { 0x3fc2df8a, 0x00 }, ++ { 0x3fc2df8b, 0x00 }, ++ { 0x3fc2df8c, 0xe0 }, ++ { 0x3fc2df8d, 0x2e }, ++ { 0x3fc2df8e, 0x00 }, ++ { 0x3fc2df8f, 0x00 }, ++ { 0x3fc2df90, 0xe0 }, ++ { 0x3fc2df91, 0x2e }, ++ { 0x3fc2df92, 0x00 }, ++ { 0x3fc2df93, 0x00 }, ++ { 0x3fc2df94, 0x01 }, ++ { 0x3fc2df95, 0x08 }, ++ { 0x3fc2df96, 0x00 }, ++ { 0x3fc2df97, 0x00 }, ++ { 0x3fc2df40, 0x80 }, ++ { 0x3fc2df41, 0xbb }, ++ { 0x3fc2df42, 0x00 }, ++ { 0x3fc2df43, 0x00 }, ++ { 0x3fc2df44, 0xc0 }, ++ { 0x3fc2df45, 0x99 }, ++ { 0x3fc2df46, 0x01 }, ++ { 0x3fc2df47, 0x00 }, ++ { 0x3fc2df48, 0x00 }, ++ { 0x3fc2df49, 0x00 }, ++ { 0x3fc2df4a, 0x00 }, ++ { 0x3fc2df4b, 0x00 }, ++ { 0x3fc2df4c, 0x00 }, ++ { 0x3fc2df4d, 0x00 }, ++ { 0x3fc2df4e, 0x00 }, ++ { 0x3fc2df4f, 0x00 }, ++ { 0x3fc2df50, 0x01 }, ++ { 0x3fc2df51, 0x00 }, ++ { 0x3fc2df52, 0x00 }, ++ { 0x3fc2df53, 0x00 }, ++ { 0x3fc2df54, 0x01 }, ++ { 0x3fc2df55, 0x00 }, ++ { 0x3fc2df56, 0x00 }, ++ { 0x3fc2df57, 0x00 }, ++ { 0x3fc2df58, 0x00 }, ++ { 0x3fc2df59, 0x00 }, ++ { 0x3fc2df5a, 0x00 }, ++ { 0x3fc2df5b, 0x00 }, ++ { 0x3fc2df5c, 0x01 }, ++ { 0x3fc2df5d, 0x00 }, ++ { 0x3fc2df5e, 0x00 }, ++ { 0x3fc2df5f, 0x00 }, ++ { 0x3fc2df60, 0x00 }, ++ { 0x3fc2df61, 0x00 }, ++ { 0x3fc2df62, 0x00 }, ++ { 0x3fc2df63, 0x00 }, ++ { 0x3fc2df64, 0x00 }, ++ { 0x3fc2df65, 0x00 }, ++ { 0x3fc2df66, 0x00 }, ++ { 0x3fc2df67, 0x10 }, ++ { 0x3fc2df68, 0x01 }, ++ { 0x3fc2df69, 0x00 }, ++ { 0x3fc2df6a, 0x00 }, ++ { 0x3fc2df6b, 0x00 }, ++ { 0x3fc2df6c, 0x01 }, ++ { 0x3fc2df6d, 0x00 }, ++ { 0x3fc2df6e, 0x00 }, ++ { 0x3fc2df6f, 0x00 }, ++ { 0x3fc2df70, 0x04 }, ++ { 0x3fc2df71, 0x00 }, ++ { 0x3fc2df72, 0x00 }, ++ { 0x3fc2df73, 0x00 }, ++ { 0x3fc2df74, 0x01 }, ++ { 0x3fc2df75, 0x00 }, ++ { 0x3fc2df76, 0x00 }, ++ { 0x3fc2df77, 0x00 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000dd0b, 0x0d }, ++ { 0x0000dd0a, 0xff }, ++ { 0x0000dd09, 0x0d }, ++ { 0x0000dd08, 0xff }, ++ { 0x0000c5fb, 0x12 }, ++ { 0x0000c570, 0x08 }, ++}; ++ + static const struct reg_default rt1320_reg_defaults[] = { + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_FU113, RT1320_SDCA_CTL_FU_MUTE, CH_01), 0x01 }, +@@ -440,6 +836,7 @@ static const struct reg_default rt1320_mbq_defaults[] = { + static bool rt1320_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000 ... 0xc086: + case 0xc400 ... 0xc409: + case 0xc480 ... 0xc48f: +@@ -497,10 +894,15 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + case RT1321_PATCH_MAIN_VER ... RT1321_PATCH_BETA_VER: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x20003000 ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x20005800 ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x20009000 ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc000c0 ... 0x3fc2dfc8: + case 0x3fe00000 ... 0x3fe36fff: + /* 0x40801508/0x40801809/0x4080180a/0x40801909/0x4080190a */ +@@ -545,6 +947,7 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000: + case 0xc003: + case 0xc081: +@@ -596,15 +999,20 @@ static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + case 0x1000c000 ... 0x1000dfff: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x2000300e ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x2000580e ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x2000900e ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc2ab80 ... 0x3fc2ac4c: + case 0x3fc2b780: + case 0x3fc2bf80 ... 0x3fc2bf83: + case 0x3fc2bfc0 ... 0x3fc2bfc8: +- case 0x3fc2d300 ... 0x3fc2d354: ++ case 0x3fc2d300 ... 0x3fc2d3cc: + case 0x3fc2dfc0 ... 0x3fc2dfc8: + case 0x3fe2e000 ... 0x3fe2e003: + case SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_ACTUAL_POWER_STATE, 0): +@@ -892,6 +1300,42 @@ static int rt1320_check_fw_ready(struct rt1320_sdw_priv *rt1320) + return 0; + } + ++static int rt1320_dspfw_status(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ unsigned int fw_status_addr, fw_ready; ++ unsigned int dspfw_run; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ fw_status_addr = RT1320_DSPFW_STATUS_ADDR; ++ break; ++ case RT1321_DEV_ID: ++ fw_status_addr = RT1321_DSPFW_STATUS_ADDR; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); ++ fw_ready &= 0x1; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_read(rt1320->regmap, 0xf01e, &dspfw_run); ++ dspfw_run &= 0x1; ++ fw_ready = (!dspfw_run && fw_ready); ++ } ++ ++ if (fw_ready) { ++ dev_dbg(dev, "%s, DSP FW was already\n", __func__); ++ return 1; ++ } ++ ++ dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); ++ return 0; ++} ++ + static int rt1320_check_power_state_ready(struct rt1320_sdw_priv *rt1320, enum rt1320_power_state ps) + { + struct device *dev = &rt1320->sdw_slave->dev; +@@ -982,6 +1426,9 @@ static int rt1320_fw_param_protocol(struct rt1320_sdw_priv *rt1320, enum rt1320_ + if (!tempbuf) + return -ENOMEM; + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) ++ paramid += 0x0bff0000; ++ + paramhr.moudleid = 1; + paramhr.commandtype = cmdid; + /* 8 is "sizeof(paramid) + sizeof(paramlength)" */ +@@ -1120,45 +1567,73 @@ static void rt1320_calc_r0(struct rt1320_sdw_priv *rt1320) + l_calir0, l_calir0_lo, r_calir0, r_calir0_lo); + } + ++static int rt1320_pilot_tone_output(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ int l_targetpostgain, r_targetpostgain; ++ unsigned long long factor = (1 << 12); ++ int l_pilotgain[9], r_pilotgain[9]; ++ const int postgain_step = 234; ++ int targetGain; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ targetGain = -320000; ++ break; ++ case RT1321_DEV_ID: ++ targetGain = -420000; ++ if (rt1320->version_id == RT1321_VA0) ++ targetGain = -320000; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 70, &l_pilotgain[0], sizeof(l_pilotgain)); ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 71, &r_pilotgain[0], sizeof(r_pilotgain)); ++ dev_dbg(dev, "%s, LR pilotgain %d, %d\n", __func__, l_pilotgain[2], r_pilotgain[2]); ++ ++ /* calculate pilot tone gain */ ++ l_pilotgain[2] = (l_pilotgain[2] * 10000) / factor; ++ r_pilotgain[2] = (r_pilotgain[2] * 10000) / factor; ++ ++ /* calculate post gain to meet target gain */ ++ l_targetpostgain = abs(targetGain - l_pilotgain[2]) / postgain_step; ++ r_targetpostgain = abs(targetGain - r_pilotgain[2]) / postgain_step; ++ l_targetpostgain = 0xfff - l_targetpostgain; ++ r_targetpostgain = 0xfff - r_targetpostgain; ++ dev_dbg(dev, "%s, LR targetpostgain=0x%x, 0x%x\n", __func__, l_targetpostgain, r_targetpostgain); ++ ++ regmap_write(rt1320->regmap, 0xdd0b, (l_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd0a, l_targetpostgain & 0xff); ++ regmap_write(rt1320->regmap, 0xdd09, (r_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd08, r_targetpostgain & 0xff); ++ ++ return 0; ++} ++ + static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = &rt1320->sdw_slave->dev; + struct rt1320_datafixpoint audfixpoint[2]; + unsigned int reg_c5fb, reg_c570, reg_cd00; +- unsigned int vol_reg[4], fw_ready; ++ unsigned int vol_reg[4]; + unsigned long long l_meanr0, r_meanr0; +- unsigned int fw_status_addr; + int l_re[5], r_re[5]; + int ret, tmp; + unsigned long long factor = (1 << 27); + unsigned short l_advancegain, r_advancegain; + unsigned int delay_s = 7; /* delay seconds for the calibration */ ++ int dspfw_status; + + if (!rt1320->component) + return; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- +- /* set volume 0dB */ + regmap_read(rt1320->regmap, 0xdd0b, &vol_reg[3]); + regmap_read(rt1320->regmap, 0xdd0a, &vol_reg[2]); + regmap_read(rt1320->regmap, 0xdd09, &vol_reg[1]); + regmap_read(rt1320->regmap, 0xdd08, &vol_reg[0]); +- regmap_write(rt1320->regmap, 0xdd0b, 0x0f); +- regmap_write(rt1320->regmap, 0xdd0a, 0xff); +- regmap_write(rt1320->regmap, 0xdd09, 0x0f); +- regmap_write(rt1320->regmap, 0xdd08, 0xff); +- + regmap_read(rt1320->regmap, 0xc5fb, ®_c5fb); + regmap_read(rt1320->regmap, 0xc570, ®_c570); + regmap_read(rt1320->regmap, 0xcd00, ®_cd00); +@@ -1171,9 +1646,8 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); + goto _finish_; + } +@@ -1184,8 +1658,19 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- if (rt1320->dev_id == RT1320_DEV_ID) +- regmap_write(rt1320->regmap, 0xc5fb, 0x00); ++ /* fine tune pilot tone output */ ++ ret = rt1320_pilot_tone_output(rt1320); ++ if (ret < 0) { ++ dev_dbg(dev, "%s, Failed to tune pilot tone output\n", __func__); ++ goto _finish_; ++ } ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x12); ++ } ++ ++ regmap_write(rt1320->regmap, 0xc5fb, 0x00); + regmap_write(rt1320->regmap, 0xc570, 0x0b); + regmap_write(rt1320->regmap, 0xcd00, 0xc5); + +@@ -1246,6 +1731,11 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x03); + ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ } ++ + /* advance gain will be set when R0 load, not here */ + regmap_write(rt1320->regmap, 0xdd0b, vol_reg[3]); + regmap_write(rt1320->regmap, 0xdd0a, vol_reg[2]); +@@ -1296,7 +1786,7 @@ static int rt1320_r0_cali_put(struct snd_kcontrol *kcontrol, + static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + { + struct sdw_slave *slave = rt1320->sdw_slave; +- const struct firmware *patch; ++ const struct firmware *patch __free(firmware) = NULL; + const char *filename; + unsigned int addr, val, min_addr, max_addr; + const unsigned char *ptr; +@@ -1312,6 +1802,9 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + max_addr = 0x10007fff; + break; + case RT1321_DEV_ID: ++ if (rt1320->version_id == RT1321_VA2) ++ return; ++ + filename = RT1321_VA_MCU_PATCH; + min_addr = 0x10008000; + max_addr = 0x10008fff; +@@ -1347,17 +1840,15 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + + if (addr > max_addr || addr < min_addr) { + dev_err(&slave->dev, "%s: the address 0x%x is wrong", __func__, addr); +- goto _exit_; ++ return; + } + if (val > 0xff) { + dev_err(&slave->dev, "%s: the value 0x%x is wrong", __func__, val); +- goto _exit_; ++ return; + } + regmap_write(rt1320->regmap, addr, val); + } + } +-_exit_: +- release_firmware(patch); + } + } + +@@ -1382,30 +1873,17 @@ static void rt1320_vab_preset(struct rt1320_sdw_priv *rt1320) + static void rt1320_t0_load(struct rt1320_sdw_priv *rt1320, unsigned int l_t0, unsigned int r_t0) + { + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int factor = (1 << 22), fw_ready; ++ unsigned int factor = (1 << 22); + int l_t0_data[38], r_t0_data[38]; +- unsigned int fw_status_addr; +- +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } ++ int dspfw_status; + + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_warn(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _exit_; + } +@@ -1444,7 +1922,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + struct device *dev = &rt1320->sdw_slave->dev; + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; +- const struct firmware *rae_fw = NULL; ++ const struct firmware *rae_fw __free(firmware) = NULL; + unsigned int fw_offset; + unsigned char *fw_data; + unsigned char *param_data; +@@ -1483,22 +1961,46 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + request_firmware(&rae_fw, rae_filename, dev); + if (rae_fw) { + +- /* RAE CRC clear */ +- regmap_write(rt1320->regmap, 0xe80b, 0x0f); +- +- /* RAE stop & CRC disable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); +- +- while (--retry) { +- regmap_read(rt1320->regmap, 0xe83f, &value); +- if (value & 0x40) +- break; +- usleep_range(1000, 1100); +- } +- if (!retry && !(value & 0x40)) { +- dev_err(dev, "%s: RAE is not ready to load\n", __func__); +- release_firmware(rae_fw); +- return -ETIMEDOUT; ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0xe80b, 0x0f); ++ /* RAE stop & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0xe83f, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0x2000300e, 0xc0); ++ regmap_write(rt1320->regmap, 0x2000300f, 0x0f); ++ /* RAE stop & Phase sync & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0xfe, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x00); ++ /* check whether write state is ready */ ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0x20003043, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; + } + + dev_dbg(dev, "%s, rae_fw size=0x%zx\n", __func__, rae_fw->size); +@@ -1551,7 +2053,6 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(rae_fw); + + } else { + dev_err(dev, "%s: Failed to load %s firmware\n", __func__, rae_filename); +@@ -1559,18 +2060,34 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + goto _exit_; + } + +- /* RAE CRC enable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); +- +- /* RAE update */ +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); +- +- /* RAE run */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); +- +- regmap_read(rt1320->regmap, 0xe80b, &value); +- dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); ++ regmap_read(rt1320->regmap, 0xe80b, &value); ++ dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0x30, 0x30); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x01); ++ /* Phase sync eanble */ ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ break; ++ } + + rt1320->rae_update_done = true; + +@@ -1598,32 +2115,21 @@ struct rt1320_dspfwheader { + + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt1320->component); + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int val, i, fw_offset, fw_ready; +- unsigned int fw_status_addr; ++ unsigned int val, i, fw_offset; + struct rt1320_dspfwheader *fwheader; + struct rt1320_imageinfo *ptr_img; + struct sdw_bpt_section sec[10]; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + unsigned char *fw_data; + bool dev_fw_match = false; + static const char hdr_sig[] = "AFX"; + unsigned int hdr_size = 0; + const char *dmi_vendor, *dmi_product, *dmi_sku; + int len_vendor, len_product, len_sku; ++ unsigned char boot_mode = 0; /* 0: from RAM; 1: from ROM */ ++ unsigned char has_0x3fc00000 = 0; + char filename[512]; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- + dmi_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + dmi_product = dmi_get_system_info(DMI_PRODUCT_NAME); + dmi_sku = dmi_get_system_info(DMI_PRODUCT_SKU); +@@ -1653,17 +2159,12 @@ struct rt1320_dspfwheader { + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (fw_ready) { ++ if (rt1320_dspfw_status(rt1320)) { + dev_dbg(dev, "%s, DSP FW was already\n", __func__); + rt1320->fw_load_done = true; + goto _exit_; + } + +- /* change to IRAM */ +- regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); +- + request_firmware(&fw, filename, dev); + if (fw) { + fwheader = (struct rt1320_dspfwheader *)fw->data; +@@ -1672,7 +2173,6 @@ struct rt1320_dspfwheader { + + if (fwheader->sync != 0x0a1c5679) { + dev_err(dev, "%s: FW sync error\n", __func__); +- release_firmware(fw); + goto _exit_; + } + +@@ -1709,9 +2209,11 @@ struct rt1320_dspfwheader { + dev_fw_match = true; + break; + case RT1321_DEV_ID: +- if (ptr_img->addr == 0x3fc00000) +- if (fw_data[9] == '1') ++ if (ptr_img->addr == 0x3fc00000) { ++ if (fw_data[7] == '1') + dev_fw_match = true; ++ has_0x3fc00000 = 1; ++ } + break; + default: + dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +@@ -1721,6 +2223,22 @@ struct rt1320_dspfwheader { + fw_offset += ptr_img->size; + } + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) { ++ /* ++ * For 1321 VA2, if the FW doesn't include the section for address 0x3fc00000, ++ * it means the FW will boot from ROM and force dev_fw_match to true to download FW by BRA. ++ */ ++ if (!has_0x3fc00000) { ++ boot_mode = 1; ++ dev_fw_match = true; ++ } ++ dev_dbg(dev, "%s: Boot from %s for VA2\n", __func__, (boot_mode ? "ROM" : "RAM")); ++ } ++ ++ /* change to IRAM */ ++ if (!boot_mode) ++ regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); ++ + if (dev_fw_match) { + dev_dbg(dev, "%s, starting BRA downloading FW..\n", __func__); + rt1320->bra_msg.dev_num = rt1320->sdw_slave->dev_num; +@@ -1732,7 +2250,6 @@ struct rt1320_dspfwheader { + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(fw); + + if (!dev_fw_match) { + dev_err(dev, "%s: FW file doesn't match to device\n", __func__); +@@ -1743,24 +2260,39 @@ struct rt1320_dspfwheader { + goto _exit_; + } + +- /* run RAM code */ +- regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); +- val |= 0x8; +- regmap_write(rt1320->regmap, 0x3fc2bfc0, val); +- +- /* clear frame counter */ + switch (rt1320->dev_id) { + case RT1320_DEV_ID: ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2bfc0, val); ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2bfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc8, 0x00); + break; + case RT1321_DEV_ID: ++ if (!boot_mode) { ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2dfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2dfc0, val); ++ } ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2dfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc8, 0x00); ++ /* enable handshake */ ++ regmap_write(rt1320->regmap, 0x3fc2dfc4, 0x00); ++ regmap_write(rt1320->regmap, 0xd470, 0xad); ++ /* minimum phase settings */ ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ regmap_write(rt1320->regmap, 0x20003003, 0x31); ++ regmap_update_bits(rt1320->regmap, 0x20003002, 0x40, 0x00); ++ regmap_write(rt1320->regmap, 0xc5b3, 0x01); ++ regmap_write(rt1320->regmap, 0xc052, 0x11); + break; + } + +@@ -1843,14 +2375,35 @@ static void rt1320_vc_preset(struct rt1320_sdw_priv *rt1320) + + static void rt1321_preset(struct rt1320_sdw_priv *rt1320) + { ++ const struct reg_sequence *blindwrite; + unsigned int i, reg, val, delay; ++ unsigned int array_size; + +- for (i = 0; i < ARRAY_SIZE(rt1321_blind_write); i++) { +- reg = rt1321_blind_write[i].reg; +- val = rt1321_blind_write[i].def; +- delay = rt1321_blind_write[i].delay_us; ++ switch (rt1320->version_id) { ++ case RT1321_VA0: ++ blindwrite = rt1321_blind_write; ++ array_size = ARRAY_SIZE(rt1321_blind_write); ++ break; ++ case RT1321_VA1: ++ blindwrite = rt1321_va1_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va1_blind_write); ++ break; ++ case RT1321_VA2: ++ blindwrite = rt1321_va2_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va2_blind_write); ++ break; ++ default: ++ dev_err(&rt1320->sdw_slave->dev, "%s: Unknown version ID %d\n", ++ __func__, rt1320->version_id); ++ return; ++ } + +- if (reg == 0x3fc2dfc3) ++ for (i = 0; i < array_size; i++) { ++ reg = blindwrite[i].reg; ++ val = blindwrite[i].def; ++ delay = blindwrite[i].delay_us; ++ ++ if (reg == 0x1000cd56) + rt1320_load_mcu_patch(rt1320); + + regmap_write(rt1320->regmap, reg, val); +@@ -1892,6 +2445,24 @@ static int rt1320_io_init(struct device *dev, struct sdw_slave *slave) + regmap_read(rt1320->regmap, RT1320_DEV_ID_0, &val); + regmap_read(rt1320->regmap, RT1320_DEV_ID_1, &tmp); + rt1320->dev_id = (val << 8) | tmp; ++ ++ /* This is a workaround that reads the value twice to obtain the correct result. */ ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ val = (val << 8) | tmp; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ if (rt1320->version_id == 0x01) ++ rt1320->version_id = RT1321_VA2; ++ else if (val == RT1321_DEV_HV_VA0_ID) ++ rt1320->version_id = RT1321_VA0; ++ else if (val == RT1321_DEV_HV_VA1_ID) ++ rt1320->version_id = RT1321_VA1; ++ else ++ dev_err(dev, "%s: Unknown version ID 0x%x for RT1321\n", __func__, rt1320->version_id); ++ } + } + + regmap_read(rt1320->regmap, +@@ -2326,25 +2897,12 @@ static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); + static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = regmap_get_device(rt1320->regmap); +- unsigned int fw_status_addr; +- unsigned int fw_ready; ++ int dspfw_status; + int ret = 0; + + if (!rt1320->r0_l_reg || !rt1320->r0_r_reg) + return -EINVAL; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return -EINVAL; +- } +- + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + ret = rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); +@@ -2353,9 +2911,8 @@ static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + goto _timeout_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _timeout_; + } +@@ -2458,7 +3015,7 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!rt1320->hw_init) + return 0; + +- ret = pm_runtime_resume(component->dev); ++ ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + +@@ -2469,6 +3026,8 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!ucontrol->value.integer.value[0]) + rt1320->fw_load_done = false; + ++ pm_runtime_mark_last_busy(component->dev); ++ pm_runtime_put_autosuspend(component->dev); + return 0; + } + +@@ -2819,6 +3378,37 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int rt1320_bus_config(struct sdw_slave *slave, ++ struct sdw_bus_params *params) ++{ ++ struct rt1320_sdw_priv *rt1320 = dev_get_drvdata(&slave->dev); ++ unsigned int clk_base; ++ ++ regmap_read(rt1320->regmap, 0x004d, &clk_base); ++ dev_dbg(&rt1320->sdw_slave->dev, "%s clk_base=%x", __func__, clk_base); ++ ++ switch (clk_base) { ++ case RT1320_CLK_FREQ_19_2_MHZ: ++ case RT1320_CLK_FREQ_24MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x04); ++ regmap_write(rt1320->regmap, 0xc601, 0x83); ++ regmap_write(rt1320->regmap, 0xc602, 0x1f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ case RT1320_CLK_FREQ_24_576MHZ: ++ case RT1320_CLK_FREQ_22_5792MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x07); ++ regmap_write(rt1320->regmap, 0xc601, 0x80); ++ regmap_write(rt1320->regmap, 0xc602, 0x0f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + /* + * slave_ops: callbacks for get_clock_stop_mode, clock_stop and + * port_prep are not defined for now +@@ -2826,12 +3416,13 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + static const struct sdw_slave_ops rt1320_slave_ops = { + .read_prop = rt1320_read_prop, + .update_status = rt1320_update_status, ++ .bus_config = rt1320_bus_config, + }; + + static int rt1320_sdw_component_probe(struct snd_soc_component *component) + { +- int ret; + struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); ++ int ret; + + rt1320->component = component; + +@@ -3062,10 +3653,23 @@ static int rt1320_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1320->regmap, false); +- regcache_sync(rt1320->regmap); ++ ret = regcache_sync(rt1320->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt1320->mbq_regmap, false); +- regcache_sync(rt1320->mbq_regmap); ++ ret = regcache_sync(rt1320->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt1320->regmap, true); ++ regcache_cache_only(rt1320->mbq_regmap, true); ++ regcache_mark_dirty(rt1320->regmap); ++ regcache_mark_dirty(rt1320->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt1320_pm = { +diff --git a/sound/soc/codecs/rt1320-sdw.h b/sound/soc/codecs/rt1320-sdw.h +--- a/sound/soc/codecs/rt1320-sdw.h ++++ b/sound/soc/codecs/rt1320-sdw.h +@@ -17,12 +17,17 @@ + + #define RT1320_DEV_ID 0x6981 + #define RT1321_DEV_ID 0x7045 ++#define RT1321_DEV_HV_VA0_ID 0x6997 ++#define RT1321_DEV_HV_VA1_ID 0x7071 + + /* imp-defined registers */ + #define RT1320_DEV_VERSION_ID_1 0xc404 + #define RT1320_DEV_ID_1 0xc405 + #define RT1320_DEV_ID_0 0xc406 + ++#define RT1320_HV_DEV_ID_0 0xf622 ++#define RT1320_HV_DEV_ID_1 0xf623 ++ + #define RT1320_POWER_STATE 0xc560 + + #define RT1321_PATCH_MAIN_VER 0x1000cffe +@@ -94,6 +99,12 @@ enum rt1320_version_id { + RT1320_VC, + }; + ++enum rt1321_version_id { ++ RT1321_VA0, ++ RT1321_VA1, ++ RT1321_VA2, ++}; ++ + #define RT1320_VER_B_ID 0x07392238 + #define RT1320_VAB_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vab.bin" + #define RT1320_VC_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vc.bin" +@@ -121,6 +132,7 @@ struct rt1320_datafixpoint { + int invrs; + }; + ++/* FW parameter id 1300 */ + typedef struct FwPara_HwSwGain { + unsigned int SwAdvGain; + unsigned int SwBasGain; +@@ -163,6 +175,13 @@ enum rt1320_rw_type { + RT1320_PARAM_READ = 3, + }; + ++enum { ++ RT1320_CLK_FREQ_19_2_MHZ = 1, ++ RT1320_CLK_FREQ_24MHZ = 2, ++ RT1320_CLK_FREQ_24_576MHZ = 3, ++ RT1320_CLK_FREQ_22_5792MHZ = 4, ++}; ++ + struct rt1320_sdw_priv { + struct snd_soc_component *component; + struct regmap *regmap; +diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c +--- a/sound/soc/codecs/rt5514-spi.c ++++ b/sound/soc/codecs/rt5514-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -79,17 +80,17 @@ static void rt5514_spi_copy_work(struct work_struct *work) + unsigned int cur_wp, remain_data; + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + if (!rt5514_dsp->substream) { + dev_err(rt5514_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5514_dsp->substream->runtime; + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + if (!period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + + if (rt5514_dsp->buf_size % period_bytes) +@@ -111,7 +112,7 @@ static void rt5514_spi_copy_work(struct work_struct *work) + + if (remain_data < period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + } + +@@ -146,9 +147,6 @@ static void rt5514_spi_copy_work(struct work_struct *work) + snd_pcm_period_elapsed(rt5514_dsp->substream); + + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- +-done: +- mutex_unlock(&rt5514_dsp->dma_lock); + } + + static void rt5514_schedule_copy(struct rt5514_dsp *rt5514_dsp) +@@ -216,7 +214,7 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + snd_soc_component_get_drvdata(component); + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + rt5514_dsp->substream = substream; + rt5514_dsp->dma_offset = 0; + +@@ -225,8 +223,6 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + if (buf[0] & RT5514_IRQ_STATUS_BIT) + rt5514_schedule_copy(rt5514_dsp); + +- mutex_unlock(&rt5514_dsp->dma_lock); +- + return 0; + } + +@@ -236,9 +232,8 @@ static int rt5514_spi_hw_free(struct snd_soc_component *component, + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5514_dsp->dma_lock); +- rt5514_dsp->substream = NULL; +- mutex_unlock(&rt5514_dsp->dma_lock); ++ scoped_guard(mutex, &rt5514_dsp->dma_lock) ++ rt5514_dsp->substream = NULL; + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + +diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c +--- a/sound/soc/codecs/rt5514.c ++++ b/sound/soc/codecs/rt5514.c +@@ -1073,12 +1073,18 @@ static int rt5514_set_bias_level(struct snd_soc_component *component, + * settings to make sure recording properly. + */ + if (rt5514->dsp_enabled) { +- rt5514->dsp_enabled = 0; +- regmap_multi_reg_write(rt5514->i2c_regmap, +- rt5514_i2c_patch, +- ARRAY_SIZE(rt5514_i2c_patch)); ++ ret = regmap_multi_reg_write(rt5514->i2c_regmap, ++ rt5514_i2c_patch, ++ ARRAY_SIZE(rt5514_i2c_patch)); ++ if (ret) ++ return ret; ++ + regcache_mark_dirty(rt5514->regmap); +- regcache_sync(rt5514->regmap); ++ ret = regcache_sync(rt5514->regmap); ++ if (ret) ++ return ret; ++ ++ rt5514->dsp_enabled = 0; + } + } + break; +diff --git a/sound/soc/codecs/rt5575-spi.c b/sound/soc/codecs/rt5575-spi.c +--- a/sound/soc/codecs/rt5575-spi.c ++++ b/sound/soc/codecs/rt5575-spi.c +@@ -93,7 +93,6 @@ static void rt5575_spi_burst_write(struct spi_device *spi, u32 addr, const u8 *t + int rt5575_spi_fw_load(struct spi_device *spi) + { + struct device *dev = &spi->dev; +- const struct firmware *firmware; + int i, ret; + static const char * const fw_path[] = { + "realtek/rt5575/rt5575_fw1.bin", +@@ -104,6 +103,7 @@ int rt5575_spi_fw_load(struct spi_device *spi) + static const u32 fw_addr[] = { 0x5f400000, 0x5f600000, 0x5f7fe000, 0x5f7ff000 }; + + for (i = 0; i < ARRAY_SIZE(fw_addr); i++) { ++ const struct firmware *firmware __free(firmware) = NULL; + ret = request_firmware(&firmware, fw_path[i], dev); + if (ret) { + dev_err(dev, "Request firmware failure: %d\n", ret); +@@ -111,7 +111,6 @@ int rt5575_spi_fw_load(struct spi_device *spi) + } + + rt5575_spi_burst_write(spi, fw_addr[i], firmware->data, firmware->size); +- release_firmware(firmware); + } + + return 0; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -3323,92 +3324,89 @@ static void rt5645_jack_detect_work(struct work_struct *work) + if (!rt5645->component) + return; + +- mutex_lock(&rt5645->jd_mutex); +- +- switch (rt5645->pdata.jd_mode) { +- case 0: /* Not using rt5645 JD */ +- if (rt5645->gpiod_hp_det) { +- gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); +- if (rt5645->pdata.inv_hp_pol) +- gpio_state ^= 1; +- dev_dbg(rt5645->component->dev, "gpio_state = %d\n", +- gpio_state); +- report = rt5645_jack_detect(rt5645->component, gpio_state); +- } +- snd_soc_jack_report(rt5645->hp_jack, +- report, SND_JACK_HEADPHONE); +- snd_soc_jack_report(rt5645->mic_jack, +- report, SND_JACK_MICROPHONE); +- mutex_unlock(&rt5645->jd_mutex); +- return; +- case 4: +- val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; +- break; +- default: /* read rt5645 jd1_1 status */ +- val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; +- break; +- +- } +- +- if (!val && (rt5645->jack_type == 0)) { /* jack in */ +- report = rt5645_jack_detect(rt5645->component, 1); +- } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { +- /* for push button and jack out */ +- btn_type = 0; +- if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { +- /* button pressed */ +- report = SND_JACK_HEADSET; +- btn_type = rt5645_button_detect(rt5645->component); +- /* rt5650 can report three kinds of button behavior, +- one click, double click and hold. However, +- currently we will report button pressed/released +- event. So all the three button behaviors are +- treated as button pressed. */ +- switch (btn_type) { +- case 0x8000: +- case 0x4000: +- case 0x2000: +- report |= SND_JACK_BTN_0; +- break; +- case 0x1000: +- case 0x0800: +- case 0x0400: +- report |= SND_JACK_BTN_1; +- break; +- case 0x0200: +- case 0x0100: +- case 0x0080: +- report |= SND_JACK_BTN_2; +- break; +- case 0x0040: +- case 0x0020: +- case 0x0010: +- report |= SND_JACK_BTN_3; +- break; +- case 0x0000: /* unpressed */ +- break; +- default: +- dev_err(rt5645->component->dev, +- "Unexpected button code 0x%04x\n", +- btn_type); +- break; ++ scoped_guard(mutex, &rt5645->jd_mutex) { ++ switch (rt5645->pdata.jd_mode) { ++ case 0: /* Not using rt5645 JD */ ++ if (rt5645->gpiod_hp_det) { ++ gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); ++ if (rt5645->pdata.inv_hp_pol) ++ gpio_state ^= 1; ++ dev_dbg(rt5645->component->dev, "gpio_state = %d\n", ++ gpio_state); ++ report = rt5645_jack_detect(rt5645->component, gpio_state); + } ++ snd_soc_jack_report(rt5645->hp_jack, ++ report, SND_JACK_HEADPHONE); ++ snd_soc_jack_report(rt5645->mic_jack, ++ report, SND_JACK_MICROPHONE); ++ return; ++ case 4: ++ val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; ++ break; ++ default: /* read rt5645 jd1_1 status */ ++ val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; ++ break; + } +- if (btn_type == 0)/* button release */ +- report = rt5645->jack_type; +- else { +- mod_timer(&rt5645->btn_check_timer, +- msecs_to_jiffies(100)); +- } +- } else { +- /* jack out */ +- report = 0; +- snd_soc_component_update_bits(rt5645->component, +- RT5645_INT_IRQ_ST, 0x1, 0x0); +- rt5645_jack_detect(rt5645->component, 0); +- } + +- mutex_unlock(&rt5645->jd_mutex); ++ if (!val && rt5645->jack_type == 0) { /* jack in */ ++ report = rt5645_jack_detect(rt5645->component, 1); ++ } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { ++ /* for push button and jack out */ ++ btn_type = 0; ++ if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { ++ /* button pressed */ ++ report = SND_JACK_HEADSET; ++ btn_type = rt5645_button_detect(rt5645->component); ++ /* ++ * rt5650 can report three kinds of button behavior, ++ * one click, double click and hold. However, ++ * currently we will report button pressed/released ++ * event. So all the three button behaviors are ++ * treated as button pressed. ++ */ ++ switch (btn_type) { ++ case 0x8000: ++ case 0x4000: ++ case 0x2000: ++ report |= SND_JACK_BTN_0; ++ break; ++ case 0x1000: ++ case 0x0800: ++ case 0x0400: ++ report |= SND_JACK_BTN_1; ++ break; ++ case 0x0200: ++ case 0x0100: ++ case 0x0080: ++ report |= SND_JACK_BTN_2; ++ break; ++ case 0x0040: ++ case 0x0020: ++ case 0x0010: ++ report |= SND_JACK_BTN_3; ++ break; ++ case 0x0000: /* unpressed */ ++ break; ++ default: ++ dev_err(rt5645->component->dev, ++ "Unexpected button code 0x%04x\n", ++ btn_type); ++ break; ++ } ++ } ++ if (btn_type == 0)/* button release */ ++ report = rt5645->jack_type; ++ else ++ mod_timer(&rt5645->btn_check_timer, ++ msecs_to_jiffies(100)); ++ } else { ++ /* jack out */ ++ report = 0; ++ snd_soc_component_update_bits(rt5645->component, ++ RT5645_INT_IRQ_ST, 0x1, 0x0); ++ rt5645_jack_detect(rt5645->component, 0); ++ } ++ } + + snd_soc_jack_report(rt5645->hp_jack, report, SND_JACK_HEADPHONE); + snd_soc_jack_report(rt5645->mic_jack, report, SND_JACK_MICROPHONE); +@@ -3522,9 +3520,15 @@ static int rt5645_suspend(struct snd_soc_component *component) + static int rt5645_resume(struct snd_soc_component *component) + { + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + return 0; + } +@@ -4335,9 +4339,15 @@ static int rt5645_sys_suspend(struct device *dev) + static int rt5645_sys_resume(struct device *dev) + { + struct rt5645_priv *rt5645 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + if (rt5645->hp_jack) { + rt5645->jack_type = 0; +diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c +--- a/sound/soc/codecs/rt5665.c ++++ b/sound/soc/codecs/rt5665.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -1208,7 +1209,7 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + val = snd_soc_component_read(rt5665->component, RT5665_AJD1_CTRL) & 0x0010; + if (!val) { +@@ -1274,8 +1275,6 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5665->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5665->jd_check_work); +- +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static const char * const rt5665_clk_sync[] = { +@@ -4564,7 +4563,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + { + int value, count; + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + regcache_cache_bypass(rt5665->regmap, true); + +@@ -4601,7 +4600,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("HP Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4620,7 +4620,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("MONO Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4635,9 +4636,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + regmap_write(rt5665->regmap, RT5665_BIAS_CUR_CTRL_8, 0xa602); + regmap_write(rt5665->regmap, RT5665_ASRC_8, 0x0120); + +-out_unlock: + rt5665->calibration_done = true; +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static void rt5665_calibrate_handler(struct work_struct *work) +diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c +--- a/sound/soc/codecs/rt5668.c ++++ b/sound/soc/codecs/rt5668.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -986,7 +987,7 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + return; + } + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + val = snd_soc_component_read(rt5668->component, RT5668_AJD1_CTRL) + & RT5668_JDH_RS_MASK; +@@ -1053,8 +1054,6 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5668->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5668->jd_check_work); +- +- mutex_unlock(&rt5668->calibrate_mutex); + } + + static const struct snd_kcontrol_new rt5668_snd_controls[] = { +@@ -2356,7 +2355,7 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + { + int value, count; + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + rt5668_reset(rt5668->regmap); + regmap_write(rt5668->regmap, RT5668_PWR_ANLG_1, 0xa2bf); +@@ -2400,9 +2399,6 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + /* restore settings */ + regmap_write(rt5668->regmap, RT5668_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5668->regmap, RT5668_PWR_DIG_1, 0x0000); +- +- mutex_unlock(&rt5668->calibrate_mutex); +- + } + + static int rt5668_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c +--- a/sound/soc/codecs/rt5677-spi.c ++++ b/sound/soc/codecs/rt5677-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -133,9 +134,8 @@ static int rt5677_spi_hw_params( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = substream; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -147,9 +147,8 @@ static int rt5677_spi_hw_free( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = NULL; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -311,17 +310,17 @@ static void rt5677_spi_copy_work(struct work_struct *work) + int ret = 0; + + /* Ensure runtime->dma_area buffer does not go away while copying. */ +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + if (!rt5677_dsp->substream) { + dev_err(rt5677_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5677_dsp->substream->runtime; + + if (rt5677_spi_mic_write_offset(&mic_write_offset)) { + dev_err(rt5677_dsp->dev, "No mic_write_offset\n"); +- goto done; ++ return; + } + + /* If this is the first time that we've asked for streaming data after +@@ -355,7 +354,7 @@ static void rt5677_spi_copy_work(struct work_struct *work) + ret = rt5677_spi_copy(rt5677_dsp, copy_bytes); + if (ret) { + dev_err(rt5677_dsp->dev, "Copy failed %d\n", ret); +- goto done; ++ return; + } + rt5677_dsp->avail_bytes += copy_bytes; + if (rt5677_dsp->avail_bytes >= period_bytes) { +@@ -367,8 +366,6 @@ static void rt5677_spi_copy_work(struct work_struct *work) + + delay = bytes_to_frames(runtime, period_bytes) / runtime->rate; + schedule_delayed_work(&rt5677_dsp->copy_work, secs_to_jiffies(delay)); +-done: +- mutex_unlock(&rt5677_dsp->dma_lock); + } + + static int rt5677_spi_pcm_new(struct snd_soc_component *component, +@@ -507,10 +504,8 @@ int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) + header[3] = ((addr + offset) & 0x0000ff00) >> 8; + header[4] = ((addr + offset) & 0x000000ff) >> 0; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); +- ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + + /* Copy data back to caller buffer */ + rt5677_spi_reverse(cb + offset, len - offset, body, t[1].len); +@@ -564,9 +559,8 @@ int rt5677_spi_write(u32 addr, const void *txbuf, size_t len) + offset += t.len; + t.len += RT5677_SPI_HEADER + 1; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + } + return status; + } +@@ -591,10 +585,10 @@ void rt5677_spi_hotword_detected(void) + return; + } + +- mutex_lock(&rt5677_dsp->dma_lock); +- dev_info(rt5677_dsp->dev, "Hotword detected\n"); +- rt5677_dsp->new_hotword = true; +- mutex_unlock(&rt5677_dsp->dma_lock); ++ scoped_guard(mutex, &rt5677_dsp->dma_lock) { ++ dev_info(rt5677_dsp->dev, "Hotword detected\n"); ++ rt5677_dsp->new_hotword = true; ++ } + + schedule_delayed_work(&rt5677_dsp->copy_work, 0); + } +diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c +--- a/sound/soc/codecs/rt5677.c ++++ b/sound/soc/codecs/rt5677.c +@@ -6,6 +6,8 @@ + * Author: Oder Chiou + */ + ++#include ++#include + #include + #include + #include +@@ -563,46 +565,43 @@ static int rt5677_dsp_mode_i2c_write_addr(struct rt5677_priv *rt5677, + struct snd_soc_component *component = rt5677->component; + int ret; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, + value >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set data msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, + value & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set data lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + opcode); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -622,36 +621,33 @@ static int rt5677_dsp_mode_i2c_read_addr( + int ret; + unsigned int msb, lsb; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + 0x0002); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, &msb); + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, &lsb); + *value = (msb << 16) | lsb; + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -853,11 +849,11 @@ static int rt5677_parse_and_load_dsp(struct rt5677_priv *rt5677, const u8 *buf, + + static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + { +- const struct firmware *fwp; + struct device *dev = rt5677->component->dev; +- int ret = 0; ++ int ret; + + /* Load dsp firmware from rt5677_elf_vad file */ ++ const struct firmware *fwp __free(firmware) = NULL; + ret = request_firmware(&fwp, "rt5677_elf_vad", dev); + if (ret) { + dev_err(dev, "Request rt5677_elf_vad failed %d\n", ret); +@@ -865,9 +861,7 @@ static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + } + dev_info(dev, "Requested rt5677_elf_vad (%zu)\n", fwp->size); + +- ret = rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); +- release_firmware(fwp); +- return ret; ++ return rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); + } + + static int rt5677_set_dsp_vad(struct snd_soc_component *component, bool on) +@@ -940,21 +934,20 @@ static void rt5677_dsp_work(struct work_struct *work) + activity = false; + + /* Don't turn off the DSP while handling irqs */ +- mutex_lock(&rt5677->irq_lock); +- /* Set DSP CPU to Stop */ +- regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, +- RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); ++ scoped_guard(mutex, &rt5677->irq_lock) { ++ /* Set DSP CPU to Stop */ ++ regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, ++ RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); + +- rt5677_set_dsp_mode(rt5677, false); ++ rt5677_set_dsp_mode(rt5677, false); + +- /* Disable and clear VAD interrupt */ +- regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); ++ /* Disable and clear VAD interrupt */ ++ regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); + +- /* Set GPIO1 pin back to be IRQ output for jack detect */ +- regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, +- RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); +- +- mutex_unlock(&rt5677->irq_lock); ++ /* Set GPIO1 pin back to be IRQ output for jack detect */ ++ regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, ++ RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); ++ } + } + } + +@@ -4767,6 +4760,21 @@ static int rt5677_gpio_direction_in(struct gpio_chip *chip, unsigned offset) + return rt5677_update_gpio_bits(rt5677, offset, m, v); + } + ++static int rt5677_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) ++{ ++ struct rt5677_priv *rt5677 = gpiochip_get_data(chip); ++ unsigned int shift = RT5677_GPIOx_DIR_SFT + (offset % 5) * 3; ++ unsigned int bank = offset / 5; ++ unsigned int reg = bank ? RT5677_GPIO_CTRL3 : RT5677_GPIO_CTRL2; ++ int ret; ++ ++ ret = regmap_test_bits(rt5677->regmap, reg, BIT(shift)); ++ if (ret < 0) ++ return ret; ++ ++ return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; ++} ++ + /* + * Configures the GPIO as + * 0 - floating +@@ -4834,6 +4842,7 @@ static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) + static const struct gpio_chip rt5677_template_chip = { + .label = RT5677_DRV_NAME, + .owner = THIS_MODULE, ++ .get_direction = rt5677_gpio_get_direction, + .direction_output = rt5677_gpio_direction_out, + .set = rt5677_gpio_set, + .direction_input = rt5677_gpio_direction_in, +@@ -4980,11 +4989,11 @@ static int rt5677_read(void *context, unsigned int reg, unsigned int *val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); ++ } + } else { + rt5677_dsp_mode_i2c_read(rt5677, reg, val); + } +@@ -5002,12 +5011,12 @@ static int rt5677_write(void *context, unsigned int reg, unsigned int val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, +- val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, ++ val); ++ } + } else { + rt5677_dsp_mode_i2c_write(rt5677, reg, val); + } +@@ -5399,7 +5408,7 @@ static void rt5677_resume_irq_check(struct work_struct *work) + * Without this explicit check, unplug the headset right after suspend + * starts, then after resume the headset is still shown as plugged in. + */ +- mutex_lock(&rt5677->irq_lock); ++ guard(mutex)(&rt5677->irq_lock); + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (rt5677->irq_en & rt5677_irq_descs[i].enable_mask) { + virq = irq_find_mapping(rt5677->domain, i); +@@ -5407,7 +5416,6 @@ static void rt5677_resume_irq_check(struct work_struct *work) + handle_nested_irq(virq); + } + } +- mutex_unlock(&rt5677->irq_lock); + } + + static void rt5677_irq_bus_lock(struct irq_data *data) +diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c +--- a/sound/soc/codecs/rt5682-sdw.c ++++ b/sound/soc/codecs/rt5682-sdw.c +@@ -6,6 +6,7 @@ + // Author: Oder Chiou + // + ++#include + #include + #include + #include +@@ -410,7 +411,9 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + if (rt5682->first_hw_init) { + regcache_cache_bypass(rt5682->regmap, false); + regcache_mark_dirty(rt5682->regmap); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) ++ goto err_sync; + + /* volatile registers */ + regmap_update_bits(rt5682->regmap, RT5682_CBJ_CTRL_2, +@@ -472,8 +475,16 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + /* Mark Slave initialization complete */ + rt5682->hw_init = true; + rt5682->first_hw_init = true; ++ goto out; ++ ++err_sync: ++ regcache_cache_bypass(rt5682->regmap, false); ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); + + err_nodev: ++out: + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete: %d\n", __func__, ret); +@@ -660,12 +671,11 @@ static int rt5682_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt5682->disable_irq_lock); ++ guard(mutex)(&rt5682->disable_irq_lock); + if (status->control_port & 0x4 && !rt5682->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt5682->jack_detect_work, msecs_to_jiffies(rt5682->irq_work_delay_time)); + } +- mutex_unlock(&rt5682->disable_irq_lock); + + return 0; + } +@@ -736,11 +746,11 @@ static int rt5682_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt5682->disable_irq_lock); +- rt5682->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt5682->disable_irq_lock); ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ rt5682->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -760,12 +770,12 @@ static int rt5682_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt5682->disable_irq_lock); +- if (rt5682->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt5682->disable_irq = false; ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ if (rt5682->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt5682->disable_irq = false; ++ } + } +- mutex_unlock(&rt5682->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT5682_PROBE_TIMEOUT); +@@ -776,7 +786,13 @@ static int rt5682_dev_resume(struct device *dev) + + regcache_cache_only(rt5682->sdw_regmap, false); + regcache_cache_only(rt5682->regmap, false); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) { ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c +--- a/sound/soc/codecs/rt5682.c ++++ b/sound/soc/codecs/rt5682.c +@@ -6,6 +6,7 @@ + // Author: Bard Liao + // + ++#include + #include + #include + #include +@@ -3125,7 +3126,7 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + { + int value, count; + +- mutex_lock(&rt5682->calibrate_mutex); ++ guard(mutex)(&rt5682->calibrate_mutex); + + rt5682_reset(rt5682); + regmap_write(rt5682->regmap, RT5682_I2C_CTRL, 0x000f); +@@ -3175,8 +3176,6 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + regmap_write(rt5682->regmap, RT5682_CALIB_ADC_CTRL, 0x2005); + regmap_write(rt5682->regmap, RT5682_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5682->regmap, RT5682_CAL_REC, 0x0c0c); +- +- mutex_unlock(&rt5682->calibrate_mutex); + } + EXPORT_SYMBOL_GPL(rt5682_calibrate); + +diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c +--- a/sound/soc/codecs/rt5682s.c ++++ b/sound/soc/codecs/rt5682s.c +@@ -6,6 +6,7 @@ + // Author: Derek Fang + // + ++#include + #include + #include + #include +@@ -648,7 +649,7 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + { + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5682s->sar_mutex); ++ guard(mutex)(&rt5682s->sar_mutex); + + switch (mode) { + case SAR_PWR_SAVING: +@@ -695,8 +696,6 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + dev_err(component->dev, "Invalid SAR Power mode: %d\n", mode); + break; + } +- +- mutex_unlock(&rt5682s->sar_mutex); + } + + static void rt5682s_enable_push_button_irq(struct snd_soc_component *component) +@@ -2534,7 +2533,7 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF2 | RT5682S_PWR_FV2 | RT5682S_PWR_MB, +@@ -2556,8 +2555,6 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + + rt5682s->wclk_enabled = 1; + +- mutex_unlock(&rt5682s->wclk_mutex); +- + return 0; + } + +@@ -2570,7 +2567,7 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + if (!rt5682s->jack_type) + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, +@@ -2585,8 +2582,6 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + rt5682s_set_pllb_power(rt5682s, 0); + + rt5682s->wclk_enabled = 0; +- +- mutex_unlock(&rt5682s->wclk_mutex); + } + + static unsigned long rt5682s_wclk_recalc_rate(struct clk_hw *hw, +@@ -2997,7 +2992,7 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + { + unsigned int count, value; + +- mutex_lock(&rt5682s->calibrate_mutex); ++ guard(mutex)(&rt5682s->calibrate_mutex); + + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0xaa80); + usleep_range(15000, 20000); +@@ -3034,8 +3029,6 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + regmap_write(rt5682s->regmap, RT5682S_PWR_DIG_1, 0x00c0); + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0x0800); + regmap_write(rt5682s->regmap, RT5682S_GLB_CLK, 0x0000); +- +- mutex_unlock(&rt5682s->calibrate_mutex); + } + + static const struct regmap_config rt5682s_regmap = { +diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c +--- a/sound/soc/codecs/rt700-sdw.c ++++ b/sound/soc/codecs/rt700-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,12 +416,11 @@ static int rt700_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt700->disable_irq_lock); ++ guard(mutex)(&rt700->disable_irq_lock); + if (status->control_port & 0x4 && !rt700->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt700->disable_irq_lock); + + return 0; + } +@@ -499,11 +499,11 @@ static int rt700_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt700->disable_irq_lock); +- rt700->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt700->disable_irq_lock); ++ scoped_guard(mutex, &rt700->disable_irq_lock) { ++ rt700->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c +--- a/sound/soc/codecs/rt711-sdca-sdw.c ++++ b/sound/soc/codecs/rt711-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,13 +416,13 @@ static int rt711_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711_sdca->disable_irq_lock); +- rt711_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt711_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt711_sdca->disable_irq_lock) { ++ rt711_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -443,13 +444,15 @@ static int rt711_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711-sdca.c b/sound/soc/codecs/rt711-sdca.c +--- a/sound/soc/codecs/rt711-sdca.c ++++ b/sound/soc/codecs/rt711-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -450,7 +451,7 @@ static void rt711_sdca_btn_check_handler(struct work_struct *work) + + static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + { +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + if (rt711->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -515,8 +516,6 @@ static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + + dev_dbg(&rt711->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt711->calibrate_mutex); + } + + static int rt711_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c +--- a/sound/soc/codecs/rt711-sdw.c ++++ b/sound/soc/codecs/rt711-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -422,12 +423,11 @@ static int rt711_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt711->disable_irq_lock); ++ guard(mutex)(&rt711->disable_irq_lock); + if (status->control_port & 0x4 && !rt711->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt711->disable_irq_lock); + + return 0; + } +@@ -509,11 +509,11 @@ static int rt711_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711->disable_irq_lock); +- rt711->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt711->disable_irq_lock); ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ rt711->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -535,12 +535,12 @@ static int rt711_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c +--- a/sound/soc/codecs/rt711.c ++++ b/sound/soc/codecs/rt711.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -89,24 +90,24 @@ static int rt711_calibration(struct rt711_priv *rt711) + struct regmap *regmap = rt711->regmap; + int ret = 0; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + dev = regmap_get_device(regmap); + + /* Calibration manual mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, RT711_FSM_CTL, +- 0xf, 0x0); ++ 0xf, 0x0); + + /* trigger */ + rt711_index_update_bits(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, +- RT711_DAC_DC_CALI_TRIGGER); ++ RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, ++ RT711_DAC_DC_CALI_TRIGGER); + + /* wait for calibration process */ + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + + while (val & RT711_DAC_DC_CALI_TRIGGER) { + if (loop >= 500) { +@@ -119,16 +120,15 @@ static int rt711_calibration(struct rt711_priv *rt711) + + usleep_range(10000, 11000); + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + } + + /* depop mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, +- RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); ++ RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); + + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + + dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret); + return ret; +@@ -362,24 +362,24 @@ static void rt711_jack_init(struct rt711_priv *rt711) + { + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt711->component); + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + /* power on */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + if (rt711->hs_jack) { + /* unsolicited response & IRQ control */ + regmap_write(rt711->regmap, +- RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); ++ RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); + regmap_write(rt711->regmap, +- RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); ++ RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); + regmap_write(rt711->regmap, +- RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); ++ RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x10, 0x2420); ++ 0x10, 0x2420); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x19, 0x2e11); ++ 0x19, 0x2e11); + + switch (rt711->jd_src) { + case RT711_JD1: +@@ -449,8 +449,7 @@ static void rt711_jack_init(struct rt711_priv *rt711) + /* power off */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + } + + static int rt711_set_jack_detect(struct snd_soc_component *component, +@@ -511,7 +510,7 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + unsigned int read_ll, read_rl; + int i; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + /* Can't use update bit function, so read the original value first */ + addr_h = mc->reg; +@@ -599,7 +598,6 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + +- mutex_unlock(&rt711->calibrate_mutex); + return 0; + } + +@@ -908,11 +906,11 @@ static int rt711_set_bias_level(struct snd_soc_component *component, + break; + + case SND_SOC_BIAS_STANDBY: +- mutex_lock(&rt711->calibrate_mutex); +- regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, +- AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ scoped_guard(mutex, &rt711->calibrate_mutex) { ++ regmap_write(rt711->regmap, ++ RT711_SET_AUDIO_POWER_STATE, ++ AC_PWRST_D3); ++ } + break; + + default: +diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c +--- a/sound/soc/codecs/rt712-sdca-dmic.c ++++ b/sound/soc/codecs/rt712-sdca-dmic.c +@@ -912,10 +912,23 @@ static int rt712_sdca_dmic_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt712->regmap, true); ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_mark_dirty(rt712->regmap); ++ regcache_mark_dirty(rt712->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt712_sdca_dmic_pm = { +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -427,13 +428,13 @@ static int rt712_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt712_sdca->disable_irq_lock); +- rt712_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt712_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt712_sdca->disable_irq_lock) { ++ rt712_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -455,14 +456,15 @@ static int rt712_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt712->disable_irq_lock); +- if (rt712->disable_irq == true) { +- +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt712->disable_irq = false; ++ scoped_guard(mutex, &rt712->disable_irq_lock) { ++ if (rt712->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt712->disable_irq = false; ++ } + } +- mutex_unlock(&rt712->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT712_PROBE_TIMEOUT); +@@ -472,9 +474,20 @@ static int rt712_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) { ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -7,6 +7,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -403,7 +404,7 @@ static void rt712_sdca_btn_check_handler(struct work_struct *work) + + static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + { +- mutex_lock(&rt712->calibrate_mutex); ++ guard(mutex)(&rt712->calibrate_mutex); + + if (rt712->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -450,8 +451,6 @@ static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + + dev_dbg(&rt712->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt712->calibrate_mutex); + } + + static int rt712_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -466,13 +467,13 @@ static int rt721_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt721_sdca->disable_irq_lock); +- rt721_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt721_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt721_sdca->disable_irq_lock) { ++ rt721_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -494,13 +495,15 @@ static int rt721_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt721->disable_irq_lock); +- if (rt721->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt721->disable_irq = false; ++ scoped_guard(mutex, &rt721->disable_irq_lock) { ++ if (rt721->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt721->disable_irq = false; ++ } + } +- mutex_unlock(&rt721->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT721_PROBE_TIMEOUT); +@@ -510,9 +513,20 @@ static int rt721_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt721->regmap, false); +- regcache_sync(rt721->regmap); ++ ret = regcache_sync(rt721->regmap); ++ if (ret) { ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt721->mbq_regmap, false); +- regcache_sync(rt721->mbq_regmap); ++ ret = regcache_sync(rt721->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt721->mbq_regmap, true); ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -5,7 +5,7 @@ + // Copyright(c) 2024 Realtek Semiconductor Corp. + // + // +- ++#include + #include + #include + #include +@@ -289,7 +289,7 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) + + static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + { +- mutex_lock(&rt721->calibrate_mutex); ++ guard(mutex)(&rt721->calibrate_mutex); + if (rt721->hs_jack) { + sdw_write_no_pm(rt721->slave, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0); +@@ -309,7 +309,6 @@ static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x4000, 0x4000); + } +- mutex_unlock(&rt721->calibrate_mutex); + } + + static int rt721_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c +--- a/sound/soc/codecs/rt722-sdca-sdw.c ++++ b/sound/soc/codecs/rt722-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -517,13 +518,13 @@ static int rt722_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt722_sdca->disable_irq_lock); +- rt722_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt722_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt722_sdca->disable_irq_lock) { ++ rt722_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -545,13 +546,15 @@ static int rt722_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt722->disable_irq_lock); +- if (rt722->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt722->disable_irq = false; ++ scoped_guard(mutex, &rt722->disable_irq_lock) { ++ if (rt722->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt722->disable_irq = false; ++ } + } +- mutex_unlock(&rt722->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT722_PROBE_TIMEOUT); +@@ -561,7 +564,13 @@ static int rt722_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt722->regmap, false); +- regcache_sync(rt722->regmap); ++ ret = regcache_sync(rt722->regmap); ++ if (ret) { ++ regcache_cache_only(rt722->regmap, true); ++ regcache_mark_dirty(rt722->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -294,7 +295,7 @@ static void rt722_sdca_btn_check_handler(struct work_struct *work) + + static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + { +- mutex_lock(&rt722->calibrate_mutex); ++ guard(mutex)(&rt722->calibrate_mutex); + if (rt722->hs_jack) { + /* set SCP_SDCA_IntMask1[0]=1 */ + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK1, +@@ -317,7 +318,6 @@ static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_HDA_CTL, + RT722_GE_RELATED_CTL2, 0x4000, 0x4000); + } +- mutex_unlock(&rt722->calibrate_mutex); + } + + static int rt722_sdca_set_jack_detect(struct snd_soc_component *component, +@@ -352,8 +352,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; + const char *dmi_vendor, *dmi_product, *dmi_sku; +- char *cae_filename; +- const struct firmware *cae_fw = NULL; + unsigned int cae_st_spk, cae_st_hp, cae_st_mic; + unsigned int func, value; + unsigned int combined_val; +@@ -385,7 +383,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + space = strchr(dmi_sku, ' '); + s_len = space ? space - dmi_sku : strlen(dmi_sku); + +- cae_filename = kasprintf(GFP_KERNEL, ++ char *cae_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, + "realtek/rt722/rt722_RAE_%.*s_%.*s_%.*s.dat", + v_len, dmi_vendor, + p_len, dmi_product, +@@ -399,8 +398,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regmap_write(rt722->regmap, RT722_MIC_CAE_PARAM39, 0x5f); + usleep_range(50000, 60000); + ++ const struct firmware *cae_fw __free(firmware) = NULL; + request_firmware(&cae_fw, cae_filename, dev); +- kfree(cae_filename); + if (!cae_fw) { + dev_err(dev, "%s: Failed to load CAE firmware\n", __func__); + return -ENOENT; +@@ -555,7 +554,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regcache_cache_bypass(rt722->regmap, false); + rt722->cae_update_done = 1; + dev_dbg(dev, "%s: CAE FW update done.\n", __func__); +- release_firmware(cae_fw); + return 0; + + verify_abort: +@@ -565,7 +563,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + out_release: + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG, + RT722_MISC_CTRL1, 0x8000, 0x0000); +- release_firmware(cae_fw); + dev_err(dev, "%s: CAE FW update aborted (ret=%d).\n", __func__, ret); + return ret; + } +@@ -1906,6 +1903,9 @@ int rt722_sdca_io_init(struct device *dev, struct sdw_slave *slave) + rt722_sdca_amp_preset(rt722); + rt722_sdca_jack_preset(rt722); + ++ if (rt722->hs_jack && (!rt722->first_hw_init)) ++ rt722_sdca_jack_init(rt722); ++ + if (rt722->first_hw_init) { + regcache_cache_bypass(rt722->regmap, false); + regcache_mark_dirty(rt722->regmap); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -0,0 +1,339 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca-sdw.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt766-sdca-sdw.h" ++ ++#define RT766_PROBE_TIMEOUT 5000 ++ ++static bool rt766_sdca_readable_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_MUTE_REG(UAJ, USER_FU41, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_PDE_REQ_REG(UAJ, PDE47): ++ case RT766_PDE_REQ_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_FUNC_STATUS_REG(UAJ): /* 0x40480000 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): /* 0x40481400 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): /* 0x40481480 */ ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): /* 0x40600490 */ ++ case RT766_PDE_REQ_REG(MIC, PDE11): ++ case RT766_MUTE_REG(MIC, USER_FU113, 1): ++ case RT766_MUTE_REG(MIC, USER_FU113, 2): ++ case RT766_MUTE_REG(MIC, USER_FU113, 3): ++ case RT766_MUTE_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_FUNC_STATUS_REG(MIC): /* 0x40880000 */ ++ case RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): /* 0x40881500 */ ++ case RT766_FUNC_STATUS_REG(HID): /* 0x40c80000 */ ++ /* 0x40c80080 - 0x40c80098 */ ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_MUTE_REG(AMP, USER_FU21, 1): ++ case RT766_MUTE_REG(AMP, USER_FU21, 2): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ case RT766_PDE_REQ_REG(AMP, PDE23): ++ case RT766_FUNC_STATUS_REG(AMP): /* 0x41080000 */ ++ case RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER): ++ case RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): /* 0x41081980 */ ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool rt766_sdca_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_FUNC_STATUS_REG(UAJ): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): ++ case RT766_FUNC_STATUS_REG(MIC): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): ++ case RT766_FUNC_STATUS_REG(HID): ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_FUNC_STATUS_REG(AMP): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static int rt766_sdca_mbq_size(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ return 2; ++ default: ++ return 1; ++ } ++} ++ ++static const struct regmap_sdw_mbq_cfg rt766_sdca_mbq_cfg = { ++ .mbq_size = rt766_sdca_mbq_size, ++}; ++ ++static const struct regmap_config rt766_sdca_regmap = { ++ .reg_bits = 32, ++ .val_bits = 16, ++ .readable_reg = rt766_sdca_readable_register, ++ .volatile_reg = rt766_sdca_volatile_register, ++ .reg_defaults = rt766_sdca_defaults, ++ .num_reg_defaults = ARRAY_SIZE(rt766_sdca_defaults), ++ .max_register = SDW_SDCA_MAX_REGISTER, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int rt766_sdca_update_status(struct sdw_slave *slave, ++ enum sdw_slave_status status) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(&slave->dev); ++ ++ if (status == SDW_SLAVE_UNATTACHED) ++ rt766->hw_init = false; ++ ++ if (status == SDW_SLAVE_ATTACHED) { ++ if (rt766->hs_jack) { ++ /* ++ * Due to the SCP_SDCA_INTMASK will be cleared by any reset, and then ++ * if the device attached again, we will need to set the setting back. ++ * It could avoid losing the jack detection interrupt. ++ * This also could sync with the cache value as the rt766_sdca_jack_init set. ++ */ ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24); ++ } ++ } ++ ++ /* ++ * Perform initialization only if slave status is present and ++ * hw_init flag is false ++ */ ++ if (rt766->hw_init || status != SDW_SLAVE_ATTACHED) ++ return 0; ++ ++ /* perform I/O transfers required for Slave initialization */ ++ return rt766_sdca_io_init(&slave->dev, slave); ++} ++ ++static int rt766_sdca_read_prop(struct sdw_slave *slave) ++{ ++ struct sdw_slave_prop *prop = &slave->prop; ++ int ret; ++ ++ ret = sdw_slave_read_prop(slave); ++ if (ret < 0) ++ return ret; ++ ++ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; ++ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; ++ /* ++ * SDCA interrupts are routed through SoundWire domain IRQ. ++ */ ++ prop->use_domain_irq = true; ++ ++ return 0; ++} ++ ++static const struct sdw_slave_ops rt766_sdca_slave_ops = { ++ .read_prop = rt766_sdca_read_prop, ++ .update_status = rt766_sdca_update_status, ++}; ++ ++static int rt766_sdca_sdw_probe(struct sdw_slave *slave, ++ const struct sdw_device_id *id) ++{ ++ struct regmap *regmap; ++ ++ /* Regmap Initialization */ ++ regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave, ++ &rt766_sdca_regmap, &rt766_sdca_mbq_cfg); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ return rt766_sdca_init(&slave->dev, regmap, slave); ++} ++ ++static void rt766_sdca_sdw_remove(struct sdw_slave *slave) ++{ ++ pm_runtime_disable(&slave->dev); ++} ++ ++static const struct sdw_device_id rt766_sdca_id[] = { ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ {}, ++}; ++MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); ++ ++static int rt766_sdca_dev_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, true); ++ return 0; ++} ++ ++static int rt766_sdca_dev_system_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ int ret1, ret2; ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ /* ++ * prevent new interrupts from being handled after the ++ * deferred work completes and before the parent disables ++ * interrupts on the link ++ */ ++ mutex_lock(&rt766->disable_irq_lock); ++ rt766->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24, 0); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ if (ret1 < 0 || ret2 < 0) { ++ /* log but don't prevent suspend from happening */ ++ dev_dbg(&slave->dev, "%s: could not disable SDCA interrupts\n:", __func__); ++ } ++ ++ return rt766_sdca_dev_suspend(dev); ++} ++ ++static int rt766_sdca_dev_resume(struct device *dev) ++{ ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ int ret; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ if (!slave->unattach_request) { ++ mutex_lock(&rt766->disable_irq_lock); ++ if (rt766->disable_irq == true) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK3, SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK4, SDW_SCP_SDCA_INTMASK_SDCA_24); ++ rt766->disable_irq = false; ++ } ++ mutex_unlock(&rt766->disable_irq_lock); ++ goto regmap_sync; ++ } ++ ++ ret = sdw_slave_wait_for_init(slave, RT766_PROBE_TIMEOUT); ++ if (ret) { ++ sdw_show_ping_status(slave->bus, true); ++ return ret; ++ } ++ ++regmap_sync: ++ regcache_cache_only(rt766->regmap, false); ++ ret = regcache_sync(rt766->regmap); ++ if (ret) { ++ regcache_cache_only(rt766->regmap, true); ++ regcache_mark_dirty(rt766->regmap); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops rt766_sdca_pm = { ++ SYSTEM_SLEEP_PM_OPS(rt766_sdca_dev_system_suspend, rt766_sdca_dev_resume) ++ RUNTIME_PM_OPS(rt766_sdca_dev_suspend, rt766_sdca_dev_resume, NULL) ++}; ++ ++static struct sdw_driver rt766_sdca_sdw_driver = { ++ .driver = { ++ .name = "rt766-sdca", ++ .pm = pm_ptr(&rt766_sdca_pm), ++ }, ++ .probe = rt766_sdca_sdw_probe, ++ .remove = rt766_sdca_sdw_remove, ++ .ops = &rt766_sdca_slave_ops, ++ .id_table = rt766_sdca_id, ++}; ++module_sdw_driver(rt766_sdca_sdw_driver); ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.h b/sound/soc/codecs/rt766-sdca-sdw.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca-sdw.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_SDW_H__ ++#define __RT766_SDW_H__ ++ ++#include ++#include ++ ++static const struct reg_default rt766_sdca_defaults[] = { ++ /* 0x40400289 - 0x4040028a */ ++ { RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 }, ++ /* 0x40400291 - 0x40400292 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 }, ++ /* 0x40400789 - 0x4040078a */ ++ { RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 }, ++ /* 0x40400791 - 0x40400792 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 }, /* 0x40401408 */ ++ { RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 }, /* 0x40401488 */ ++ { RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480080 */ ++ { RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480880 */ ++ /* 0x40600259 - 0x4060025a */ ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 }, ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 }, ++ { RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), 0x00 }, /* 0x40600488 */ ++ ++ { RT766_PDE_REQ_REG(MIC, PDE11), 0x03 }, /* 0x40801508 */ ++ /* 0x40801809 - 0x4080180c */ ++ { RT766_MUTE_REG(MIC, USER_FU113, 1), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 2), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 3), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 4), 0x01 }, ++ /* 0x40801811 - 0x40801814 */ ++ { RT766_VOLUME_REG(MIC, USER_FU113, 1), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 2), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 3), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 4), 0x0000 }, ++ { RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40880900 */ ++ ++ /* 0x41000189 - 0x4100018a */ ++ { RT766_MUTE_REG(AMP, USER_FU21, 1), 0x01 }, ++ { RT766_MUTE_REG(AMP, USER_FU21, 2), 0x01 }, ++ /* 0x41000191 - 0x41000192 */ ++ { RT766_VOLUME_REG(AMP, USER_FU21, 1), 0x0000 }, ++ { RT766_VOLUME_REG(AMP, USER_FU21, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(AMP, PDE23), 0x03 }, /* 0x41001988 */ ++ { RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0x00 }, /* 0x41080200 */ ++ { RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x41081080 */ ++ ++}; ++ ++#endif /* __RT766_SDW_H__ */ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -0,0 +1,1378 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt-sdw-common.h" ++ ++static int rt766_sdca_btn_detect(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ unsigned char *buf = NULL; ++ unsigned int offset, owner, length; ++ unsigned int det_mode, idx, val; ++ int ret; ++ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ /* get current UMP message owner */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), ++ &owner); ++ if (ret < 0) ++ goto io_error; ++ ++ /* if owner is device then there is no button event from device */ ++ if (owner == 1) ++ return 0; ++ ++ if (det_mode) { ++ /* read UMP message length */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH), ++ &length); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ /* read UMP message offset */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGEOFFSET), ++ &offset); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ buf = devm_kzalloc(&rt766->slave->dev, length, GFP_KERNEL); ++ if (!buf) { ++ dev_err(&rt766->slave->dev, "%s: alloc buf failed\n", __func__); ++ goto _end_btn_det_; ++ } ++ ++ for (idx = 0; idx < length; idx++) { ++ ret = regmap_read(rt766->regmap, ++ RT766_BUF_ADDR_HID1 + offset + idx, &val); ++ if (ret < 0) ++ goto _end_btn_det_; ++ buf[idx] = val & 0xff; ++ } ++ ++ if (rt766->hid) ++ hid_input_report(rt766->hid, HID_INPUT_REPORT, ++ buf, length, 1); ++ } ++ ++_end_btn_det_: ++ if (buf) ++ devm_kfree(&rt766->slave->dev, buf); ++ ++ /* Host is owner, so set back to device */ ++ if (owner == 0) { ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), 0x01); ++ } ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_btn_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_btn_detect(interrupt); ++ mutex_unlock(&rt766->disable_irq_lock); ++ return IRQ_HANDLED; ++} ++ ++static int rt766_sdca_headset_detect(struct rt766_sdca_priv *rt766) ++{ ++ unsigned int det_mode; ++ int ret; ++ ++ /* get detected_mode */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ switch (det_mode) { ++ case 0x00: ++ rt766->jack_type = 0; ++ break; ++ case 0x03: ++ rt766->jack_type = SND_JACK_HEADPHONE; ++ break; ++ case 0x05: ++ rt766->jack_type = SND_JACK_HEADSET; ++ break; ++ } ++ ++ /* write selected_mode */ ++ if (det_mode) { ++ ret = regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), ++ det_mode); ++ if (ret < 0) ++ goto io_error; ++ } ++ ++ dev_dbg(&rt766->slave->dev, ++ "%s, detected_mode=0x%x\n", __func__, det_mode); ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_headset_detect(rt766); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ dev_dbg(&rt766->slave->dev, ++ "in %s, jack_type=%d\n", __func__, rt766->jack_type); ++ ++ snd_soc_jack_report(rt766->hs_jack, rt766->jack_type, SND_JACK_HEADSET); ++ return IRQ_HANDLED; ++} ++ ++static void rt766_sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ hid_destroy_device(rt766->hid); ++} ++ ++static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766, ++ struct sdca_function_data *function, ++ struct snd_soc_component *component, ++ struct sdca_interrupt_info *info, ++ bool enabled) ++{ ++ struct device *dev = &rt766->slave->dev; ++ struct sdca_interrupt *interrupt; ++ struct sdca_control *control; ++ struct sdca_entity *entity; ++ irq_handler_t handler; ++ int i, j, irq, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ for (j = 0; j < entity->num_controls; j++) { ++ control = &entity->controls[j]; ++ irq = control->interrupt_position; ++ ++ switch (SDCA_CTL_TYPE(entity->type, control->sel)) { ++ case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): ++ handler = rt766_sdca_irq_jd_handler; ++ break; ++ case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ handler = rt766_sdca_irq_btn_handler; ++ break; ++ default: ++ continue; ++ } ++ ++ interrupt = &info->irqs[irq]; ++ ++ if (enabled) { ++ ret = sdca_irq_data_populate(dev, rt766->regmap, component, ++ function, entity, control, ++ interrupt); ++ if (ret) ++ return ret; ++ ++ if (handler == rt766_sdca_irq_btn_handler) { ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ ++ interrupt->free_priv = rt766_sdca_destroy_hid_device; ++ rt766->hid = interrupt->priv; ++ } ++ ++ interrupt->priv = rt766; ++ ret = sdca_irq_request(dev, info, irq, interrupt->name, ++ handler, interrupt); ++ if (ret) { ++ dev_err(dev, "failed to request irq %s: %d\n", ++ interrupt->name, ret); ++ sdca_irq_cleanup_late(dev, function, info); ++ return ret; ++ } ++ dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name); ++ } else { ++ sdca_irq_cleanup_late(dev, function, info); ++ dev_dbg(dev, "Freeing IRQ %d\n", irq); ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_jack_detect(struct snd_soc_component *component, ++ struct snd_soc_jack *hs_jack, void *data) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ if (!rt766->uaj_func_data) { ++ dev_err(&rt766->slave->dev, "The SDCA UAJ function is not supported.\n"); ++ return -EINVAL; ++ } ++ ++ rt766->hs_jack = hs_jack; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ ret = pm_runtime_resume_and_get(component->dev); ++ if (ret < 0) { ++ if (ret != -EACCES) { ++ dev_err(component->dev, "%s: failed to resume %d\n", __func__, ret); ++ return ret; ++ } ++ ++ /* pm_runtime not enabled yet */ ++ dev_dbg(component->dev, "%s: skipping jack init for now\n", __func__); ++ return 0; ++ } ++ ++ /* disable interrupts if hs_jack is not set */ ++ if (!rt766->hs_jack) { ++ if (rt766->uaj_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ rt766->component, rt766->irq_info, false); ++ ++ if (rt766->hid_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ rt766->component, rt766->irq_info, false); ++ } ++ ++ pm_runtime_put_autosuspend(component->dev); ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_fu_ctl(struct rt766_sdca_priv *rt766, int func_num, int fu_num) ++{ ++ unsigned int fu01_reg, fu02_reg; ++ unsigned int ch_01, ch_02; ++ unsigned int ch_mute; ++ unsigned int fu_reg; ++ int err, i; ++ ++ switch (fu_num) { ++ case RT766_SDCA_ENT_USER_FU41: ++ ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU36: ++ ch_01 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU21: ++ ch_01 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU113: ++ for (i = 0; i < ARRAY_SIZE(rt766->fu113_mixer_mute); i++) { ++ ch_mute = (rt766->fu113_dapm_mute || rt766->fu113_mixer_mute[i]) ? 0x01 : 0x00; ++ fu_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1) + i; ++ err = regmap_write(rt766->regmap, fu_reg, ch_mute); ++ if (err < 0) ++ return err; ++ } ++ return 0; ++ } ++ ++ fu01_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1); ++ fu02_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 2); ++ err = regmap_write(rt766->regmap, fu01_reg, ch_01); ++ if (err < 0) ++ return err; ++ err = regmap_write(rt766->regmap, fu02_reg, ch_02); ++ if (err < 0) ++ return err; ++ ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu41_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu41_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu41_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu41_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu41_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu41_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu36_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu36_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu36_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu36_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu36_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu36_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu36_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu36_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu21_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu21_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu21_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu21_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu21_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu21_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu21_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu21_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu113_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu113_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu113_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ const unsigned int interval_offset = 0xc0; ++ unsigned int regvalue, ctl, i; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value); ++ ctl = p->max - (((0x1e00 - regvalue) & 0xffff) / interval_offset); ++ ++ ucontrol->value.integer.value[i] = ctl; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ const unsigned int interval_offset = 0xc0; ++ unsigned int gain_val[4]; ++ unsigned int i, changed = 0; ++ unsigned int regvalue[4]; ++ int err; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value[i]); ++ ++ gain_val[i] = ucontrol->value.integer.value[i]; ++ if (gain_val[i] > p->max) ++ gain_val[i] = p->max; ++ ++ gain_val[i] = 0x1e00 - ((p->max - gain_val[i]) * interval_offset); ++ gain_val[i] &= 0xffff; ++ ++ if (regvalue[i] != gain_val[i]) ++ changed = 1; ++ } ++ ++ if (!changed) ++ return 0; ++ ++ for (i = 0; i < p->count; i++) { ++ err = regmap_write(rt766->regmap, p->reg_base + i, gain_val[i]); ++ if (err < 0) ++ dev_err(&rt766->slave->dev, "0x%08x can't be set\n", p->reg_base + i); ++ } ++ ++ return changed; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ unsigned int i; ++ ++ for (i = 0; i < 4; i++) ++ ucontrol->value.integer.value[i] = !rt766->fu113_mixer_mute[i]; ++ return 0; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err, changed = 0, i; ++ ++ for (i = 0; i < 4; i++) { ++ if (rt766->fu113_mixer_mute[i] != !ucontrol->value.integer.value[i]) ++ changed = 1; ++ rt766->fu113_mixer_mute[i] = !ucontrol->value.integer.value[i]; ++ } ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ if (err < 0) ++ return err; ++ return changed; ++} ++ ++static int rt766_sdca_fu41_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu41_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu41_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event, int func_num, int pde_num, const char *pde_ent) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdca_function_data *func_data; ++ unsigned char ps0 = 0x0, ps3 = 0x3; ++ const struct sdca_entity *entity; ++ unsigned int pde_req_reg; ++ int from_ps, to_ps; ++ int ret; ++ ++ pde_req_reg = SDW_SDCA_CTL(func_num, pde_num, SDCA_CTL_PDE_REQUESTED_PS, 0); ++ ++ switch (func_num) { ++ case RT766_FUNC_NUM_UAJ: ++ func_data = rt766->uaj_func_data; ++ break; ++ case RT766_FUNC_NUM_AMP: ++ func_data = rt766->sa_func_data; ++ break; ++ case RT766_FUNC_NUM_MIC: ++ func_data = rt766->sm_func_data; ++ break; ++ default: ++ dev_err(component->dev, "%s: unsupported func_num %d\n", ++ __func__, func_num); ++ return -EINVAL; ++ } ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ regmap_write(rt766->regmap, pde_req_reg, ps0); ++ from_ps = ps3; ++ to_ps = ps0; ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ regmap_write(rt766->regmap, pde_req_reg, ps3); ++ from_ps = ps0; ++ to_ps = ps3; ++ break; ++ } ++ ++ entity = sdca_find_entity_by_label(func_data, pde_ent); ++ if (!entity) { ++ dev_err(component->dev, "%s: failed to find entity %s\n", ++ __func__, pde_ent); ++ return -EINVAL; ++ } ++ ++ ret = sdca_asoc_pde_poll_actual_ps(rt766->regmap, ++ func_num, ++ pde_num, ++ from_ps, to_ps, ++ entity->pde.max_delay, ++ entity->pde.num_max_delay); ++ if (ret) ++ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ __func__, from_ps, to_ps, ret); ++ ++ return ret; ++} ++ ++static int rt766_sdca_pde47_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE47, "PDE 47"); ++} ++ ++static int rt766_sdca_fu36_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu36_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu36_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde34_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE34, "PDE 34"); ++} ++ ++static int rt766_sdca_fu21_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu21_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu21_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde23_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_PDE23, "PDE 23"); ++} ++ ++static int rt766_sdca_pde11_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_PDE11, "PDE 11"); ++} ++ ++static int rt766_dmic_fu_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ ++ if (p->max == 1) ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; ++ else ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; ++ uinfo->count = p->count; ++ uinfo->value.integer.min = 0; ++ uinfo->value.integer.max = p->max; ++ return 0; ++} ++ ++static const char * const rt766_rx_data_ch_select[] = { ++ "L,R", ++ "R,L", ++ "L,L", ++ "R,R", ++ "L,L+R", ++ "R,L+R", ++ "L+R,L", ++ "L+R,R", ++ "L+R,L+R", ++}; ++ ++static SOC_ENUM_SINGLE_DECL(rt766_rx_data_ch_enum, ++ RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0, ++ rt766_rx_data_ch_select); ++ ++static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -9525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(spk_vol_tlv, -6525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); ++static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, -200, 200, 0); ++ ++#define RT766_P75DB_STEP 0xC0 /* 0.75 dB in Q7.8 format */ ++#define RT766_2DB_STEP 0x200 /* 2 dB in Q7.8 format */ ++#define RT766_HP_VOL_MIN (-127) /* -95.25 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MIN (-23) /* -17.25 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MIN (-1) /* -2 dB / 2 dB step */ ++#define RT766_SPK_VOL_MIN (-87) /* -65.25 dB / 0.75 dB step */ ++#define RT766_P_VOL_MAX 0 /* 0 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MAX 40 /* 30 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MAX 20 /* 40 dB / 2 dB step */ ++ ++static const struct snd_kcontrol_new rt766_sdca_controls[] = { ++ SOC_DOUBLE_EXT("FU41 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu41_playback_get, rt766_sdca_fu41_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU41 Playback Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU41, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU41, 2), ++ RT766_HP_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, hp_vol_tlv), ++ SOC_DOUBLE_EXT("FU36 Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu36_capture_get, rt766_sdca_fu36_capture_put), ++ SDCA_DOUBLE_Q78_TLV("FU36 Capture Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU36, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU36, 2), ++ RT766_HS_VOL_MIN, RT766_HS_VOL_MAX, RT766_P75DB_STEP, mic_vol_tlv), ++ SDCA_DOUBLE_Q78_TLV("FU33 Boost Volume", ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), ++ RT766_HS_BOOST_VOL_MIN, RT766_HS_BOOST_VOL_MAX, RT766_2DB_STEP, boost_vol_tlv), ++ ++ SOC_DOUBLE_EXT("FU21 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu21_playback_get, rt766_sdca_fu21_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU21 Playback Volume", ++ RT766_VOLUME_REG(AMP, USER_FU21, 1), ++ RT766_VOLUME_REG(AMP, USER_FU21, 2), ++ RT766_SPK_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, spk_vol_tlv), ++ SOC_ENUM("RX Channel Select", rt766_rx_data_ch_enum), ++ ++ RT_SDCA_FU_CTRL("FU113 Capture Switch", ++ RT766_MUTE_REG(MIC, USER_FU113, 1), 1, 1, 4, rt766_dmic_fu_info, ++ rt766_sdca_dmic_fu113_capture_get, rt766_sdca_dmic_fu113_capture_put), ++ RT_SDCA_EXT_TLV("FU113 Capture Volume", ++ RT766_VOLUME_REG(MIC, USER_FU113, 1), ++ rt766_sdca_dmic_set_gain_get, rt766_sdca_dmic_set_gain_put, ++ 4, 0x3f, mic_vol_tlv, rt766_dmic_fu_info), ++}; ++ ++static const struct snd_soc_dapm_widget rt766_sdca_dapm_widgets[] = { ++ /* UAJ */ ++ SND_SOC_DAPM_OUTPUT("HP"), ++ SND_SOC_DAPM_INPUT("MIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 47", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde47_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 34", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde34_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_DAC_E("FU 41", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu41_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 36", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu36_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("DP12TX", "DP12 Capture", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* AMP */ ++ SND_SOC_DAPM_OUTPUT("SPOL"), ++ SND_SOC_DAPM_OUTPUT("SPOR"), ++ SND_SOC_DAPM_DAC_E("FU 21", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu21_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 23", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde23_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* DMIC */ ++ SND_SOC_DAPM_INPUT("DMIC1"), ++ SND_SOC_DAPM_INPUT("DMIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 11", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde11_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 113", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu113_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_OUT("DP8TX", "DP8 Capture", 0, SND_SOC_NOPM, 0, 0), ++}; ++ ++static const struct snd_soc_dapm_route rt766_sdca_audio_map[] = { ++ { "FU 41", NULL, "DP3RX" }, ++ { "DP12TX", NULL, "FU 36" }, ++ { "FU 36", NULL, "PDE 34" }, ++ { "FU 36", NULL, "MIC2" }, ++ { "HP", NULL, "PDE 47" }, ++ { "HP", NULL, "FU 41" }, ++ ++ { "FU 21", NULL, "DP1RX" }, ++ { "FU 21", NULL, "PDE 23" }, ++ { "SPOL", NULL, "FU 21" }, ++ { "SPOR", NULL, "FU 21" }, ++ ++ {"DP8TX", NULL, "FU 113"}, ++ {"FU 113", NULL, "PDE 11"}, ++ {"FU 113", NULL, "DMIC1"}, ++ {"FU 113", NULL, "DMIC2"}, ++}; ++ ++static int rt766_sdca_probe(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct device *dev = &rt766->slave->dev; ++ int ret; ++ ++ rt766->component = component; ++ ++ ret = pm_runtime_resume(component->dev); ++ if (ret < 0 && ret != -EACCES) ++ return ret; ++ ++ if (rt766->uaj_func_data) { ++ dev_dbg(dev, "%s : irq %d\n", __func__, rt766->slave->irq); ++ ++ rt766->irq_info = devm_sdca_irq_allocate(dev, rt766->regmap, rt766->slave->irq); ++ if (IS_ERR(rt766->irq_info)) ++ return PTR_ERR(rt766->irq_info); ++ ++ ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request UAJ SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ ++ if (rt766->hid_func_data) { ++ ret = rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request HID SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static void rt766_sdca_remove(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ sdca_irq_cleanup_late(component->dev, rt766->uaj_func_data, rt766->irq_info); ++ sdca_irq_cleanup_late(component->dev, rt766->hid_func_data, rt766->irq_info); ++} ++ ++static const struct snd_soc_component_driver soc_sdca_dev_rt766 = { ++ .probe = rt766_sdca_probe, ++ .remove = rt766_sdca_remove, ++ .controls = rt766_sdca_controls, ++ .num_controls = ARRAY_SIZE(rt766_sdca_controls), ++ .dapm_widgets = rt766_sdca_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(rt766_sdca_dapm_widgets), ++ .dapm_routes = rt766_sdca_audio_map, ++ .num_dapm_routes = ARRAY_SIZE(rt766_sdca_audio_map), ++ .set_jack = rt766_sdca_set_jack_detect, ++ .endianness = 1, ++}; ++ ++static int rt766_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, ++ int direction) ++{ ++ snd_soc_dai_dma_data_set(dai, direction, sdw_stream); ++ ++ return 0; ++} ++ ++static void rt766_sdca_shutdown(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ snd_soc_dai_set_dma_data(dai, substream, NULL); ++} ++ ++static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_config stream_config; ++ struct sdw_port_config port_config; ++ enum sdw_data_direction direction; ++ struct sdw_stream_runtime *sdw_stream; ++ unsigned int sampling_rate; ++ int retval, port; ++ ++ dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); ++ sdw_stream = snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!sdw_stream) ++ return -EINVAL; ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); ++ ++ /* SoundWire specific configuration */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ direction = SDW_DATA_DIR_RX; ++ if (dai->id == RT766_AIF1) ++ port = 3; ++ else if (dai->id == RT766_AIF2) ++ port = 1; ++ else ++ return -EINVAL; ++ } else { ++ direction = SDW_DATA_DIR_TX; ++ if (dai->id == RT766_AIF1) ++ port = 12; ++ else if (dai->id == RT766_AIF3) ++ port = 8; ++ else ++ return -EINVAL; ++ } ++ ++ port_config.num = port; ++ retval = sdw_stream_add_slave(rt766->slave, &stream_config, ++ &port_config, 1, sdw_stream); ++ if (retval) { ++ dev_err(dai->dev, "%s: Unable to configure port\n", __func__); ++ return retval; ++ } ++ ++ if (params_channels(params) > 16) { ++ dev_err(component->dev, "%s: Unsupported channels %d\n", ++ __func__, params_channels(params)); ++ return -EINVAL; ++ } ++ ++ /* sampling rate configuration */ ++ switch (params_rate(params)) { ++ case 44100: ++ sampling_rate = RT766_SDCA_RATE_44100HZ; ++ break; ++ case 48000: ++ sampling_rate = RT766_SDCA_RATE_48000HZ; ++ break; ++ case 96000: ++ sampling_rate = RT766_SDCA_RATE_96000HZ; ++ break; ++ case 192000: ++ sampling_rate = RT766_SDCA_RATE_192000HZ; ++ break; ++ default: ++ dev_err(component->dev, "%s: Rate %d is not supported\n", ++ __func__, params_rate(params)); ++ return -EINVAL; ++ } ++ ++ /* set sampling frequency */ ++ switch (dai->id) { ++ case RT766_AIF1: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF2: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF3: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ default: ++ dev_err(component->dev, "%s: Wrong DAI id\n", __func__); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_runtime *sdw_stream = ++ snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ sdw_stream_remove_slave(rt766->slave, sdw_stream); ++ return 0; ++} ++ ++#define RT766_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ ++ SNDRV_PCM_RATE_192000) ++#define RT766_DAC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) ++#define RT766_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ ++ SNDRV_PCM_FMTBIT_S32_LE) ++ ++static const struct snd_soc_dai_ops rt766_sdca_ops = { ++ .hw_params = rt766_sdca_pcm_hw_params, ++ .hw_free = rt766_sdca_pcm_hw_free, ++ .set_stream = rt766_sdca_set_sdw_stream, ++ .shutdown = rt766_sdca_shutdown, ++}; ++ ++static struct snd_soc_dai_driver rt766_sdca_dai[] = { ++ { ++ .name = "rt766-sdca-aif1", ++ .id = RT766_AIF1, ++ .playback = { ++ .stream_name = "DP3 Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .capture = { ++ .stream_name = "DP12 Capture", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ .symmetric_rate = 1, ++ }, ++ { ++ .name = "rt766-sdca-aif2", ++ .id = RT766_AIF2, ++ .playback = { ++ .stream_name = "DP1 Playback", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ }, ++ { ++ .name = "rt766-sdca-aif3", ++ .id = RT766_AIF3, ++ .capture = { ++ .stream_name = "DP8 Capture", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ } ++}; ++ ++static unsigned int rt766_find_dt_rates(struct device *dev, struct sdca_function_data *function, ++ const char *label) ++{ ++ struct snd_soc_pcm_stream stream; ++ struct sdca_entity *entity; ++ int i, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ if (strcmp(entity->label, label)) ++ continue; ++ ++ /* Can't check earlier as only terminals have an iot member. */ ++ if (!entity->iot.is_dataport) ++ continue; ++ ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, &stream); ++ if (ret < 0) { ++ dev_dbg(dev, "%s: failed to parse rates for entity %s\n", ++ __func__, entity->label); ++ return 0; ++ } ++ ++ dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__, entity->label, stream.rates); ++ } ++ ++ return stream.rates; ++} ++ ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave) ++{ ++ struct sdca_function_data *func_data_ptr; ++ struct snd_soc_dai_driver *dai_drv; ++ struct rt766_sdca_priv *rt766; ++ unsigned int rates; ++ int ret; ++ int i; ++ ++ rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL); ++ if (!rt766) ++ return -ENOMEM; ++ ++ dev_set_drvdata(dev, rt766); ++ rt766->slave = slave; ++ rt766->regmap = regmap; ++ ++ regcache_cache_only(rt766->regmap, true); ++ ++ ret = devm_mutex_init(dev, &rt766->disable_irq_lock); ++ if (ret < 0) { ++ dev_err(dev, "Failed to initialize mutex\n"); ++ return ret; ++ } ++ ++ /* ++ * Mark hw_init to false ++ * HW init will be performed when device reports present ++ */ ++ rt766->hw_init = false; ++ rt766->first_hw_init = false; ++ rt766->fu41_dapm_mute = true; ++ rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false; ++ rt766->fu36_dapm_mute = true; ++ rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true; ++ rt766->fu21_dapm_mute = true; ++ rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false; ++ rt766->fu113_dapm_mute = true; ++ rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] = ++ rt766->fu113_mixer_mute[2] = rt766->fu113_mixer_mute[3] = true; ++ ++ dai_drv = devm_kzalloc(dev, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai), GFP_KERNEL); ++ if (!dai_drv) { ++ dev_err(dev, "Failed to allocate memory for DAI driver\n"); ++ ret = -ENOMEM; ++ goto _sdw_init_err_; ++ } ++ memcpy(dai_drv, rt766_sdca_dai, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai)); ++ ++ /* get SDCA function data */ ++ dev_dbg(dev, "SDCA functions found: %d", slave->sdca_data.num_functions); ++ for (i = 0; i < slave->sdca_data.num_functions; i++) { ++ func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr), GFP_KERNEL); ++ if (!func_data_ptr) { ++ dev_err(dev, "Failed to allocate memory for function data\n"); ++ ret = -ENOMEM; ++ goto _free_dai_drv_; ++ } ++ ++ func_data_ptr->desc = &slave->sdca_data.function[i]; ++ ret = sdca_parse_function(dev, func_data_ptr); ++ if (ret) { ++ devm_kfree(dev, func_data_ptr); ++ goto _free_dai_drv_; ++ } ++ dev_dbg(dev, "Function type=%d, num_entities=%d", ++ slave->sdca_data.function[i].type, func_data_ptr->num_entities); ++ ++ switch (slave->sdca_data.function[i].type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ rt766->uaj_func_data = func_data_ptr; ++ /* ++ * Some machines may only support a subset of the sample rates supported by the codec. ++ * Therefore, we need to parse the supported sample rates from the DisCo table and ++ * configure them in the DAI. If the DisCo table does not provide sample rate information, ++ * we will fall back to the default supported rates defined in the codec driver. ++ */ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 41"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].playback.rates = rates; ++ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 36"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ rt766->sa_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 21"); ++ if (rates) ++ dai_drv[RT766_DAI_AMP].playback.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ rt766->sm_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 113"); ++ if (rates) ++ dai_drv[RT766_DAI_MIC].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ rt766->hid_func_data = func_data_ptr; ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ slave->sdca_data.function[i].type); ++ } ++ } ++ ++ ret = devm_snd_soc_register_component(dev, ++ &soc_sdca_dev_rt766, dai_drv, ARRAY_SIZE(rt766_sdca_dai)); ++ if (ret < 0) ++ goto _free_dai_drv_; ++ ++ /* set autosuspend parameters */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ ++ /* make sure the device does not suspend immediately */ ++ pm_runtime_mark_last_busy(dev); ++ ++ pm_runtime_enable(dev); ++ dev_dbg(dev, "%s\n", __func__); ++ return 0; ++ ++_free_dai_drv_: ++ if (dai_drv) ++ devm_kfree(dev, dai_drv); ++ ++_sdw_init_err_: ++ return ret; ++} ++ ++static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_function_data *func_data) ++{ ++ struct device *dev = &rt766->slave->dev; ++ unsigned int func_status_reg; ++ unsigned int func_status; ++ int ret; ++ ++ switch (func_data->desc->type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ func_status_reg = RT766_FUNC_STATUS_REG(UAJ); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ func_status_reg = RT766_FUNC_STATUS_REG(AMP); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ func_status_reg = RT766_FUNC_STATUS_REG(MIC); ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ func_status_reg = RT766_FUNC_STATUS_REG(HID); ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ func_data->desc->type); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt766->regmap, func_status_reg, &func_status); ++ dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status); ++ ++ if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) { ++ ret = sdca_regmap_write_init(dev, rt766->regmap, func_data); ++ if (ret) { ++ dev_err(dev, "%s initialization table update failed\n", func_data->desc->name); ++ goto _func_init_err_; ++ } ++ ++ regmap_write(rt766->regmap, func_status_reg, ++ SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION); ++ } ++ ++ return 0; ++ ++_func_init_err_: ++ dev_err(dev, "%s: %s init writes failed, err=%d", __func__, func_data->desc->name, ret); ++ return ret; ++} ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ rt766->disable_irq = false; ++ ++ if (rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, false); ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, true); ++ } else { ++ /* ++ * PM runtime status is marked as 'active' only when a Slave reports as Attached ++ */ ++ ++ /* update count of parent 'active' children */ ++ pm_runtime_set_active(&slave->dev); ++ } ++ ++ pm_runtime_get_noresume(&slave->dev); ++ ++ regmap_read(rt766->regmap, RT766_BOND_LATCH_ID, &val); ++ dev_dbg(&slave->dev, "%s bond ID=0x%x (%s)\n", __func__, val, (val == 0x1) ? "RT767" : "RT766"); ++ ++ /* check function status and initialize if needed */ ++ if (rt766->uaj_func_data) ++ rt766_func_initialize(rt766, rt766->uaj_func_data); ++ if (rt766->sa_func_data) ++ rt766_func_initialize(rt766, rt766->sa_func_data); ++ if (rt766->sm_func_data) ++ rt766_func_initialize(rt766, rt766->sm_func_data); ++ if (rt766->hid_func_data) ++ rt766_func_initialize(rt766, rt766->hid_func_data); ++ ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, false); ++ regcache_mark_dirty(rt766->regmap); ++ } else { ++ rt766->first_hw_init = true; ++ } ++ ++ /* Mark Slave initialization complete */ ++ rt766->hw_init = true; ++ ++ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); ++ ++ pm_runtime_put_autosuspend(&slave->dev); ++ ++ return 0; ++} ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.h +@@ -0,0 +1,140 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_H__ ++#define __RT766_H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct rt766_sdca_priv { ++ struct regmap *regmap; ++ struct snd_soc_component *component; ++ struct sdw_slave *slave; ++ bool hw_init; ++ bool first_hw_init; ++ struct snd_soc_jack *hs_jack; ++ struct mutex disable_irq_lock; /* SDCA irq lock protection */ ++ bool disable_irq; ++ int jack_type; ++ bool fu41_dapm_mute; ++ bool fu41_mixer_l_mute; ++ bool fu41_mixer_r_mute; ++ bool fu113_dapm_mute; ++ bool fu113_mixer_mute[4]; ++ bool fu21_dapm_mute; ++ bool fu21_mixer_l_mute; ++ bool fu21_mixer_r_mute; ++ bool fu36_dapm_mute; ++ bool fu36_mixer_l_mute; ++ bool fu36_mixer_r_mute; ++ struct sdca_function_data *uaj_func_data; ++ struct sdca_function_data *sm_func_data; ++ struct sdca_function_data *sa_func_data; ++ struct sdca_function_data *hid_func_data; ++ struct sdca_interrupt_info *irq_info; ++ struct hid_device *hid; ++}; ++ ++/* vendor registers */ ++#define RT766_VERSION_ID 0xc404 ++#define RT766_DEV_ID1 0xc405 ++#define RT766_DEV_ID0 0xc406 ++#define RT766_BOND_LATCH_ID 0xc407 ++ ++#define RT766_HP_POWER_STATE 0x1000004 ++#define RT766_HP_FSM_CTL2_1 0x100000d ++ ++/* MCU Patch address */ ++#define RT766_MCU_PATCH_ADDR1_START 0x10010000 ++#define RT766_MCU_PATCH_ADDR1_END 0x10011fff ++#define RT766_MCU_PATCH_ADDR2_START 0x10020000 ++#define RT766_MCU_PATCH_ADDR2_END 0x10023fff ++ ++/* Buffer address for HID */ ++#define RT766_BUF_ADDR_HID1 0x44030000 ++#define RT766_BUF_ADDR_HID2 0x44030020 ++ ++/* SDCA (Channel) */ ++#define RT766_CH_1 0x01 ++#define RT766_CH_2 0x02 ++#define RT766_CH_3 0x03 ++#define RT766_CH_4 0x04 ++ ++/* RT766 SDCA Control - function number */ ++#define RT766_FUNC_NUM_UAJ 0x01 ++#define RT766_FUNC_NUM_MIC 0x02 ++#define RT766_FUNC_NUM_HID 0x03 ++#define RT766_FUNC_NUM_AMP 0x04 ++ ++/* RT766 SDCA entity */ ++#define RT766_SDCA_ENT_0 0x00 ++#define RT766_SDCA_ENT_HID101 0x01 ++#define RT766_SDCA_ENT_GE49 0x49 ++#define RT766_SDCA_ENT_USER_FU41 0x05 ++#define RT766_SDCA_ENT_USER_FU36 0x0f ++#define RT766_SDCA_ENT_USER_FU21 0x03 ++#define RT766_SDCA_ENT_USER_FU113 0x30 ++#define RT766_SDCA_ENT_PDE23 0x33 ++#define RT766_SDCA_ENT_PDE47 0x28 ++#define RT766_SDCA_ENT_PDE11 0x2a ++#define RT766_SDCA_ENT_PDE34 0x29 ++#define RT766_SDCA_ENT_CS41 0x01 ++#define RT766_SDCA_ENT_CS36 0x11 ++#define RT766_SDCA_ENT_CS113 0x12 ++#define RT766_SDCA_ENT_CS21 0x21 ++#define RT766_SDCA_ENT_PLATFORM_FU33 0x44 ++#define RT766_SDCA_ENT_PPU21 0x04 ++ ++/* sample frequency index */ ++#define RT766_SDCA_RATE_44100HZ 0x08 ++#define RT766_SDCA_RATE_48000HZ 0x09 ++#define RT766_SDCA_RATE_96000HZ 0x0b ++#define RT766_SDCA_RATE_192000HZ 0x0d ++ ++/* SDCA Register macros */ ++#define RT766_MUTE_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_MUTE, RT766_CH_##ch) ++ ++#define RT766_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_CHANNEL_VOLUME, RT766_CH_##ch) ++ ++#define RT766_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_GAIN, RT766_CH_##ch) ++ ++#define RT766_PDE_REQ_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_REQUESTED_PS, 0) ++ ++#define RT766_PDE_ACTUAL_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_ACTUAL_PS, 0) ++ ++#define RT766_FUNC_STATUS_REG(func) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0) ++ ++#define RT766_SDCA_CTL(func, ent, ctl) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##ent, ctl, 0) ++ ++enum { ++ RT766_AIF1, ++ RT766_AIF2, ++ RT766_AIF3, ++}; ++ ++enum { ++ RT766_DAI_UAJ, ++ RT766_DAI_AMP, ++ RT766_DAI_MIC, ++}; ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave); ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave); ++#endif /* __RT766_H__ */ +diff --git a/sound/soc/codecs/rt9120.c b/sound/soc/codecs/rt9120.c +--- a/sound/soc/codecs/rt9120.c ++++ b/sound/soc/codecs/rt9120.c +@@ -606,12 +606,19 @@ static int rt9120_runtime_suspend(struct device *dev) + static int rt9120_runtime_resume(struct device *dev) + { + struct rt9120_data *data = dev_get_drvdata(dev); ++ int ret; + + if (data->pwdnn_gpio) { + gpiod_set_value(data->pwdnn_gpio, 1); + msleep(RT9120_CHIPON_WAITMS); + regcache_cache_only(data->regmap, false); +- regcache_sync(data->regmap); ++ ret = regcache_sync(data->regmap); ++ if (ret) { ++ regcache_cache_only(data->regmap, true); ++ regcache_mark_dirty(data->regmap); ++ gpiod_set_value(data->pwdnn_gpio, 0); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c +--- a/sound/soc/codecs/sigmadsp.c ++++ b/sound/soc/codecs/sigmadsp.c +@@ -5,6 +5,7 @@ + * Copyright 2009-2014 Analog Devices Inc. + */ + ++#include + #include + #include + #include +@@ -135,7 +136,7 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + uint8_t *data; + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + data = ucontrol->value.bytes.data; + +@@ -148,8 +149,6 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + ctrl->cached = true; + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -160,7 +159,7 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + if (!ctrl->cached) { + ret = sigmadsp_read(sigmadsp, ctrl->addr, ctrl->cache, +@@ -174,8 +173,6 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + ctrl->num_bytes); + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -487,7 +484,7 @@ static void devm_sigmadsp_release(struct device *dev, void *res) + static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + { + const struct sigma_firmware_header *ssfw_head; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + u32 crc; + +@@ -495,7 +492,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + ret = request_firmware(&fw, name, sigmadsp->dev); + if (ret) { + pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); +- goto done; ++ return ret; + } + + /* then verify the header */ +@@ -509,13 +506,13 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + */ + if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n"); +- goto done; ++ return -EINVAL; + } + + ssfw_head = (void *)fw->data; + if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n"); +- goto done; ++ return -EINVAL; + } + + crc = crc32(0, fw->data + sizeof(*ssfw_head), +@@ -524,7 +521,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (crc != le32_to_cpu(ssfw_head->crc)) { + dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", + le32_to_cpu(ssfw_head->crc), crc); +- goto done; ++ return -EINVAL; + } + + switch (ssfw_head->version) { +@@ -545,9 +542,6 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (ret) + sigmadsp_firmware_release(sigmadsp); + +-done: +- release_firmware(fw); +- + return ret; + } + +@@ -677,10 +671,10 @@ static void sigmadsp_activate_ctrl(struct sigmadsp *sigmadsp, + return; + changed = snd_ctl_activate_id(card, &ctrl->kcontrol->id, active); + if (active && changed > 0) { +- mutex_lock(&sigmadsp->lock); +- if (ctrl->cached) +- sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); +- mutex_unlock(&sigmadsp->lock); ++ scoped_guard(mutex, &sigmadsp->lock) { ++ if (ctrl->cached) ++ sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); ++ } + } + } + +diff --git a/sound/soc/codecs/sma1303.c b/sound/soc/codecs/sma1303.c +--- a/sound/soc/codecs/sma1303.c ++++ b/sound/soc/codecs/sma1303.c +@@ -1777,6 +1777,8 @@ static void sma1303_i2c_remove(struct i2c_client *client) + struct sma1303_priv *sma1303 = + (struct sma1303_priv *) i2c_get_clientdata(client); + ++ if (sma1303->attr_grp) ++ sysfs_remove_group(sma1303->kobj, sma1303->attr_grp); + cancel_delayed_work_sync(&sma1303->check_fault_work); + } + +diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c +--- a/sound/soc/codecs/sma1307.c ++++ b/sound/soc/codecs/sma1307.c +@@ -1690,7 +1690,7 @@ static void sma1307_check_fault_worker(struct work_struct *work) + + static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *file) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int size, offset, num_mode; + int ret; + +@@ -1703,22 +1703,18 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil + return; + } else if ((fw->size) < SMA1307_SETTING_HEADER_SIZE) { + dev_err(sma1307->dev, "%s: Invalid file\n", __func__); +- release_firmware(fw); + sma1307->set.status = false; + return; + } + + int *data __free(kfree) = kzalloc(fw->size, GFP_KERNEL); + if (!data) { +- release_firmware(fw); + sma1307->set.status = false; + return; + } + size = fw->size >> 2; + memcpy(data, fw->data, fw->size); + +- release_firmware(fw); +- + /* HEADER */ + sma1307->set.header_size = SMA1307_SETTING_HEADER_SIZE; + sma1307->set.checksum = data[sma1307->set.header_size - 2]; +diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c +--- a/sound/soc/codecs/sta32x.c ++++ b/sound/soc/codecs/sta32x.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -269,9 +270,9 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta32x->coeff_lock); ++ guard(mutex)(&sta32x->coeff_lock); + + /* preserve reserved bits in STA32X_CFUD */ + regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); +@@ -283,24 +284,20 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + + regmap_write(sta32x->regmap, STA32X_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; ++ + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta32x->regmap, STA32X_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta32x->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c +--- a/sound/soc/codecs/sta350.c ++++ b/sound/soc/codecs/sta350.c +@@ -16,6 +16,7 @@ + + #define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__ + ++#include + #include + #include + #include +@@ -306,9 +307,9 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta350->coeff_lock); ++ guard(mutex)(&sta350->coeff_lock); + + /* preserve reserved bits in STA350_CFUD */ + regmap_read(sta350->regmap, STA350_CFUD, &cfud); +@@ -320,24 +321,19 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta350->regmap, STA350_CFUD, cfud); + + regmap_write(sta350->regmap, STA350_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta350->regmap, STA350_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta350->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta350_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -88,6 +88,9 @@ + #define TAC_FW_FILE_HDR 20 + #define TAC_MAX_FW_CHUNKS 512 + ++#define TAC_UAJ_PREP_CONNECTED 0xff ++#define TAC_UAJ_PREP_DISCONNECTED 0xdf ++ + struct tac_fw_hdr { + u32 size; + u32 version_offset; +@@ -138,6 +141,7 @@ struct tac5xx2_prv { + bool hw_init; + bool first_hw_init_done; + u32 part_id; ++ u32 rev_id; + struct snd_soc_jack *hs_jack; + int jack_type; + /* Custom fw binary. UMP File Download is not used. */ +@@ -261,7 +265,6 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 0, 0x78), 0x0}, + {TAC_REG_SDW(0, 0, 0x7b), 0x0}, + {TAC_REG_SDW(0, 0, 0x7c), 0xd0}, +- {TAC_REG_SDW(0, 0, 0x7d), 0x0}, + {TAC_REG_SDW(0, 0, 0x7e), 0x0}, + {TAC_REG_SDW(0, 1, 0x1), 0x0}, + {TAC_REG_SDW(0, 1, 0x2), 0x0}, +@@ -507,18 +510,6 @@ static const struct regmap_config tac_regmap = { + .use_single_write = true, + }; + +-/* Check if device has DSP algo that needs status monitoring */ +-static bool tac_has_dsp_algo(struct tac5xx2_prv *tac_dev) +-{ +- switch (tac_dev->part_id) { +- case 0x5682: +- case 0x2883: +- return true; +- default: +- return false; +- } +-} +- + /* Check if device has UAJ (Universal Audio Jack) support */ + static bool tac_has_uaj_support(struct tac5xx2_prv *tac_dev) + { +@@ -800,7 +791,7 @@ static int tac_sdw_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, pde_entity, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, pde_entity, + SDCA_PDE_PS3, SDCA_PDE_PS0, NULL, 0); + if (ret) + dev_err(tac_dev->dev, "failed to transition func %d, pde %d from PS3 -> PS0, err=%d\n", +@@ -847,7 +838,7 @@ static int tac_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, + pde_entity, SDCA_PDE_PS0, SDCA_PDE_PS3, + NULL, 0); + if (ret) +@@ -942,6 +933,7 @@ static int tac5xx2_sdca_button_detect(struct tac5xx2_prv *tac_dev) + static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + { + int val, ret; ++ u8 jack_prep; + + ret = regmap_read(tac_dev->regmap, + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_GE35, +@@ -951,6 +943,8 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + return ret; + } + ++ jack_prep = TAC_UAJ_PREP_CONNECTED; ++ + switch (val) { + case 4: + tac_dev->jack_type = SND_JACK_MICROPHONE; +@@ -964,6 +958,7 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + case 0: + default: + tac_dev->jack_type = 0; ++ jack_prep = TAC_UAJ_PREP_DISCONNECTED; + break; + } + +@@ -973,26 +968,44 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + if (ret) + dev_err(tac_dev->dev, "Failed to update the jack type to device"); + ++ /* ++ * When uaj is uplugged and booted, we end up with the channel prepare ++ * timeout error for the uaj ports. This writes allows the channel prepare ++ * to succeed when the uaj is not plugged in. ++ */ ++ if (tac_dev->rev_id >= 0x30) { ++ ret = regmap_write(tac_dev->regmap, TAC_REG_SDW(0, 3, 127), jack_prep); ++ if (ret) ++ dev_warn(tac_dev->dev, "Failed to write jack_prep register: %d\n", ret); ++ } ++ + return 0; + } + + static int tac5xx2_jack_init(struct tac5xx2_prv *tac_dev) + { ++ u32 jd_int_mask, hid_int_mask; + int ret = 0; + ++ if (tac_dev->rev_id >= 0x30) { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_12; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_17; ++ } else { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_11; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_16; ++ } ++ + if (!tac_dev->hs_jack) + goto disable_interrupts; + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_11); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, jd_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register jack detection interrupt: %d\n", ret); + goto disable_interrupts; + } + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, +- SDW_SCP_SDCA_INTMASK_SDCA_16); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, hid_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register for button detect interrupt: %d\n", ret); +@@ -1051,6 +1064,7 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + unsigned int sdca_int2, sdca_int3, jack_report_mask = 0; + struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev); + struct device *dev = &slave->dev; ++ u32 headset_detect_chk, hid_detect_chk; + int btn_type = 0; + int ret = 0; + +@@ -1081,14 +1095,22 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + dev_dbg(dev, "SDCA_INT2: 0x%02x, SDCA_INT3: 0x%02x\n", + sdca_int2, sdca_int3); + +- if (sdca_int2 & SDW_SCP_SDCA_INT_SDCA_11) { ++ if (tac_dev->rev_id >= 0x30) { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_12; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_17; ++ } else { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_11; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_16; ++ } ++ ++ if (sdca_int2 & headset_detect_chk) { + ret = tac5xx2_sdca_headset_detect(tac_dev); + if (ret < 0) + goto clear; + jack_report_mask |= SND_JACK_HEADSET; + } + +- if (sdca_int3 & SDW_SCP_SDCA_INT_SDCA_16) { ++ if (sdca_int3 & hid_detect_chk) { + btn_type = tac5xx2_sdca_button_detect(tac_dev); + if (btn_type < 0) + btn_type = 0; +@@ -1695,6 +1717,15 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + ++ if (!tac_dev->rev_id) { ++ ret = regmap_read(tac_dev->regmap, TAC_REV_ID, &tac_dev->rev_id); ++ if (ret) { ++ dev_err(tac_dev->dev, "failed to rev id, err=%d\n", ret); ++ goto io_init_err; ++ } ++ dev_dbg(tac_dev->dev, "detected rev_id 0x%x", tac_dev->rev_id); ++ } ++ + if (tac_dev->fw_files && tac_dev->fw_file_cnt > 0) { + ret = tac_download_fw_to_hw(tac_dev); + if (ret) { +@@ -1940,7 +1971,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + "failed to allocate %s function data", + func_name); + function_data->desc = &peripheral->sdca_data.function[i]; +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + *func_ptr = function_data; + else +@@ -1959,6 +1990,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->hw_init = false; + tac_dev->first_hw_init_done = false; + tac_dev->part_id = id->part_id; ++ tac_dev->rev_id = 0x0; + dev_set_drvdata(dev, tac_dev); + + regmap = devm_regmap_init_sdw_mbq_cfg(&peripheral->dev, peripheral, +@@ -1972,17 +2004,13 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->jack_type = 0; + init_completion(&tac_dev->fw_caching_complete); + +- if (tac_has_dsp_algo(tac_dev)) { +- tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, +- sizeof(tac_dev->fw_binaryname)); ++ tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, ++ sizeof(tac_dev->fw_binaryname)); + +- ret = tac_load_and_cache_firmware_async(tac_dev); +- if (ret) { +- complete_all(&tac_dev->fw_caching_complete); +- dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); +- } +- } else { ++ ret = tac_load_and_cache_firmware_async(tac_dev); ++ if (ret) { + complete_all(&tac_dev->fw_caching_complete); ++ dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); + } + + ret = tac_init(tac_dev); +diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c +--- a/sound/soc/codecs/tas2552.c ++++ b/sound/soc/codecs/tas2552.c +@@ -495,13 +495,21 @@ static int tas2552_runtime_suspend(struct device *dev) + static int tas2552_runtime_resume(struct device *dev) + { + struct tas2552_data *tas2552 = dev_get_drvdata(dev); ++ int ret; + + gpiod_set_value_cansleep(tas2552->enable_gpio, 1); + + tas2552_sw_shutdown(tas2552, 0); + + regcache_cache_only(tas2552->regmap, false); +- regcache_sync(tas2552->regmap); ++ ret = regcache_sync(tas2552->regmap); ++ if (ret) { ++ regcache_cache_only(tas2552->regmap, true); ++ regcache_mark_dirty(tas2552->regmap); ++ tas2552_sw_shutdown(tas2552, 1); ++ gpiod_set_value_cansleep(tas2552->enable_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c +--- a/sound/soc/codecs/tas2781-comlib-i2c.c ++++ b/sound/soc/codecs/tas2781-comlib-i2c.c +@@ -6,6 +6,7 @@ + // + // Author: Shenghao Ding + ++#include + #include + #include + #include +@@ -342,7 +343,7 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + /* Codec Lock Hold to ensure that codec_probe and firmware parsing and + * loading do not simultaneously execute. + */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + + if (tas_priv->name_prefix) + scnprintf(tas_priv->rca_binaryname, 64, "%s-%sRCA%d.bin", +@@ -360,8 +361,6 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n", + ret); + +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); + return ret; + } + EXPORT_SYMBOL_GPL(tascodec_init); +diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c +--- a/sound/soc/codecs/tas2781-fmwlib.c ++++ b/sound/soc/codecs/tas2781-fmwlib.c +@@ -2253,7 +2253,7 @@ int tas2781_load_calibration(void *context, char *file_name, + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; + struct tasdevice *tasdev = &(tas_priv->tasdevice[i]); +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + struct tasdevice_fw *tas_fmw; + struct firmware fmw; + int offset = 0; +@@ -2263,60 +2263,50 @@ int tas2781_load_calibration(void *context, char *file_name, + if (ret) { + dev_err(tas_priv->dev, "%s: Request firmware %s failed\n", + __func__, file_name); +- goto out; ++ return ret; + } + + if (!fw_entry->size) { + dev_err(tas_priv->dev, "%s: file read error: size = %lu\n", + __func__, (unsigned long)fw_entry->size); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + fmw.size = fw_entry->size; + fmw.data = fw_entry->data; + + tas_fmw = tasdev->cali_data_fmw = kzalloc_obj(struct tasdevice_fw); +- if (!tasdev->cali_data_fmw) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!tasdev->cali_data_fmw) ++ return -ENOMEM; ++ + tas_fmw->dev = tas_priv->dev; + offset = fw_parse_header(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, "fw_parse_header EXIT!\n"); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_variable_hdr_cal(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, + "%s: fw_parse_variable_header_cal EXIT!\n", __func__); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_program_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_program_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_configuration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_configuration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_calibration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_calibration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + +-out: +- release_firmware(fw_entry); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_NS_GPL(tas2781_load_calibration, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2409,7 +2399,7 @@ static int tasdevice_dspfw_ready(const struct firmware *fmw, + int tasdevice_dsp_parser(void *context) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; +- const struct firmware *fw_entry; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, +@@ -2417,15 +2407,10 @@ int tasdevice_dsp_parser(void *context) + if (ret) { + dev_err(tas_priv->dev, "%s: load %s error\n", __func__, + tas_priv->coef_binaryname); +- goto out; ++ return ret; + } + +- ret = tasdevice_dspfw_ready(fw_entry, tas_priv); +- release_firmware(fw_entry); +- fw_entry = NULL; +- +-out: +- return ret; ++ return tasdevice_dspfw_ready(fw_entry, tas_priv); + } + EXPORT_SYMBOL_NS_GPL(tasdevice_dsp_parser, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2807,11 +2792,12 @@ int tasdevice_prmg_load(void *context, int prm_no) + } + EXPORT_SYMBOL_NS_GPL(tasdevice_prmg_load, "SND_SOC_TAS2781_FMWLIB"); + +-void tasdevice_tuning_switch(void *context, int state) ++void tasdevice_tuning_switch(void *context, int state, bool is_cap) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; +- int profile_cfg_id = tas_priv->rcabin.profile_cfg_id; ++ int profile_cfg_id = is_cap ? tas_priv->rcabin.capture_profile_id : ++ tas_priv->rcabin.profile_cfg_id; + + /* + * Only RCA-based Playback can still work with no dsp program running +@@ -2828,7 +2814,6 @@ void tasdevice_tuning_switch(void *context, int state) + if (state == 0) { + if (tas_fmw && tas_priv->cur_prog < tas_fmw->nr_programs) { + /* dsp mode or tuning mode */ +- profile_cfg_id = tas_priv->rcabin.profile_cfg_id; + tasdevice_select_tuningprm_cfg(tas_priv, + tas_priv->cur_prog, tas_priv->cur_conf, + profile_cfg_id); +diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c +--- a/sound/soc/codecs/tas2781-i2c.c ++++ b/sound/soc/codecs/tas2781-i2c.c +@@ -13,6 +13,7 @@ + // Author: Kevin Lu + // + ++#include + #include + #include + #include +@@ -617,6 +618,7 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); ++ int i; + + guard(mutex)(&priv->codec_lock); + if (priv->chip_id == TAS2563) +@@ -624,6 +626,14 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + else + tas2781_calib_stop_put(priv); + ++ /* ++ * Set reloading-firmware flag after calibration, the flag will work ++ * during next playback, then set to the program id after reloading ++ * firmware. ++ */ ++ for (i = 0; i < priv->ndev; i++) ++ priv->tasdevice[i].cur_prog = -1; ++ + return 1; + } + +@@ -843,12 +853,12 @@ static int tasdevice_digital_gain_get( + unsigned char data[4]; + int ret; + +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- goto out; ++ return ret; + } + + target = get_unaligned_be32(&data[0]); +@@ -868,8 +878,7 @@ static int tasdevice_digital_gain_get( + /* find out the member same as or closer to the current volume */ + ucontrol->value.integer.value[0] = + abs(target - ar_l) <= abs(target - ar_r) ? l : r; +-out: +- mutex_unlock(&tas_dev->codec_lock); ++ + return 0; + } + +@@ -882,29 +891,26 @@ static int tasdevice_digital_gain_put( + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *tas_dev = snd_soc_component_get_drvdata(codec); + int vol = ucontrol->value.integer.value[0]; +- int status = 0, max = mc->max, rc = 1; ++ int status = 0, max = mc->max; + int i, ret; + unsigned int reg = mc->reg; + unsigned int volrd, volwr; + unsigned char data[4]; + + vol = clamp(vol, 0, max); +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- rc = -1; +- goto out; ++ return -1; + } + + volrd = get_unaligned_be32(&data[0]); + volwr = get_unaligned_be32(tas_dev->dvc_tlv_table[vol]); + +- if (volrd == volwr) { +- rc = 0; +- goto out; +- } ++ if (volrd == volwr) ++ return 0; + + for (i = 0; i < tas_dev->ndev; i++) { + ret = tasdevice_dev_bulk_write(tas_dev, i, reg, +@@ -918,10 +924,9 @@ static int tasdevice_digital_gain_put( + } + + if (status) +- rc = -1; +-out: +- mutex_unlock(&tas_dev->codec_lock); +- return rc; ++ return -1; ++ ++ return 1; + } + + static const struct snd_kcontrol_new tasdevice_cali_controls[] = { +@@ -995,6 +1000,57 @@ static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, + return ret; + } + ++/** ++ * tasdevice_set_capture_profile_id - Set runtime capture profile index via ++ * ALSA control ++ * @kcontrol: ALSA kcontrol handle that triggers this operation ++ * @ucontrol: User space control value carrying the new profile index ++ * ++ * This mixer control handler validates the user-provided capture profile ID ++ * against the maximum valid index parsed from the loaded DSP firmware, ++ * then updates the runtime stored capture profile ID only if the new value ++ * differs from the current active one. It will immediately return -EINVAL ++ * if the submitted profile ID falls outside the valid range, including the ++ * edge case that no valid configuration blocks are detected in firmware. ++ * ++ * No actual DSP register write is performed in this handler. The updated ++ * profile ID will be applied to the hardware when the next ALSA capture ++ * stream starts up. Caller does not need to take extra codec lock here, ++ * as the ALSA control core already guarantees serialized execution. ++ * ++ * Return: 1 if profile ID value was changed, 0 if no modification needed, ++ * -EINVAL if the input profile ID is out of valid range ++ */ ++static int tasdevice_set_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int user_prof_id = ucontrol->value.integer.value[0]; ++ unsigned int max_valid_id; ++ int ret = 0; ++ ++ /* ++ * Align valid range with the bound defined in ++ * tasdevice_info_profile() ++ */ ++ max_valid_id = tas_priv->rcabin.ncfgs - 1; ++ ++ /* ++ * Reject invalid input including zero total configuration edge ++ * case ++ */ ++ if (tas_priv->rcabin.ncfgs == 0 || user_prof_id > max_valid_id) ++ return -EINVAL; ++ ++ if (tas_priv->rcabin.capture_profile_id != user_prof_id) { ++ tas_priv->rcabin.capture_profile_id = user_prof_id; ++ ret = 1; ++ } ++ ++ return ret; ++} ++ + static int tasdevice_info_active_num(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +@@ -1075,6 +1131,41 @@ static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol, + return 0; + } + ++/** ++ * tasdevice_get_capture_profile_id - Report current active capture profile ++ * ID to user space ++ * @kcontrol: ALSA kcontrol structure passed from ALSA core ++ * @ucontrol: User-space control element value buffer to write the result back ++ * ++ * This function ensures the returned profile ID is always clamped inside the ++ * valid range advertised by the info callback, preventing accidental invalid ++ * values from being exposed to applications even if internal driver state is ++ * temporarily inconsistent. ++ * ++ * Returns 0 on successful fill of the control value, no error conditions ++ * are defined for this getter callback. ++ */ ++static int tasdevice_get_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int max_valid_id, current_prof_id; ++ ++ max_valid_id = tas_priv->rcabin.ncfgs > 0 ? ++ (tas_priv->rcabin.ncfgs - 1U) : 0; ++ ++ /* ++ * Cast current profile id to unsigned to match type with max_valid_id, ++ * avoid signedness mismatch; ++ */ ++ current_prof_id = (unsigned int)tas_priv->rcabin.capture_profile_id; ++ /* Prevent underflow when there are no loaded capture profiles. */ ++ ucontrol->value.integer.value[0] = min(current_prof_id, max_valid_id); ++ ++ return 0; ++} ++ + static int tasdevice_get_chip_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -1117,6 +1208,41 @@ static int tasdevice_create_control(struct tasdevice_priv *tas_priv) + ret = snd_soc_add_component_controls(tas_priv->codec, + prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); + ++ mix_index = 0; ++ switch (tas_priv->chip_id) { ++ case TAS2563: ++ case TAS2568: ++ case TAS2570: ++ case TAS2572: ++ case TAS2573: ++ case TAS2574: ++ case TAS2781: ++ prof_ctrls = devm_kcalloc(tas_priv->dev, nr_controls, ++ sizeof(prof_ctrls[0]), GFP_KERNEL); ++ if (!prof_ctrls) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* Create a mixer item for selecting the capture profile */ ++ name = devm_kstrdup(tas_priv->dev, "Speaker Capture Profile Id", ++ GFP_KERNEL); ++ if (!name) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ prof_ctrls[mix_index].name = name; ++ prof_ctrls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ prof_ctrls[mix_index].info = tasdevice_info_profile; ++ prof_ctrls[mix_index].get = tasdevice_get_capture_profile_id; ++ prof_ctrls[mix_index].put = tasdevice_set_capture_profile_id; ++ mix_index++; ++ ++ ret = snd_soc_add_component_controls(tas_priv->codec, ++ prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); ++ break; ++ } ++ + out: + return ret; + } +@@ -1330,6 +1456,43 @@ static void alpa_cali_update(struct bulk_reg_val *p, + p->val_len = 4; + } + ++static int create_tas2781_cali_start_ktrl(struct tasdevice_priv ++ *priv, struct snd_kcontrol_new *cali_ctrl) ++{ ++ struct soc_bytes_ext *ext_cali_start; ++ char *cali_start_name; ++ ++ ext_cali_start = devm_kzalloc(priv->dev, ++ sizeof(*ext_cali_start), GFP_KERNEL); ++ if (!ext_cali_start) ++ return -ENOMEM; ++ ++ cali_start_name = devm_kstrdup(priv->dev, ++ "Calibration Start", GFP_KERNEL); ++ if (!cali_start_name) ++ return -ENOMEM; ++ /* ++ * package structure for tas2781 ftc start: ++ * Pkg len (1 byte) ++ * Reg id (1 byte, constant 'r') ++ * book, page, register for pilot threshold, pilot tone ++ * and sine gain (12 bytes) ++ * for (i = 0; i < Device-Sum; i++) { ++ * Device #i index_info (1 byte) ++ * Sine gain for Device #i (8 bytes) ++ * } ++ */ ++ ext_cali_start->max = 14 + priv->ndev * 9; ++ cali_ctrl->name = cali_start_name; ++ cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ cali_ctrl->info = snd_soc_bytes_info_ext; ++ cali_ctrl->put = tas2781_calib_start_put; ++ cali_ctrl->get = tasdev_nop_get; ++ cali_ctrl->private_value = (unsigned long)ext_cali_start; ++ ++ return 0; ++} ++ + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + { + struct calidata *cali_data = &priv->cali_data; +@@ -1448,37 +1611,11 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + */ + cali_data->data[0] = 0xff; + if (priv->chip_id == TAS2781) { +- struct soc_bytes_ext *ext_cali_start; +- char *cali_start_name; +- +- ext_cali_start = devm_kzalloc(priv->dev, +- sizeof(*ext_cali_start), GFP_KERNEL); +- if (!ext_cali_start) +- return -ENOMEM; +- +- cali_start_name = devm_kstrdup(priv->dev, +- "Calibration Start", GFP_KERNEL); +- if (!cali_start_name) +- return -ENOMEM; +- /* +- * package structure for tas2781 ftc start: +- * Pkg len (1 byte) +- * Reg id (1 byte, constant 'r') +- * book, page, register for pilot threshold, pilot tone +- * and sine gain (12 bytes) +- * for (i = 0; i < Device-Sum; i++) { +- * Device #i index_info (1 byte) +- * Sine gain for Device #i (8 bytes) +- * } +- */ +- ext_cali_start->max = 14 + priv->ndev * 9; +- cali_ctrls[i].name = cali_start_name; +- cali_ctrls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; +- cali_ctrls[i].info = snd_soc_bytes_info_ext; +- cali_ctrls[i].put = tas2781_calib_start_put; +- cali_ctrls[i].get = tasdev_nop_get; +- cali_ctrls[i].private_value = (unsigned long)ext_cali_start; ++ rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); ++ if (rc != 0) ++ return rc; + i++; ++ + } + + return snd_soc_add_component_controls(priv->codec, cali_ctrls, +@@ -1765,13 +1902,25 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + int state = 0; + +- /* Codec Lock Hold */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + if (event == SND_SOC_DAPM_PRE_PMD) + state = 1; +- tasdevice_tuning_switch(tas_priv, state); +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); ++ tasdevice_tuning_switch(tas_priv, state, false); ++ ++ return 0; ++} ++ ++static int tasdevice_capture_dapm_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ int state = 0; ++ ++ guard(mutex)(&tas_priv->codec_lock); ++ if (event == SND_SOC_DAPM_PRE_PMD) ++ state = 1; ++ tasdevice_tuning_switch(tas_priv, state, true); + + return 0; + } +@@ -1779,7 +1928,7 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + static const struct snd_soc_dapm_widget tasdevice_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT_E("ASI OUT", "ASI Capture", 0, SND_SOC_NOPM, +- 0, 0, tasdevice_dapm_event, ++ 0, 0, tasdevice_capture_dapm_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SPK("SPK", tasdevice_dapm_event), + SND_SOC_DAPM_OUTPUT("OUT"), +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -12,6 +12,7 @@ + // Author: Baojun Xu + // Author: Kevin Lu + ++#include + #include + #include + #include +@@ -494,6 +495,56 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) + return tas2783_sdca_mbq_size(dev, reg) > 0; + } + ++static bool tas2783_writeable_register(struct device *dev, unsigned int reg) ++{ ++ /* ++ * The Latency Control of every Entity, together with the Power Domain ++ * actual state and the protection status, is read-only. They are ++ * listed in tas2783_reg_default[] with a placeholder value, so without ++ * this a regcache_sync() would try to write them back and the ++ * peripheral would reject the transaction, aborting the sync. ++ */ ++ switch (reg) { ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): ++ return false; ++ ++ default: ++ return tas2783_sdca_mbq_size(dev, reg) > 0; ++ } ++} ++ + static bool tas2783_volatile_register(struct device *dev, u32 reg) + { + switch (reg) { +@@ -515,6 +566,7 @@ static const struct regmap_config tas_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = tas2783_readable_register, ++ .writeable_reg = tas2783_writeable_register, + .volatile_reg = tas2783_volatile_register, + .reg_defaults = tas2783_reg_default, + .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), +@@ -692,12 +744,12 @@ static s32 tas2783_update_calibdata(struct tas2783_prv *tas_dev) + return 0; + } + +- mutex_lock(&tas_dev->calib_lock); +- ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, +- tas_dev->cali_data.read_sz); +- if (!ret) +- tas2783_set_calib_params_to_device(tas_dev, tmp_val); +- mutex_unlock(&tas_dev->calib_lock); ++ scoped_guard(mutex, &tas_dev->calib_lock) { ++ ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, ++ tas_dev->cali_data.read_sz); ++ if (!ret) ++ tas2783_set_calib_params_to_device(tas_dev, tmp_val); ++ } + + return ret; + } +@@ -926,22 +978,23 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + dev_err(tas_dev->dev, + "clear latch failed, err=%d", ret); + +- mutex_lock(&tas_dev->pde_lock); +- /* +- * Sometimes, there is error returned during power on. +- * So added retry logic to ensure power on so that +- * port prepare succeeds +- */ +- do { +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_ON); +- if (!ret) +- break; +- usleep_range(2000, 2200); +- } while (retry--); +- mutex_unlock(&tas_dev->pde_lock); ++ scoped_guard(mutex, &tas_dev->pde_lock) { ++ /* ++ * Sometimes, there is error returned during power on. ++ * So added retry logic to ensure power on so that ++ * port prepare succeeds ++ */ ++ do { ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (!ret) ++ break; ++ usleep_range(2000, 2200); ++ } while (retry--); ++ } ++ + if (ret) + return ret; + +@@ -965,7 +1018,6 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +- s32 ret; + struct snd_soc_component *component = dai->component; + struct tas2783_prv *tas_dev = + snd_soc_component_get_drvdata(component); +@@ -974,14 +1026,11 @@ static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + + sdw_stream_remove_slave(tas_dev->sdw_peripheral, sdw_stream); + +- mutex_lock(&tas_dev->pde_lock); +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_OFF); +- mutex_unlock(&tas_dev->pde_lock); +- +- return ret; ++ guard(mutex)(&tas_dev->pde_lock); ++ return regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_OFF); + } + + static const struct snd_soc_dai_ops tas_dai_ops = { +@@ -1349,7 +1398,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, + function_data->desc = &peripheral->sdca_data.function[i]; + + /* Parse the function */ +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + tas_dev->sa_func_data = function_data; + else +diff --git a/sound/soc/codecs/tas5805m.c b/sound/soc/codecs/tas5805m.c +--- a/sound/soc/codecs/tas5805m.c ++++ b/sound/soc/codecs/tas5805m.c +@@ -12,6 +12,7 @@ + // + // It has been simplified a little and reworked for the 5.x ALSA SoC API. + ++#include + #include + #include + #include +@@ -230,10 +231,9 @@ static int tas5805m_vol_get(struct snd_kcontrol *kcontrol, + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + ucontrol->value.integer.value[0] = tas5805m->vol[0]; + ucontrol->value.integer.value[1] = tas5805m->vol[1]; +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -249,13 +249,12 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); +- int ret = 0; + + if (!(volume_is_valid(ucontrol->value.integer.value[0]) && + volume_is_valid(ucontrol->value.integer.value[1]))) + return -EINVAL; + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->vol[0] != ucontrol->value.integer.value[0] || + tas5805m->vol[1] != ucontrol->value.integer.value[1]) { + tas5805m->vol[0] = ucontrol->value.integer.value[0]; +@@ -265,11 +264,10 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + tas5805m->is_powered); + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- ret = 1; ++ return 1; + } +- mutex_unlock(&tas5805m->lock); + +- return ret; ++ return 0; + } + + static const struct snd_kcontrol_new tas5805m_snd_controls[] = { +@@ -332,7 +330,7 @@ static void do_work(struct work_struct *work) + + dev_dbg(&tas5805m->i2c->dev, "DSP startup\n"); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + /* We mustn't issue any I2C transactions until the I2S + * clock is stable. Furthermore, we must allow a 5ms + * delay after the first set of register writes to +@@ -345,7 +343,6 @@ static void do_work(struct work_struct *work) + + tas5805m->is_powered = true; + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + } + + static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, +@@ -362,7 +359,7 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + dev_dbg(component->dev, "DSP shutdown\n"); + cancel_work_sync(&tas5805m->work); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->is_powered) { + tas5805m->is_powered = false; + +@@ -379,7 +376,6 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + + regmap_write(rm, REG_DEVICE_CTRL_2, DCTRL2_MODE_HIZ); + } +- mutex_unlock(&tas5805m->lock); + } + + return 0; +@@ -414,14 +410,13 @@ static int tas5805m_mute(struct snd_soc_dai *dai, int mute, int direction) + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + dev_dbg(component->dev, "set mute=%d (is_powered=%d)\n", + mute, tas5805m->is_powered); + + tas5805m->is_muted = mute; + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -462,7 +457,6 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + struct tas5805m_priv *tas5805m; + char filename[128]; + const char *config_name; +- const struct firmware *fw; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &tas5805m_regmap); +@@ -507,24 +501,20 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + + snprintf(filename, sizeof(filename), "tas5805m_dsp_%s.bin", + config_name); ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, dev); + if (ret) + return ret; + + if ((fw->size < 2) || (fw->size & 1)) { + dev_err(dev, "firmware is invalid\n"); +- release_firmware(fw); + return -EINVAL; + } + + tas5805m->dsp_cfg_len = fw->size; + tas5805m->dsp_cfg_data = devm_kmemdup(dev, fw->data, fw->size, GFP_KERNEL); +- if (!tas5805m->dsp_cfg_data) { +- release_firmware(fw); ++ if (!tas5805m->dsp_cfg_data) + return -ENOMEM; +- } +- +- release_firmware(fw); + + /* Do the first part of the power-on here, while we can expect + * the I2S interface to be quiet. We must raise PDN# and then +diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c +--- a/sound/soc/codecs/tlv320aic26.c ++++ b/sound/soc/codecs/tlv320aic26.c +@@ -320,8 +320,14 @@ static int aic26_probe(struct snd_soc_component *component) + return 0; + } + ++static void aic26_remove(struct snd_soc_component *component) ++{ ++ device_remove_file(component->dev, &dev_attr_keyclick); ++} ++ + static const struct snd_soc_component_driver aic26_soc_component_dev = { + .probe = aic26_probe, ++ .remove = aic26_remove, + .controls = aic26_snd_controls, + .num_controls = ARRAY_SIZE(aic26_snd_controls), + .dapm_widgets = tlv320aic26_dapm_widgets, +diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c +--- a/sound/soc/codecs/tlv320aic31xx.c ++++ b/sound/soc/codecs/tlv320aic31xx.c +@@ -1720,18 +1720,14 @@ static int tlv320dac3100_fw_load(struct aic31xx_priv *aic31xx, + static int tlv320dac3100_load_coeffs(struct aic31xx_priv *aic31xx, + const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, aic31xx->dev); + if (ret) + return ret; + +- ret = tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); +- +- release_firmware(fw); +- +- return ret; ++ return tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); + } + + static int aic31xx_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c +--- a/sound/soc/codecs/tlv320aic32x4-clk.c ++++ b/sound/soc/codecs/tlv320aic32x4-clk.c +@@ -1,5 +1,5 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Clock Tree for the Texas Instruments TLV320AIC32x4 + * + * Copyright 2019 Annaliese McDermond +@@ -9,6 +9,7 @@ + + #include + #include ++#include + #include + #include + +@@ -57,7 +58,7 @@ static void clk_aic32x4_pll_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLLEN, 0); ++ AIC32X4_PLLEN, 0); + } + + static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) +@@ -75,7 +76,7 @@ static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) + } + + static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + /* Change to use regmap_bulk_read? */ + unsigned int val; +@@ -106,19 +107,19 @@ static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, + } + + static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + int ret; + /* Change to use regmap_bulk_write for some if not all? */ + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_R_MASK, settings->r); ++ AIC32X4_PLL_R_MASK, settings->r); + if (ret < 0) + return ret; + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_P_MASK, +- settings->p << AIC32X4_PLL_P_SHIFT); ++ AIC32X4_PLL_P_MASK, ++ settings->p << AIC32X4_PLL_P_SHIFT); + if (ret < 0) + return ret; + +@@ -136,23 +137,21 @@ static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, + return 0; + } + +-static unsigned long clk_aic32x4_pll_calc_rate( +- struct clk_aic32x4_pll_muldiv *settings, +- unsigned long parent_rate) ++static unsigned long clk_aic32x4_pll_calc_rate(struct clk_aic32x4_pll_muldiv *settings, ++ unsigned long parent_rate) + { + u64 rate; + /* + * We scale j by 10000 to account for the decimal part of P and divide + * it back out later. + */ +- rate = (u64) parent_rate * settings->r * +- ((settings->j * 10000) + settings->d); ++ rate = (u64)parent_rate * settings->r * ((settings->j * 10000) + settings->d); + +- return (unsigned long) DIV_ROUND_UP_ULL(rate, settings->p * 10000); ++ return (unsigned long)DIV_ROUND_UP_ULL(rate, settings->p * 10000); + } + + static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, +- unsigned long rate, unsigned long parent_rate) ++ unsigned long rate, unsigned long parent_rate) + { + u64 multiplier; + +@@ -165,14 +164,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + * of the multiplier. This is because we can't do floating point + * math in the kernel. + */ +- multiplier = (u64) rate * settings->p * 10000; ++ multiplier = (u64)rate * settings->p * 10000; + do_div(multiplier, parent_rate); + + /* + * J can't be over 64, so R can scale this. + * R can't be greater than 4. + */ +- settings->r = ((u32) multiplier / 640000) + 1; ++ settings->r = ((u32)multiplier / 640000) + 1; + if (settings->r > 4) + return -1; + do_div(multiplier, settings->r); +@@ -184,14 +183,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + return -1; + + /* Figure out the integer part, J, and the fractional part, D. */ +- settings->j = (u32) multiplier / 10000; +- settings->d = (u32) multiplier % 10000; ++ settings->j = (u32)multiplier / 10000; ++ settings->d = (u32)multiplier % 10000; + + return 0; + } + + static unsigned long clk_aic32x4_pll_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -220,8 +219,8 @@ static int clk_aic32x4_pll_determine_rate(struct clk_hw *hw, + } + + static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, +- unsigned long rate, +- unsigned long parent_rate) ++ unsigned long rate, ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -236,7 +235,7 @@ static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, + return ret; + + /* 10ms is the delay to wait before the clocks are stable */ +- msleep(10); ++ usleep_range(10000, 20000); + + return 0; + } +@@ -261,7 +260,6 @@ static u8 clk_aic32x4_pll_get_parent(struct clk_hw *hw) + return (val & AIC32X4_PLL_CLKIN_MASK) >> AIC32X4_PLL_CLKIN_SHIFT; + } + +- + static const struct clk_ops aic32x4_pll_ops = { + .prepare = clk_aic32x4_pll_prepare, + .unprepare = clk_aic32x4_pll_unprepare, +@@ -311,11 +309,11 @@ static void clk_aic32x4_div_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + regmap_update_bits(div->regmap, div->reg, +- AIC32X4_DIVEN, 0); ++ AIC32X4_DIVEN, 0); + } + + static int clk_aic32x4_div_set_rate(struct clk_hw *hw, unsigned long rate, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + u8 divisor; +@@ -342,7 +340,7 @@ static int clk_aic32x4_div_determine_rate(struct clk_hw *hw, + } + + static unsigned long clk_aic32x4_div_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + unsigned int val; +@@ -399,7 +397,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + { + .name = "pll", + .parent_names = +- (const char* []) { "mclk", "bclk", "gpio", "din" }, ++ (const char *[]) { "mclk", "bclk", "gpio", "din" }, + .num_parents = 4, + .ops = &aic32x4_pll_ops, + .reg = 0, +@@ -414,28 +412,28 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }, + { + .name = "ndac", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NDAC, + }, + { + .name = "mdac", +- .parent_names = (const char * []) { "ndac" }, ++ .parent_names = (const char *[]) { "ndac" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MDAC, + }, + { + .name = "nadc", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NADC, + }, + { + .name = "madc", +- .parent_names = (const char * []) { "nadc" }, ++ .parent_names = (const char *[]) { "nadc" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MADC, +@@ -451,7 +449,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }; + + static struct clk *aic32x4_register_clk(struct device *dev, +- struct aic32x4_clkdesc *desc) ++ struct aic32x4_clkdesc *desc) + { + struct clk_init_data init; + struct clk_aic32x4 *priv; +@@ -464,8 +462,8 @@ static struct clk *aic32x4_register_clk(struct device *dev, + init.flags = 0; + + priv = devm_kzalloc(dev, sizeof(struct clk_aic32x4), GFP_KERNEL); +- if (priv == NULL) +- return (struct clk *) -ENOMEM; ++ if (!priv) ++ return ERR_PTR(-ENOMEM); + + priv->dev = dev; + priv->hw.init = &init; +diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c +--- a/sound/soc/codecs/tlv320aic32x4-i2c.c ++++ b/sound/soc/codecs/tlv320aic32x4-i2c.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,17 +15,23 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_i2c_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 8; +- config.val_bits = 8; ++ regmap = devm_regmap_init_i2c(i2c, &aic32x4_i2c_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_i2c(i2c, &config); + type = (uintptr_t)i2c_get_match_data(i2c); + + return aic32x4_probe(&i2c->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c +--- a/sound/soc/codecs/tlv320aic32x4-spi.c ++++ b/sound/soc/codecs/tlv320aic32x4-spi.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,19 +15,25 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_spi_regmap_config = { ++ .reg_bits = 7, ++ .pad_bits = 1, ++ .val_bits = 8, ++ .read_flag_mask = 0x01, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_spi_probe(struct spi_device *spi) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 7; +- config.pad_bits = 1; +- config.val_bits = 8; +- config.read_flag_mask = 0x01; ++ regmap = devm_regmap_init_spi(spi, &aic32x4_spi_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_spi(spi, &config); + type = (uintptr_t)spi_get_device_match_data(spi); + + return aic32x4_probe(&spi->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c +--- a/sound/soc/codecs/tlv320aic32x4.c ++++ b/sound/soc/codecs/tlv320aic32x4.c +@@ -1,7 +1,5 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + /* +- * linux/sound/soc/codecs/tlv320aic32x4.c +- * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin +@@ -28,7 +26,6 @@ + #include + #include + #include +-#include + + #include "tlv320aic32x4.h" + +@@ -45,7 +42,8 @@ struct aic32x4_priv { + struct regulator *supply_dv; + struct regulator *supply_av; + +- struct aic32x4_setup_data *setup; ++ unsigned int gpio_func[5]; ++ + struct device *dev; + enum aic32x4_type type; + +@@ -75,7 +73,7 @@ static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w, + }; + + static int mic_bias_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *kcontrol, int event) ++ struct snd_kcontrol *kcontrol, int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + +@@ -83,25 +81,23 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, + case SND_SOC_DAPM_POST_PMU: + /* Change Mic Bias Registor */ + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, +- AIC32X4_MICBIAS_LDOIN | +- AIC32X4_MICBIAS_2075V); +- printk(KERN_DEBUG "%s: Mic Bias will be turned ON\n", __func__); ++ AIC32x4_MICBIAS_MASK, ++ AIC32X4_MICBIAS_LDOIN | ++ AIC32X4_MICBIAS_2075V); ++ dev_dbg(component->dev, "Mic Bias will be turned ON\n"); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, 0); +- printk(KERN_DEBUG "%s: Mic Bias will be turned OFF\n", +- __func__); ++ AIC32x4_MICBIAS_MASK, 0); ++ dev_dbg(component->dev, "Mic Bias will be turned OFF\n"); + break; + } + + return 0; + } + +- + static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -114,7 +110,7 @@ static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -123,8 +119,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_DOUTCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP2 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP2 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -142,7 +137,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -155,7 +150,7 @@ static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -164,8 +159,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_MISOCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP4 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP4 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -183,7 +177,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -195,7 +189,7 @@ static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -204,8 +198,7 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_GPIOCTL); + gpio_check = (val & AIC32X4_MFP5_GPIO_OUTPUT); + if (gpio_check != AIC32X4_MFP5_GPIO_OUTPUT) { +- printk(KERN_ERR "%s: MFP5 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP5 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -222,25 +215,31 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + return 0; + }; + +-static const struct snd_kcontrol_new aic32x4_mfp1[] = { +- SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp2[] = { +- SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp3[] = { +- SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp4[] = { +- SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp5[] = { +- SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, +- aic32x4_set_mfp5_gpio), ++static const struct { ++ unsigned int reg; ++ struct snd_kcontrol_new ctrl; ++} aic32x4_mfp_cfg[] = { ++ { ++ .reg = AIC32X4_DINCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_DOUTCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), ++ }, ++ { ++ .reg = AIC32X4_SCLKCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_MISOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), ++ }, ++ { ++ .reg = AIC32X4_GPIOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, ++ aic32x4_set_mfp5_gpio), ++ }, + }; + + /* 0dB min, 0.5dB steps */ +@@ -270,53 +269,42 @@ static SOC_ENUM_SINGLE_DECL(r_ptm_enum, AIC32X4_RPLAYBACK, 2, ptm_text); + + static const struct snd_kcontrol_new aic32x4_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("PCM Playback Volume", AIC32X4_LDACVOL, +- AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), ++ AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), + SOC_ENUM("DAC Left Playback PowerTune Switch", l_ptm_enum), + SOC_ENUM("DAC Right Playback PowerTune Switch", r_ptm_enum), + SOC_DOUBLE_R_S_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R_S_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 6, 0x01, 1), ++ AIC32X4_HPRGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 6, 0x01, 1), ++ AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_ENUM("LO Playback Common Mode Switch", lo_cm_enum), + SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 7, 0x01, 1), ++ AIC32X4_RMICPGAVOL, 7, 0x01, 1), + + SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), + SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0), + + SOC_DOUBLE_R_S_TLV("ADC Level Volume", AIC32X4_LADCVOL, +- AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), ++ AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), + SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), ++ AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + + SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0), + SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0), +- SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 4, 0x07, 0), +- SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 0, 0x03, 0), +- SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 6, 0x03, 0), +- SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 1, 0x1F, 0), +- SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, +- 0, 0x7F, 0), +- SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, +- 0, 0x1F, 0), +- SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, +- 0, 0x0F, 0), ++ SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, 4, 0x07, 0), ++ SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, 0, 0x03, 0), ++ SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, 6, 0x03, 0), ++ SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, 1, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, 0, 0x7F, 0), ++ SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, 0, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, 0, 0x0F, 0), + }; + + static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { +@@ -354,21 +342,27 @@ static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cml_to_lmixer_controls[] = { + SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), + }; +@@ -385,21 +379,27 @@ static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = { + SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum), + }; +@@ -428,38 +428,38 @@ static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_rmixer_controls), ++ in1r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_rmixer_controls), ++ in2r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_rmixer_controls), ++ in3r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_rmixer_controls), ++ in2l_to_rmixer_controls), + SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cmr_to_rmixer_controls), ++ cmr_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_rmixer_controls), ++ in1l_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_rmixer_controls), ++ in3l_to_rmixer_controls), + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_lmixer_controls), ++ in1l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_lmixer_controls), ++ in2l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_lmixer_controls), ++ in3l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_lmixer_controls), ++ in1r_to_lmixer_controls), + SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cml_to_lmixer_controls), ++ cml_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_lmixer_controls), ++ in2r_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_lmixer_controls), ++ in3r_to_lmixer_controls), + + SND_SOC_DAPM_SUPPLY("Mic Bias", AIC32X4_MICBIAS, 6, 0, mic_bias_event, +- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_POST("ADC Reset", aic32x4_reset_adc), + +@@ -575,7 +575,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + {"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"}, + }; + +-static const struct regmap_range_cfg aic32x4_regmap_pages[] = { ++const struct regmap_range_cfg aic32x4_regmap_pages[] = { + { + .selector_reg = 0, + .selector_mask = 0xff, +@@ -585,13 +585,7 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = { + .range_max = AIC32X4_REFPOWERUP, + }, + }; +- +-const struct regmap_config aic32x4_regmap_config = { +- .max_register = AIC32X4_REFPOWERUP, +- .ranges = aic32x4_regmap_pages, +- .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), +-}; +-EXPORT_SYMBOL(aic32x4_regmap_config); ++EXPORT_SYMBOL_GPL(aic32x4_regmap_pages); + + static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +@@ -624,7 +618,7 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: +- printk(KERN_ERR "aic32x4: invalid clock provider\n"); ++ dev_err(component->dev, "invalid clock provider\n"); + return -EINVAL; + } + +@@ -651,19 +645,20 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + AIC32X4_IFACE1_DATATYPE_SHIFT); + break; + default: +- printk(KERN_ERR "aic32x4: invalid DAI interface format\n"); ++ dev_err(component->dev, "invalid DAI interface format\n"); + return -EINVAL; + } + + aic32x4->fmt = fmt; + + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATATYPE_MASK | +- AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); ++ AIC32X4_IFACE1_DATATYPE_MASK | ++ AIC32X4_IFACE1_MASTER_MASK, ++ iface_reg_1); + snd_soc_component_update_bits(component, AIC32X4_IFACE2, +- AIC32X4_DATA_OFFSET_MASK, iface_reg_2); ++ AIC32X4_DATA_OFFSET_MASK, iface_reg_2); + snd_soc_component_update_bits(component, AIC32X4_IFACE3, +- AIC32X4_BCLKINV_MASK, iface_reg_3); ++ AIC32X4_BCLKINV_MASK, iface_reg_3); + + return 0; + } +@@ -676,14 +671,13 @@ static int aic32x4_set_aosr(struct snd_soc_component *component, u8 aosr) + static int aic32x4_set_dosr(struct snd_soc_component *component, u16 dosr) + { + snd_soc_component_write(component, AIC32X4_DOSRMSB, dosr >> 8); +- snd_soc_component_write(component, AIC32X4_DOSRLSB, +- (dosr & 0xff)); ++ snd_soc_component_write(component, AIC32X4_DOSRLSB, dosr & 0xff); + + return 0; + } + + static int aic32x4_set_processing_blocks(struct snd_soc_component *component, +- u8 r_block, u8 p_block) ++ u8 r_block, u8 p_block) + { + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + +@@ -703,6 +697,45 @@ static int aic32x4_set_processing_blocks(struct snd_soc_component *component, + return 0; + } + ++static int aic32x4_configure_rate(struct snd_soc_component *component, ++ unsigned int rate, u8 *aosr, u8 *adc_rc, ++ u8 *dac_rc, u8 *dosr_inc) ++{ ++ struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); ++ u8 prb_rx, prb_tx; ++ ++ if (rate <= 48000) { ++ *aosr = 128; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 8; ++ prb_rx = 1; ++ prb_tx = 1; ++ } else if (rate <= 96000) { ++ *aosr = 64; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 4; ++ prb_rx = 1; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 9; ++ } else if (rate == 192000) { ++ *aosr = 32; ++ *adc_rc = 3; ++ *dac_rc = 4; ++ *dosr_inc = 2; ++ prb_rx = 13; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 19; ++ } else { ++ dev_err(component->dev, "Sampling rate %u not supported\n", rate); ++ return -EINVAL; ++ } ++ ++ if (aic32x4->type == AIC32X4_TYPE_TAS2505) ++ prb_rx = 0; ++ ++ return aic32x4_set_processing_blocks(component, prb_rx, prb_tx); ++} ++ + static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned int sample_rate, unsigned int channels, + unsigned int bit_depth) +@@ -717,7 +750,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned long adc_clock_rate, dac_clock_rate; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "pll" }, + { .id = "nadc" }, + { .id = "madc" }, +@@ -729,37 +762,11 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + if (ret) + return ret; + +- if (sample_rate <= 48000) { +- aosr = 128; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 8; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 1); +- } else if (sample_rate <= 96000) { +- aosr = 64; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 4; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 9); +- } else if (sample_rate == 192000) { +- aosr = 32; +- adc_resource_class = 3; +- dac_resource_class = 4; +- dosr_increment = 2; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 13, 19); +- } else { +- dev_err(component->dev, "Sampling rate not supported\n"); +- return -EINVAL; +- } ++ ret = aic32x4_configure_rate(component, sample_rate, &aosr, ++ &adc_resource_class, &dac_resource_class, ++ &dosr_increment); ++ if (ret) ++ return ret; + + /* PCM over I2S is always 2-channel */ + if ((aic32x4->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) +@@ -781,50 +788,39 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + (min_mdac * dosr * sample_rate); + for (mdac = min_mdac; mdac <= 128; ++mdac) { + for (ndac = max_ndac; ndac > 0; --ndac) { +- dac_clock_rate = ndac * mdac * dosr * +- sample_rate; +- if (dac_clock_rate == adc_clock_rate) { +- if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) +- continue; ++ dac_clock_rate = ndac * mdac * dosr * sample_rate; ++ if (dac_clock_rate != adc_clock_rate) ++ continue; + +- clk_set_rate(clocks[0].clk, +- dac_clock_rate); ++ if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) ++ continue; + +- clk_set_rate(clocks[1].clk, +- sample_rate * aosr * +- madc); +- clk_set_rate(clocks[2].clk, +- sample_rate * aosr); +- aic32x4_set_aosr(component, +- aosr); ++ clk_set_rate(clocks[0].clk, dac_clock_rate); + +- clk_set_rate(clocks[3].clk, +- sample_rate * dosr * +- mdac); +- clk_set_rate(clocks[4].clk, +- sample_rate * dosr); +- aic32x4_set_dosr(component, +- dosr); ++ clk_set_rate(clocks[1].clk, sample_rate * aosr * madc); ++ clk_set_rate(clocks[2].clk, sample_rate * aosr); ++ aic32x4_set_aosr(component, aosr); + +- clk_set_rate(clocks[5].clk, +- sample_rate * channels * +- bit_depth); ++ clk_set_rate(clocks[3].clk, sample_rate * dosr * mdac); ++ clk_set_rate(clocks[4].clk, sample_rate * dosr); ++ aic32x4_set_dosr(component, dosr); + +- return 0; +- } ++ clk_set_rate(clocks[5].clk, ++ sample_rate * channels * bit_depth); ++ ++ return 0; + } + } + } + } + +- dev_err(component->dev, +- "Could not set clocks to support sample rate.\n"); ++ dev_err(component->dev, "Could not set clocks to support sample rate.\n"); + return -EINVAL; + } + + static int aic32x4_hw_params(struct snd_pcm_substream *substream, +- struct snd_pcm_hw_params *params, +- struct snd_soc_dai *dai) ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { + struct snd_soc_component *component = dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); +@@ -837,24 +833,20 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + + switch (params_physical_width(params)) { + case 16: +- iface1_reg |= (AIC32X4_WORD_LEN_16BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_16BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 20: +- iface1_reg |= (AIC32X4_WORD_LEN_20BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 24: +- iface1_reg |= (AIC32X4_WORD_LEN_24BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 32: +- iface1_reg |= (AIC32X4_WORD_LEN_32BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + } + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); ++ AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); + + if (params_channels(params) == 1) { + dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2LCHN; +@@ -865,7 +857,7 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + dacsetup_reg = AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN; + } + snd_soc_component_update_bits(component, AIC32X4_DACSETUP, +- AIC32X4_DAC_CHAN_MASK, dacsetup_reg); ++ AIC32X4_DAC_CHAN_MASK, dacsetup_reg); + + return 0; + } +@@ -875,7 +867,7 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute, int direction) + struct snd_soc_component *component = dai->component; + + snd_soc_component_update_bits(component, AIC32X4_DACMUTE, +- AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); ++ AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); + + return 0; + } +@@ -886,7 +878,7 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "madc" }, + { .id = "mdac" }, + { .id = "bdiv" }, +@@ -955,44 +947,14 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + + /* setup GPIO functions */ +- /* MFP1 */ +- if (aic32x4->setup->gpio_func[0] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DINCTL, +- aic32x4->setup->gpio_func[0]); +- snd_soc_add_component_controls(component, aic32x4_mfp1, +- ARRAY_SIZE(aic32x4_mfp1)); +- } ++ BUILD_BUG_ON(ARRAY_SIZE(aic32x4->gpio_func) != ARRAY_SIZE(aic32x4_mfp_cfg)); ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) { ++ if (aic32x4->gpio_func[i] == AIC32X4_MFPX_DEFAULT_VALUE) ++ continue; + +- /* MFP2 */ +- if (aic32x4->setup->gpio_func[1] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DOUTCTL, +- aic32x4->setup->gpio_func[1]); +- snd_soc_add_component_controls(component, aic32x4_mfp2, +- ARRAY_SIZE(aic32x4_mfp2)); +- } +- +- /* MFP3 */ +- if (aic32x4->setup->gpio_func[2] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_SCLKCTL, +- aic32x4->setup->gpio_func[2]); +- snd_soc_add_component_controls(component, aic32x4_mfp3, +- ARRAY_SIZE(aic32x4_mfp3)); +- } +- +- /* MFP4 */ +- if (aic32x4->setup->gpio_func[3] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_MISOCTL, +- aic32x4->setup->gpio_func[3]); +- snd_soc_add_component_controls(component, aic32x4_mfp4, +- ARRAY_SIZE(aic32x4_mfp4)); +- } +- +- /* MFP5 */ +- if (aic32x4->setup->gpio_func[4] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_GPIOCTL, +- aic32x4->setup->gpio_func[4]); +- snd_soc_add_component_controls(component, aic32x4_mfp5, +- ARRAY_SIZE(aic32x4_mfp5)); ++ snd_soc_component_write(component, aic32x4_mfp_cfg[i].reg, ++ aic32x4->gpio_func[i]); ++ snd_soc_add_component_controls(component, &aic32x4_mfp_cfg[i].ctrl, 1); + } + } + +@@ -1002,7 +964,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1013,8 +975,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1022,7 +983,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Power platform configuration */ + if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { + snd_soc_component_write(component, AIC32X4_MICBIAS, +- AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); ++ AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) + snd_soc_component_write(component, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); +@@ -1041,16 +1002,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Mic PGA routing */ + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_IN2R_10K); ++ AIC32X4_LMICPGANIN_IN2R_10K); + else + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_CM1L_10K); ++ AIC32X4_LMICPGANIN_CM1L_10K); + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_IN1L_10K); ++ AIC32X4_RMICPGANIN_IN1L_10K); + else + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_CM1R_10K); ++ AIC32X4_RMICPGANIN_CM1R_10K); + + /* + * Workaround: for an unknown reason, the ADC needs to be powered up +@@ -1058,8 +1019,8 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + * a HW BUG or some kind of behavior not documented in the datasheet. + */ + tmp_reg = snd_soc_component_read(component, AIC32X4_ADCSETUP); +- snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg | +- AIC32X4_LADC_EN | AIC32X4_RADC_EN); ++ snd_soc_component_write(component, AIC32X4_ADCSETUP, ++ tmp_reg | AIC32X4_LADC_EN | AIC32X4_RADC_EN); + snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg); + + /* +@@ -1102,13 +1063,13 @@ static const struct snd_kcontrol_new aic32x4_tas2505_snd_controls[] = { + SOC_ENUM("DAC Playback PowerTune Switch", l_ptm_enum), + + SOC_SINGLE_TLV("HP Driver Gain Volume", +- AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), ++ AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE("HP DAC Playback Switch", AIC32X4_HPLGAIN, 6, 1, 1), + + SOC_SINGLE_TLV("Speaker Driver Playback Volume", +- TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), ++ TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE_TLV("Speaker Amplifier Playback Volume", +- TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), ++ TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + }; +@@ -1144,11 +1105,12 @@ static const struct snd_soc_dapm_route aic32x4_tas2505_dapm_routes[] = { + static struct snd_soc_dai_driver aic32x4_tas2505_dai = { + .name = "tas2505-hifi", + .playback = { +- .stream_name = "Playback", +- .channels_min = 1, +- .channels_max = 2, +- .rates = SNDRV_PCM_RATE_8000_96000, +- .formats = AIC32X4_FORMATS,}, ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = AIC32X4_FORMATS, ++ }, + .ops = &aic32x4_ops, + .symmetric_rate = 1, + }; +@@ -1159,7 +1121,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1170,8 +1132,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1192,7 +1153,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AIC32X4_CMMODE, tmp_reg); + + /* +- * Enable the fast charging feature and ensure the needed 40ms ellapsed ++ * Enable the fast charging feature and ensure the needed 40ms elapsed + * before using the analog circuits. + */ + snd_soc_component_write(component, TAS2505_REFPOWERUP, +@@ -1218,17 +1179,10 @@ static const struct snd_soc_component_driver soc_component_dev_aic32x4_tas2505 = + .endianness = 1, + }; + +-static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, +- struct device_node *np) ++static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, struct device_node *np) + { +- struct aic32x4_setup_data *aic32x4_setup; + int ret; + +- aic32x4_setup = devm_kzalloc(aic32x4->dev, sizeof(*aic32x4_setup), +- GFP_KERNEL); +- if (!aic32x4_setup) +- return -ENOMEM; +- + ret = of_property_match_string(np, "clock-names", "mclk"); + if (ret < 0) + return -EINVAL; +@@ -1245,9 +1199,11 @@ static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, + gpiod_set_consumer_name(aic32x4->rstn_gpio, "tlv320aic32x4_rstn"); + } + +- if (of_property_read_u32_array(np, "aic32x4-gpio-func", +- aic32x4_setup->gpio_func, 5) >= 0) +- aic32x4->setup = aic32x4_setup; ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) ++ aic32x4->gpio_func[i] = AIC32X4_MFPX_DEFAULT_VALUE; ++ of_property_read_u32_array(np, "aic32x4-gpio-func", ++ aic32x4->gpio_func, ARRAY_SIZE(aic32x4->gpio_func)); ++ + return 0; + } + +@@ -1266,7 +1222,7 @@ static void aic32x4_disable_regulators(struct aic32x4_priv *aic32x4) + } + + static int aic32x4_setup_regulators(struct device *dev, +- struct aic32x4_priv *aic32x4) ++ struct aic32x4_priv *aic32x4) + { + int ret = 0; + +@@ -1356,12 +1312,8 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + struct device_node *np = dev->of_node; + int ret; + +- if (IS_ERR(regmap)) +- return PTR_ERR(regmap); +- +- aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), +- GFP_KERNEL); +- if (aic32x4 == NULL) ++ aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), GFP_KERNEL); ++ if (!aic32x4) + return -ENOMEM; + + aic32x4->dev = dev; +@@ -1405,11 +1357,13 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + switch (aic32x4->type) { + case AIC32X4_TYPE_TAS2505: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4_tas2505, &aic32x4_tas2505_dai, 1); ++ &soc_component_dev_aic32x4_tas2505, ++ &aic32x4_tas2505_dai, 1); + break; + default: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4, &aic32x4_dai, 1); ++ &soc_component_dev_aic32x4, ++ &aic32x4_dai, 1); + } + + if (ret) { +diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h +--- a/sound/soc/codecs/tlv320aic32x4.h ++++ b/sound/soc/codecs/tlv320aic32x4.h +@@ -3,7 +3,6 @@ + * tlv320aic32x4.h + */ + +- + #ifndef _TLV320AIC32X4_H + #define _TLV320AIC32X4_H + +@@ -16,7 +15,7 @@ enum aic32x4_type { + AIC32X4_TYPE_TAS2505, + }; + +-extern const struct regmap_config aic32x4_regmap_config; ++extern const struct regmap_range_cfg aic32x4_regmap_pages[]; + int aic32x4_probe(struct device *dev, struct regmap *regmap, + enum aic32x4_type type); + void aic32x4_remove(struct device *dev); +@@ -24,7 +23,7 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + + /* tlv320aic32x4 register space (in decimal to match datasheet) */ + +-#define AIC32X4_REG(page, reg) ((page * 128) + reg) ++#define AIC32X4_REG(page, reg) (((page) * 128) + (reg)) + + #define AIC32X4_PSEL AIC32X4_REG(0, 0) + +@@ -234,4 +233,31 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + #define AIC32X4_MAX_CODEC_CLKIN_FREQ 110000000 + #define AIC32X4_MAX_PLL_CLKIN 20000000 + ++#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 ++#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 ++#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 ++#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 ++#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 ++ ++#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 ++#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 ++ ++/* GPIO API */ ++#define AIC32X4_MFPX_DEFAULT_VALUE 0xff ++ ++#define AIC32X4_MFP1_DIN_DISABLED 0 ++#define AIC32X4_MFP1_DIN_ENABLED 0x2 ++#define AIC32X4_MFP1_GPIO_IN 0x4 ++ ++#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 ++ ++#define AIC32X4_MFP_GPIO_ENABLED 0x4 ++ ++#define AIC32X4_MFP5_GPIO_DISABLED 0x0 ++#define AIC32X4_MFP5_GPIO_INPUT 0x8 ++#define AIC32X4_MFP5_GPIO_OUTPUT 0xc ++#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 ++ + #endif /* _TLV320AIC32X4_H */ +diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c +--- a/sound/soc/codecs/tlv320dac33.c ++++ b/sound/soc/codecs/tlv320dac33.c +@@ -7,6 +7,7 @@ + * Copyright: (C) 2009 Nokia Corporation + */ + ++#include + #include + #include + #include +@@ -236,13 +237,10 @@ static int dac33_write_locked(struct snd_soc_component *component, unsigned int + unsigned int value) + { + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&dac33->mutex); +- ret = dac33_write(component, reg, value); +- mutex_unlock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + +- return ret; ++ return dac33_write(component, reg, value); + } + + #define DAC33_I2C_ADDR_AUTOINC 0x80 +@@ -365,13 +363,13 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_dbg(component->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); +- goto exit; ++ return ret; + } + + if (power) { +@@ -380,7 +378,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + if (dac33->reset_gpiod) { +@@ -388,7 +386,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -400,7 +398,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -409,14 +407,12 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to disable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + dac33->chip_power = 0; + } + +-exit: +- mutex_unlock(&dac33->mutex); + return ret; + } + +@@ -659,7 +655,6 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; + unsigned int delay; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: +@@ -667,10 +662,10 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + DAC33_THRREG(dac33->nsample)); + + /* Take the timestamps */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- dac33->t_stamp1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); ++ dac33->t_stamp1 = dac33->t_stamp2; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(dac33->alarm_threshold)); +@@ -682,11 +677,11 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + break; + case DAC33_FIFO_MODE7: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- /* Move back the timestamp with drain time */ +- dac33->t_stamp1 -= dac33->mode7_us_to_lthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); ++ /* Move back the timestamp with drain time */ ++ dac33->t_stamp1 -= dac33->mode7_us_to_lthr; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(DAC33_MODE7_MARGIN)); +@@ -704,14 +699,12 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); + + dac33_write16(component, DAC33_NSAMPLE_MSB, + DAC33_THRREG(dac33->nsample)); +@@ -735,7 +728,7 @@ static void dac33_work(struct work_struct *work) + dac33 = container_of(work, struct tlv320dac33_priv, work); + component = dac33->component; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + switch (dac33->state) { + case DAC33_PREFILL: + dac33->state = DAC33_PLAYBACK; +@@ -757,18 +750,15 @@ static void dac33_work(struct work_struct *work) + dac33_write(component, DAC33_FIFO_CTRL_A, reg); + break; + } +- mutex_unlock(&dac33->mutex); + } + + static irqreturn_t dac33_interrupt_handler(int irq, void *dev) + { + struct snd_soc_component *component = dev; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); + + /* Do not schedule the workqueue in Mode7 */ + if (dac33->fifo_mode != DAC33_FIFO_MODE7) +@@ -902,14 +892,13 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ +- mutex_unlock(&dac33->mutex); + return 0; + } + +@@ -1053,8 +1042,6 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + break; + } + +- mutex_unlock(&dac33->mutex); +- + return 0; + } + +@@ -1156,21 +1143,20 @@ static snd_pcm_sframes_t dac33_dai_delay( + unsigned int time_delta, uthr; + int samples_out, samples_in, samples; + snd_pcm_sframes_t delay = 0; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_BYPASS: + break; + case DAC33_FIFO_MODE1: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- t1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ t1 = dac33->t_stamp2; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t1) +- goto out; ++ return 0; + + if (t0 > t1) { + /* +@@ -1230,23 +1216,22 @@ static snd_pcm_sframes_t dac33_dai_delay( + } + break; + case DAC33_FIFO_MODE7: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- uthr = dac33->uthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ uthr = dac33->uthr; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t0) +- goto out; ++ return 0; + + if (t_now <= t0) { + /* + * Either the timestamps are messed or equal. Report + * maximum delay + */ +- delay = uthr; +- goto out; ++ return uthr; + } + + time_delta = t_now - t0; +@@ -1287,7 +1272,7 @@ static snd_pcm_sframes_t dac33_dai_delay( + dac33->fifo_mode); + break; + } +-out: ++ + return delay; + } + +diff --git a/sound/soc/codecs/tscs42xx.c b/sound/soc/codecs/tscs42xx.c +--- a/sound/soc/codecs/tscs42xx.c ++++ b/sound/soc/codecs/tscs42xx.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -210,25 +211,21 @@ static int power_up_audio_plls(struct snd_soc_component *component) + return ret; + } + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, mask, val); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL on (%d)\n", ret); +- goto exit; ++ return ret; + } + + if (!plls_locked(component)) { + dev_err(component->dev, "Failed to lock plls\n"); + ret = -ENOMSG; +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int power_down_audio_plls(struct snd_soc_component *component) +@@ -236,28 +233,24 @@ static int power_down_audio_plls(struct snd_soc_component *component) + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL1, + RV_PLLCTL1C_PDB_PLL1_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL2, + RV_PLLCTL1C_PDB_PLL2_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int coeff_ram_get(struct snd_kcontrol *kcontrol, +@@ -269,13 +262,11 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + memcpy(ucontrol->value.bytes.data, + &tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], params->max); + +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- + return 0; + } + +@@ -290,14 +281,14 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + unsigned int coeff_cnt = params->max / COEFF_SIZE; + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + tscs42xx->coeff_ram_synced = false; + + memcpy(&tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + if (plls_locked(component)) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, +@@ -305,18 +296,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + /* Input L Capture Route */ +@@ -385,21 +370,17 @@ static int dac_event(struct snd_soc_dapm_widget *w, + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + if (!tscs42xx->coeff_ram_synced) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, 0x00, + COEFF_RAM_COEFF_COUNT); + if (ret < 0) +- goto exit; ++ return ret; + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static const struct snd_soc_dapm_widget tscs42xx_dapm_widgets[] = { +@@ -926,12 +907,10 @@ static int setup_sample_rate(struct snd_soc_component *component, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->samplerate = rate; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +@@ -1253,12 +1232,10 @@ static int tscs42xx_set_dai_bclk_ratio(struct snd_soc_dai *codec_dai, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->bclk_ratio = ratio; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +diff --git a/sound/soc/codecs/tscs454.c b/sound/soc/codecs/tscs454.c +--- a/sound/soc/codecs/tscs454.c ++++ b/sound/soc/codecs/tscs454.c +@@ -4,6 +4,7 @@ + // Author: Steven Eckhoff + + #include ++#include + #include + #include + #include +@@ -329,12 +330,10 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); +- +- memcpy(ucontrol->value.bytes.data, +- &coeff_ram[ctl->addr * COEFF_SIZE], params->max); +- +- mutex_unlock(coeff_ram_lock); ++ scoped_guard(mutex, coeff_ram_lock) { ++ memcpy(ucontrol->value.bytes.data, ++ &coeff_ram[ctl->addr * COEFF_SIZE], params->max); ++ } + + return 0; + } +@@ -428,15 +427,15 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); ++ guard(mutex)(coeff_ram_lock); + + *coeff_ram_synced = false; + + memcpy(&coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs454->pll1.lock); +- mutex_lock(&tscs454->pll2.lock); ++ guard(mutex)(&tscs454->pll1.lock); ++ guard(mutex)(&tscs454->pll2.lock); + + val = snd_soc_component_read(component, R_PLLSTAT); + if (val) { /* PLLs locked */ +@@ -446,18 +445,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + *coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->pll2.lock); +- mutex_unlock(&tscs454->pll1.lock); +- mutex_unlock(coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static inline int coeff_ram_sync(struct snd_soc_component *component, +@@ -465,41 +458,35 @@ static inline int coeff_ram_sync(struct snd_soc_component *component, + { + int ret; + +- mutex_lock(&tscs454->dac_ram.lock); +- if (!tscs454->dac_ram.synced) { +- ret = write_coeff_ram(component, tscs454->dac_ram.cache, +- R_DACCRS, R_DACCRADD, R_DACCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->dac_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->dac_ram.lock) { ++ if (!tscs454->dac_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->dac_ram.cache, ++ R_DACCRS, R_DACCRADD, R_DACCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->dac_ram.lock); + +- mutex_lock(&tscs454->spk_ram.lock); +- if (!tscs454->spk_ram.synced) { +- ret = write_coeff_ram(component, tscs454->spk_ram.cache, +- R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->spk_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->spk_ram.lock) { ++ if (!tscs454->spk_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->spk_ram.cache, ++ R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->spk_ram.lock); + +- mutex_lock(&tscs454->sub_ram.lock); +- if (!tscs454->sub_ram.synced) { +- ret = write_coeff_ram(component, tscs454->sub_ram.cache, +- R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->sub_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->sub_ram.lock) { ++ if (!tscs454->sub_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->sub_ram.cache, ++ R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->sub_ram.lock); + + return 0; + } +@@ -658,16 +645,14 @@ static int set_sysclk(struct snd_soc_component *component) + + static inline void reserve_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users++; +- mutex_unlock(&pll->lock); + } + + static inline void free_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users--; +- mutex_unlock(&pll->lock); + } + + static int pll_connected(struct snd_soc_dapm_widget *source, +@@ -679,15 +664,13 @@ static int pll_connected(struct snd_soc_dapm_widget *source, + int users; + + if (strstr(source->name, "PLL 1")) { +- mutex_lock(&tscs454->pll1.lock); +- users = tscs454->pll1.users; +- mutex_unlock(&tscs454->pll1.lock); ++ scoped_guard(mutex, &tscs454->pll1.lock) ++ users = tscs454->pll1.users; + dev_dbg(component->dev, "%s(): PLL 1 users = %d\n", __func__, + users); + } else { +- mutex_lock(&tscs454->pll2.lock); +- users = tscs454->pll2.users; +- mutex_unlock(&tscs454->pll2.lock); ++ scoped_guard(mutex, &tscs454->pll2.lock) ++ users = tscs454->pll2.users; + dev_dbg(component->dev, "%s(): PLL 2 users = %d\n", __func__, + users); + } +@@ -806,7 +789,7 @@ static inline int aif_free(struct snd_soc_component *component, + { + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d\n", __func__, aif->id); + +@@ -829,8 +812,6 @@ static inline int aif_free(struct snd_soc_component *component, + free_pll(tscs454->internal_rate.pll); + } + +- mutex_unlock(&tscs454->aifs_status_lock); +- + return 0; + } + +@@ -3174,7 +3155,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + unsigned int val; + int ret; + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d fs = %u\n", __func__, + aif->id, fs); +@@ -3207,14 +3188,14 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + ret = set_aif_fs(component, aif->id, fs); + if (ret < 0) { + dev_err(component->dev, "Failed to set aif fs (%d)\n", ret); +- goto exit; ++ return ret; + } + + ret = set_aif_sample_format(component, params_format(params), aif->id); + if (ret < 0) { + dev_err(component->dev, + "Failed to set aif sample format (%d)\n", ret); +- goto exit; ++ return ret; + } + + set_aif_status_active(&tscs454->aifs_status, aif->id, +@@ -3223,11 +3204,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + dev_dbg(component->dev, "Set aif %d active. Streams status is 0x%x\n", + aif->id, tscs454->aifs_status.streams); + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->aifs_status_lock); +- +- return ret; ++ return 0; + } + + static int tscs454_hw_free(struct snd_pcm_substream *substream, +diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c +--- a/sound/soc/codecs/twl6040.c ++++ b/sound/soc/codecs/twl6040.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int status; + +- mutex_lock(&priv->mutex); ++ guard(mutex)(&priv->mutex); + + /* Sync status */ + status = twl6040_read(component, TWL6040_REG_STATUS); +@@ -281,8 +282,6 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + snd_soc_jack_report(jack, report, report); + else + snd_soc_jack_report(jack, 0, report); +- +- mutex_unlock(&priv->mutex); + } + + void twl6040_hs_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/uda1342.c b/sound/soc/codecs/uda1342.c +--- a/sound/soc/codecs/uda1342.c ++++ b/sound/soc/codecs/uda1342.c +@@ -308,9 +308,16 @@ static int uda1342_suspend(struct device *dev) + static int uda1342_resume(struct device *dev) + { + struct uda1342_priv *uda1342 = dev_get_drvdata(dev); ++ int ret; + ++ regcache_cache_only(uda1342->regmap, false); + regcache_mark_dirty(uda1342->regmap); +- regcache_sync(uda1342->regmap); ++ ret = regcache_sync(uda1342->regmap); ++ if (ret) { ++ regcache_cache_only(uda1342->regmap, true); ++ regcache_mark_dirty(uda1342->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c +--- a/sound/soc/codecs/wcd9335.c ++++ b/sound/soc/codecs/wcd9335.c +@@ -5010,9 +5010,9 @@ static int wcd9335_power_on_reset(struct wcd9335_codec *wcd) + */ + usleep_range(600, 650); + +- gpiod_set_value(wcd->reset_gpio, 1); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 1); + msleep(20); +- gpiod_set_value(wcd->reset_gpio, 0); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 0); + msleep(20); + + return 0; +diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c +--- a/sound/soc/codecs/wcd934x.c ++++ b/sound/soc/codecs/wcd934x.c +@@ -1265,13 +1265,10 @@ static int wcd934x_set_sido_input_src(struct wcd934x_codec *wcd, int sido_src) + + static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- +- if (++wcd->sysclk_users != 1) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (++wcd->sysclk_users != 1) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_ANA_BIAS, + WCD934X_ANA_BIAS_EN_MASK, +@@ -1328,12 +1325,10 @@ static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + + static int wcd934x_disable_ana_bias_and_syclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- if (--wcd->sysclk_users != 0) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (--wcd->sysclk_users != 0) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_CLK_SYS_MCLK_PRG, + WCD934X_EXT_CLK_BUF_EN_MASK | +@@ -2384,7 +2379,7 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + __func__, micb_num); + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + + switch (req) { + case MICB_PULLUP_ENABLE: +@@ -2446,8 +2441,6 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&wcd934x->micb_lock); +- + return 0; + } + +@@ -2488,7 +2481,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd934x_codec *wcd934x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -2506,7 +2499,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2521,15 +2514,11 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD934X_MICB_VAL_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD934X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2550,9 +2539,8 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd934x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd934x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +@@ -5899,10 +5887,8 @@ static int wcd934x_codec_probe(struct platform_device *pdev) + } + + static const struct platform_device_id wcd934x_driver_id[] = { +- { +- .name = "wcd934x-codec", +- }, +- {}, ++ { .name = "wcd934x-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, wcd934x_driver_id); + +diff --git a/sound/soc/codecs/wcd937x-sdw.c b/sound/soc/codecs/wcd937x-sdw.c +--- a/sound/soc/codecs/wcd937x-sdw.c ++++ b/sound/soc/codecs/wcd937x-sdw.c +@@ -1084,10 +1084,16 @@ static int wcd937x_sdw_runtime_suspend(struct device *dev) + static int wcd937x_sdw_runtime_resume(struct device *dev) + { + struct wcd937x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c +--- a/sound/soc/codecs/wcd937x.c ++++ b/sound/soc/codecs/wcd937x.c +@@ -2,6 +2,7 @@ + // Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. + + #include ++#include + #include + #include + #include +@@ -1056,7 +1057,7 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + return -EINVAL; + } + +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + switch (req) { + case MICB_PULLUP_ENABLE: + wcd937x->pullup_ref[micb_index]++; +@@ -1136,7 +1137,6 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + } + break; + } +- mutex_unlock(&wcd937x->micb_lock); + + return 0; + } +@@ -1460,7 +1460,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd937x_priv *wcd937x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1475,7 +1475,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -1490,15 +1490,11 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD937X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD937X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -1519,9 +1515,8 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd937x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd937x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd938x-sdw.c b/sound/soc/codecs/wcd938x-sdw.c +--- a/sound/soc/codecs/wcd938x-sdw.c ++++ b/sound/soc/codecs/wcd938x-sdw.c +@@ -1245,10 +1245,16 @@ static int wcd938x_sdw_runtime_suspend(struct device *dev) + static int wcd938x_sdw_runtime_resume(struct device *dev) + { + struct wcd938x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + pm_runtime_mark_last_busy(dev); +diff --git a/sound/soc/codecs/wcd938x.c b/sound/soc/codecs/wcd938x.c +--- a/sound/soc/codecs/wcd938x.c ++++ b/sound/soc/codecs/wcd938x.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -1976,7 +1977,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd938x_priv *wcd938x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1994,7 +1995,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd938x->micb_lock); ++ guard(mutex)(&wcd938x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2009,15 +2010,11 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD938X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD938X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2038,9 +2035,8 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd938x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd938x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd939x-sdw.c b/sound/soc/codecs/wcd939x-sdw.c +--- a/sound/soc/codecs/wcd939x-sdw.c ++++ b/sound/soc/codecs/wcd939x-sdw.c +@@ -1431,10 +1431,16 @@ static int wcd939x_sdw_runtime_suspend(struct device *dev) + static int wcd939x_sdw_runtime_resume(struct device *dev) + { + struct wcd939x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd939x.c b/sound/soc/codecs/wcd939x.c +--- a/sound/soc/codecs/wcd939x.c ++++ b/sound/soc/codecs/wcd939x.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1923,7 +1924,6 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(component); + unsigned int micb_reg, cur_vout_ctl, micb_en; + int req_vout_ctl; +- int ret = 0; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1941,7 +1941,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd939x->micb_lock); ++ guard(mutex)(&wcd939x->micb_lock); + + /* + * If requested micbias voltage is same as current micbias +@@ -1957,15 +1957,11 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD939X_MICB_VOUT_CTL); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = req_vout_ctl; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return req_vout_ctl; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + dev_dbg(component->dev, "%s: micb_num: %d, cur_mv: %d, req_mv: %d, micb_en: %d\n", + __func__, micb_num, WCD_VOUT_CTL_TO_MICB(cur_vout_ctl), +@@ -1990,9 +1986,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + usleep_range(2000, 2100); + } + +-exit: +- mutex_unlock(&wcd939x->micb_lock); +- return ret; ++ return 0; + } + + static int wcd939x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c +--- a/sound/soc/codecs/wm0010.c ++++ b/sound/soc/codecs/wm0010.c +@@ -9,6 +9,7 @@ + * Scott Ling + */ + ++#include + #include + #include + #include +@@ -148,13 +149,11 @@ static const char *wm0010_state_to_str(enum wm0010_state state) + static void wm0010_halt(struct snd_soc_component *component) + { + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + enum wm0010_state state; + + /* Fetch the wm0010 state */ +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + switch (state) { + case WM0010_POWER_OFF: +@@ -173,9 +172,8 @@ static void wm0010_halt(struct snd_soc_component *component) + break; + } + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- wm0010->state = WM0010_POWER_OFF; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ wm0010->state = WM0010_POWER_OFF; + } + + struct wm0010_boot_xfer { +@@ -190,11 +188,9 @@ struct wm0010_boot_xfer { + static void wm0010_mark_boot_failure(struct wm0010_priv *wm0010) + { + enum wm0010_state state; +- unsigned long flags; + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + dev_err(wm0010->dev, "Failed to transition from `%s' state to `%s' state\n", + wm0010_state_to_str(state), wm0010_state_to_str(state + 1)); +@@ -337,7 +333,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + struct wm0010_boot_xfer *xfer; + int ret; + DECLARE_COMPLETION_ONSTACK(done); +- const struct firmware *fw; + const struct dfw_binrec *rec; + const struct dfw_inforec *inforec; + u64 *img; +@@ -346,6 +341,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + INIT_LIST_HEAD(&xfer_list); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, name, component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request application(%s): %d\n", +@@ -364,16 +360,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* First record should be INFO */ + if (rec->command != DFW_CMD_INFO) { + dev_err(component->dev, "First record not INFO\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + if (inforec->info_version != INFO_VERSION) { + dev_err(component->dev, + "Unsupported version (%02d) of INFO record\r\n", + inforec->info_version); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + dev_dbg(component->dev, "Version v%02d INFO record found\r\n", +@@ -382,8 +376,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* Check it's a DSP file */ + if (dsp != DEVICE_ID_WM0010) { + dev_err(component->dev, "Not a WM0010 firmware file.\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + /* Skip the info record as we don't need to send it */ +@@ -408,14 +401,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + out = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!out) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.rx_buf = out; + + img = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.tx_buf = img; + +@@ -453,13 +446,13 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + ret = spi_async(spi, &xfer->m); + if (ret != 0) { + dev_err(component->dev, "Write failed: %d\n", ret); +- goto abort1; ++ goto abort; + } + + if (wm0010->boot_failed) { + dev_dbg(component->dev, "Boot fail!\n"); + ret = -EINVAL; +- goto abort1; ++ goto abort; + } + } + +@@ -467,7 +460,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + ret = 0; + +-abort1: ++abort: + while (!list_empty(&xfer_list)) { + xfer = list_first_entry(&xfer_list, struct wm0010_boot_xfer, + list); +@@ -477,8 +470,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + kfree(xfer); + } + +-abort: +- release_firmware(fw); + return ret; + } + +@@ -486,14 +477,12 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + { + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + struct spi_message m; + struct spi_transfer t; +- u32 *img; +- u8 *out; + int i; + int ret = 0; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, "wm0010_stage2.bin", component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request stage2 loader: %d\n", +@@ -504,17 +493,15 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_dbg(component->dev, "Downloading %zu byte stage 2 loader\n", fw->size); + + /* Copy to local buffer first as vmalloc causes problems for dma */ +- img = kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); +- if (!img) { +- ret = -ENOMEM; +- goto abort2; +- } ++ u32 *img __free(kfree) = ++ kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); ++ if (!img) ++ return -ENOMEM; + +- out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); +- if (!out) { +- ret = -ENOMEM; +- goto abort1; +- } ++ u8 *out __free(kfree) = ++ kzalloc(fw->size, GFP_KERNEL | GFP_DMA); ++ if (!out) ++ return -ENOMEM; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); +@@ -531,7 +518,7 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + ret = spi_sync(spi, &m); + if (ret != 0) { + dev_err(component->dev, "Initial download failed: %d\n", ret); +- goto abort; ++ return ret; + } + + /* Look for errors from the boot ROM */ +@@ -540,18 +527,11 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_err(component->dev, "Boot ROM error: %x in %d\n", + out[i], i); + wm0010_mark_boot_failure(wm0010); +- ret = -EBUSY; +- goto abort; ++ return -EBUSY; + } + } +-abort: +- kfree(out); +-abort1: +- kfree(img); +-abort2: +- release_firmware(fw); + +- return ret; ++ return 0; + } + + static int wm0010_boot(struct snd_soc_component *component) +@@ -734,9 +714,8 @@ static int wm0010_set_bias_level(struct snd_soc_component *component, + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_PREPARE) { +- mutex_lock(&wm0010->lock); +- wm0010_halt(component); +- mutex_unlock(&wm0010->lock); ++ scoped_guard(mutex, &wm0010->lock) ++ wm0010_halt(component); + } + break; + case SND_SOC_BIAS_OFF: +@@ -832,9 +811,8 @@ static irqreturn_t wm0010_irq(int irq, void *data) + case WM0010_OUT_OF_RESET: + case WM0010_BOOTROM: + case WM0010_STAGE2: +- spin_lock(&wm0010->irq_lock); +- complete(&wm0010->boot_completion); +- spin_unlock(&wm0010->irq_lock); ++ scoped_guard(spinlock, &wm0010->irq_lock) ++ complete(&wm0010->boot_completion); + return IRQ_HANDLED; + default: + return IRQ_NONE; +diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c +--- a/sound/soc/codecs/wm2000.c ++++ b/sound/soc/codecs/wm2000.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -612,20 +613,15 @@ static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int anc_active = ucontrol->value.integer.value[0]; +- int ret; + + if (anc_active > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->anc_active = anc_active; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static int wm2000_speaker_get(struct snd_kcontrol *kcontrol, +@@ -645,20 +641,15 @@ static int wm2000_speaker_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int val = ucontrol->value.integer.value[0]; +- int ret; + + if (val > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->spk_ena = val; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_kcontrol_new wm2000_controls[] = { +@@ -676,9 +667,8 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); +- int ret; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + wm2000->anc_eng_ena = 1; +@@ -686,11 +676,7 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + if (SND_SOC_DAPM_EVENT_OFF(event)) + wm2000->anc_eng_ena = 0; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = { +@@ -810,7 +796,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + struct wm2000_priv *wm2000; + struct wm2000_platform_data *pdata; + const char *filename; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + int ret, i; + unsigned int reg; + u16 id; +@@ -828,7 +814,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + ret = PTR_ERR(wm2000->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); +- goto out; ++ return ret; + } + + for (i = 0; i < WM2000_NUM_SUPPLIES; i++) +@@ -922,9 +908,6 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + + err_supplies: + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); +- +-out: +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c +--- a/sound/soc/codecs/wm2200.c ++++ b/sound/soc/codecs/wm2200.c +@@ -2461,7 +2461,15 @@ static int wm2200_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm2200->regmap, false); +- regcache_sync(wm2200->regmap); ++ ret = regcache_sync(wm2200->regmap); ++ if (ret) { ++ regcache_cache_only(wm2200->regmap, true); ++ regcache_mark_dirty(wm2200->regmap); ++ gpiod_set_value_cansleep(wm2200->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), ++ wm2200->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c +--- a/sound/soc/codecs/wm5100.c ++++ b/sound/soc/codecs/wm5100.c +@@ -2659,7 +2659,15 @@ static int wm5100_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm5100->regmap, false); +- regcache_sync(wm5100->regmap); ++ ret = regcache_sync(wm5100->regmap); ++ if (ret) { ++ regcache_cache_only(wm5100->regmap, true); ++ regcache_mark_dirty(wm5100->regmap); ++ gpiod_set_value_cansleep(wm5100->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm5100->core_supplies), ++ wm5100->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c +--- a/sound/soc/codecs/wm5102.c ++++ b/sound/soc/codecs/wm5102.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -667,10 +668,9 @@ static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + put_unaligned_be16(arizona->dac_comp_coeff, + ucontrol->value.bytes.data); +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -681,16 +681,14 @@ static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + uint16_t dac_comp_coeff = get_unaligned_be16(ucontrol->value.bytes.data); +- int ret = 0; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_coeff != dac_comp_coeff) { + arizona->dac_comp_coeff = dac_comp_coeff; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, +@@ -699,9 +697,8 @@ static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + ucontrol->value.integer.value[0] = arizona->dac_comp_enabled; +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -712,19 +709,17 @@ static int wm5102_out_comp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; +- int ret = 0; + + if (ucontrol->value.integer.value[0] > mc->max) + return -EINVAL; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_enabled != ucontrol->value.integer.value[0]) { + arizona->dac_comp_enabled = ucontrol->value.integer.value[0]; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static const char * const wm5102_osr_text[] = { +diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c +--- a/sound/soc/codecs/wm8731.c ++++ b/sound/soc/codecs/wm8731.c +@@ -10,6 +10,7 @@ + * Based on wm8753.c by Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -110,22 +111,20 @@ static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8731->lock); ++ guard(mutex)(&wm8731->lock); + if (wm8731->deemph != deemph) { + wm8731->deemph = deemph; + + wm8731_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8731->lock); + +- return ret; ++ return 0; + } + + static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0); +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -458,22 +459,20 @@ static int wm8903_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8903->lock); ++ guard(mutex)(&wm8903->lock); + if (wm8903->deemph != deemph) { + wm8903->deemph = deemph; + + wm8903_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8903->lock); + +- return ret; ++ return 0; + } + + /* ALSA can only do steps of .01dB */ +diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c +--- a/sound/soc/codecs/wm8940.c ++++ b/sound/soc/codecs/wm8940.c +@@ -333,7 +333,7 @@ static const struct snd_soc_dapm_route wm8940_dapm_routes[] = { + {"ADC", NULL, "Boost Mixer"}, + }; + +-#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0); ++#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0) + + static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c +--- a/sound/soc/codecs/wm8958-dsp2.c ++++ b/sound/soc/codecs/wm8958-dsp2.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -864,9 +865,8 @@ static void wm8958_enh_eq_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "ENH_EQ", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->enh_eq = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -876,9 +876,8 @@ static void wm8958_mbc_vss_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC+VSS", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc_vss = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -888,9 +887,8 @@ static void wm8958_mbc_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c +--- a/sound/soc/codecs/wm8962.c ++++ b/sound/soc/codecs/wm8962.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1564,11 +1565,10 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int old = wm8962->dsp2_ena; +- int ret = 0; + int dsp2_running = snd_soc_component_read(component, WM8962_DSP2_POWER_MANAGEMENT) & + WM8962_DSP2_ENA; + +- mutex_lock(&wm8962->dsp2_ena_lock); ++ guard(mutex)(&wm8962->dsp2_ena_lock); + + if (ucontrol->value.integer.value[0]) + wm8962->dsp2_ena |= 1 << shift; +@@ -1576,9 +1576,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962->dsp2_ena &= ~(1 << shift); + + if (wm8962->dsp2_ena == old) +- goto out; +- +- ret = 1; ++ return 0; + + if (dsp2_running) { + if (wm8962->dsp2_ena) +@@ -1587,10 +1585,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962_dsp2_stop(component); + } + +-out: +- mutex_unlock(&wm8962->dsp2_ena_lock); +- +- return ret; ++ return 1; + } + + /* The VU bits for the headphones are in a different register to the mute +@@ -3937,7 +3932,9 @@ static int wm8962_runtime_resume(struct device *dev) + WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA, + 0); + +- regcache_sync(wm8962->regmap); ++ ret = regcache_sync(wm8962->regmap); ++ if (ret) ++ goto cache_sync_err; + + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, + WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, +@@ -3955,6 +3952,11 @@ static int wm8962_runtime_resume(struct device *dev) + + return 0; + ++cache_sync_err: ++ regcache_cache_only(wm8962->regmap, true); ++ regcache_mark_dirty(wm8962->regmap); ++ regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), ++ wm8962->supplies); + disable_clock: + clk_disable_unprepare(wm8962->pdata.mclk); + return ret; +diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c +--- a/sound/soc/codecs/wm8994.c ++++ b/sound/soc/codecs/wm8994.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -766,7 +767,7 @@ static void active_reference(struct snd_soc_component *component) + { + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount++; + +@@ -775,8 +776,6 @@ static void active_reference(struct snd_soc_component *component) + + /* If we're using jack detection go into audio mode */ + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_AUDIO); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void active_dereference(struct snd_soc_component *component) +@@ -784,7 +783,7 @@ static void active_dereference(struct snd_soc_component *component) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + u16 mode; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount--; + +@@ -800,8 +799,6 @@ static void active_dereference(struct snd_soc_component *component) + + wm1811_jackdet_set_mode(component, mode); + } +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static int clk_sys_event(struct snd_soc_dapm_widget *w, +@@ -3704,7 +3701,7 @@ static void wm8958_open_circuit_work(struct work_struct *work) + open_circuit_work.work); + struct device *dev = wm8994->wm8994->dev; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm1811_micd_stop(wm8994->hubs.component); + +@@ -3718,8 +3715,6 @@ static void wm8958_open_circuit_work(struct work_struct *work) + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void wm8958_mic_id(void *data, u16 status) +@@ -3777,7 +3772,7 @@ static void wm1811_mic_work(struct work_struct *work) + struct snd_soc_component *component = wm8994->hubs.component; + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + +- pm_runtime_get_sync(component->dev); ++ guard(pm_runtime_active)(component->dev); + + /* If required for an external cap force MICBIAS on */ + if (control->pdata.jd_ext_cap) { +@@ -3785,7 +3780,7 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_dapm_sync(dapm); + } + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + dev_dbg(component->dev, "Starting mic detection\n"); + +@@ -3803,10 +3798,6 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, WM8958_MICD_ENA); + } +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm1811_jackdet_irq(int irq, void *data) +@@ -4026,15 +4017,10 @@ static void wm8958_mic_work(struct work_struct *work) + mic_complete_work.work); + struct snd_soc_component *component = wm8994->hubs.component; + +- pm_runtime_get_sync(component->dev); +- +- mutex_lock(&wm8994->accdet_lock); ++ guard(pm_runtime_active)(component->dev); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->mic_id_cb(wm8994->mic_id_cb_data, wm8994->mic_status); +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm8958_mic_irq(int irq, void *data) +diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c +--- a/sound/soc/codecs/wm9712.c ++++ b/sound/soc/codecs/wm9712.c +@@ -6,6 +6,7 @@ + * Author: Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -229,7 +230,7 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = 1 << shift; + +- mutex_lock(&wm9712->lock); ++ guard(mutex)(&wm9712->lock); + old = wm9712->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9712->hp_mixer[mixer] |= mask; +@@ -251,8 +252,6 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9712->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c +--- a/sound/soc/codecs/wm9713.c ++++ b/sound/soc/codecs/wm9713.c +@@ -11,6 +11,7 @@ + * o Support for DAPM + */ + ++#include + #include + #include + #include +@@ -238,7 +239,7 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = (1 << shift); + +- mutex_lock(&wm9713->lock); ++ guard(mutex)(&wm9713->lock); + old = wm9713->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9713->hp_mixer[mixer] |= mask; +@@ -260,8 +261,6 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9713->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -348,7 +348,6 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); +- int ret = 1; + + if (ucontrol->value.enumerated.item[0] == dsp[e->shift_l].fw) + return 0; +@@ -356,16 +355,14 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + if (ucontrol->value.enumerated.item[0] >= WM_ADSP_NUM_FW) + return -EINVAL; + +- mutex_lock(&dsp[e->shift_l].cs_dsp.pwr_lock); ++ guard(mutex)(&dsp[e->shift_l].cs_dsp.pwr_lock); + + if (dsp[e->shift_l].cs_dsp.booted || !list_empty(&dsp[e->shift_l].compr_list)) +- ret = -EBUSY; ++ return -EBUSY; + else + dsp[e->shift_l].fw = ucontrol->value.enumerated.item[0]; + +- mutex_unlock(&dsp[e->shift_l].cs_dsp.pwr_lock); +- +- return ret; ++ return 1; + } + EXPORT_SYMBOL_GPL(wm_adsp_fw_put); + +@@ -450,15 +447,13 @@ static int wm_coeff_put_acked(struct snd_kcontrol *kctl, + if (val == 0) + return 0; /* 0 means no event */ + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + if (cs_ctl->enabled) + ret = cs_dsp_coeff_write_acked_control(cs_ctl, val); + else + ret = -EPERM; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + if (ret < 0) + return ret; + +@@ -486,15 +481,13 @@ static int wm_coeff_tlv_get(struct snd_kcontrol *kctl, + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + int ret = 0; + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, cs_ctl->cache, size); + + if (!ret && copy_to_user(bytes, cs_ctl->cache, size)) + ret = -EFAULT; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + return ret; + } + +@@ -694,10 +687,9 @@ int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, + struct cs_dsp_coeff_ctl *cs_ctl; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); + + if (ret < 0) + return ret; +@@ -709,14 +701,10 @@ EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); + int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) + { +- int ret; ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); +- ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), ++ return cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), + 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); + +@@ -1270,38 +1258,32 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + { + struct wm_adsp_compr *compr, *tmp; + struct snd_soc_pcm_runtime *rtd = stream->private_data; +- int ret = 0; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (wm_adsp_fw[dsp->fw].num_caps == 0) { + adsp_err(dsp, "%s: Firmware does not support compressed API\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -ENXIO; +- goto out; ++ return -ENXIO; + } + + if (wm_adsp_fw[dsp->fw].compr_direction != stream->direction) { + adsp_err(dsp, "%s: Firmware does not support stream direction\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + list_for_each_entry(tmp, &dsp->compr_list, list) { + if (!strcmp(tmp->name, snd_soc_rtd_to_codec(rtd, 0)->name)) { + adsp_err(dsp, "%s: Only a single stream supported per dai\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + } + + compr = kzalloc_obj(*compr); +- if (!compr) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!compr) ++ return -ENOMEM; + + compr->dsp = dsp; + compr->stream = stream; +@@ -1311,10 +1293,7 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + + stream->runtime->private_data = compr; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_open); + +@@ -1324,7 +1303,7 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + wm_adsp_compr_detach(compr); + list_del(&compr->list); +@@ -1332,8 +1311,6 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + kfree(compr->raw_buf); + kfree(compr); + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_free); +@@ -1741,7 +1718,7 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + + compr_dbg(compr, "Trigger: %d\n", cmd); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -1777,8 +1754,6 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_trigger); +@@ -1907,21 +1882,20 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + + compr_dbg(compr, "Pointer request\n"); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + buf = compr->buf; + + if (dsp->fatal_error || !buf || buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); +- ret = -EIO; +- goto out; ++ return -EIO; + } + + if (buf->avail < wm_adsp_compr_frag_words(compr)) { + ret = wm_adsp_buffer_update_avail(buf); + if (ret < 0) { + compr_err(compr, "Error reading avail: %d\n", ret); +- goto out; ++ return ret; + } + + /* +@@ -1934,14 +1908,14 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + if (buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); +- goto out; ++ return ret; + } + + ret = wm_adsp_buffer_reenable_irq(buf); + if (ret < 0) { + compr_err(compr, "Failed to re-enable buffer IRQ: %d\n", + ret); +- goto out; ++ return ret; + } + } + } +@@ -1950,9 +1924,6 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + tstamp->copied_total += buf->avail * CS_DSP_DATA_WORD_SIZE; + tstamp->sampling_rate = compr->sample_rate; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_pointer); +@@ -2063,15 +2034,13 @@ int wm_adsp_compr_copy(struct snd_soc_component *component, + struct wm_adsp *dsp = compr->dsp; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (stream->direction == SND_COMPRESS_CAPTURE) + ret = wm_adsp_compr_read(compr, buf, count); + else + ret = -ENOTSUPP; + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); +diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c +--- a/sound/soc/codecs/wsa881x.c ++++ b/sound/soc/codecs/wsa881x.c +@@ -1178,7 +1178,13 @@ static int wsa881x_runtime_resume(struct device *dev) + } + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ gpiod_direction_output(wsa881x->sd_n, 1); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa883x.c b/sound/soc/codecs/wsa883x.c +--- a/sound/soc/codecs/wsa883x.c ++++ b/sound/soc/codecs/wsa883x.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -1237,9 +1238,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = true; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = true; + + switch (wsa883x->dev_mode) { + case RECEIVER: +@@ -1290,9 +1290,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + WSA883X_GLOBAL_PA_EN_MASK, 0); + snd_soc_component_write_field(component, WSA883X_PDM_WD_CTL, + WSA883X_PDM_EN_MASK, 0); +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = false; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = false; + break; + } + return 0; +@@ -1696,9 +1695,15 @@ static int wsa883x_runtime_suspend(struct device *dev) + static int wsa883x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c +--- a/sound/soc/codecs/wsa884x.c ++++ b/sound/soc/codecs/wsa884x.c +@@ -1701,9 +1701,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = true; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = true; + + wsa884x_spkr_post_pmu(component, wsa884x); + +@@ -1717,9 +1716,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK, + 0x0); + +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = false; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = false; + break; + } + +@@ -2147,9 +2145,15 @@ static int wsa884x_runtime_suspend(struct device *dev) + static int wsa884x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/wsa885x.c +@@ -0,0 +1,1533 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. ++ */ ++ ++/* WSA885X codec driver */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Control Registers - Audio Processing */ ++#define WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S 0x0000 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK 0x0004 ++#define WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX 0x0140 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID 0x0208 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX 0x0240 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER 0x0340 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0 0x4405 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1 0x4406 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB 0x4409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB 0x6409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB 0x440a ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB 0x640a ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS 0x0a04 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS 0x0a40 ++#define WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE 0x0b10 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX 0x0e40 ++ ++/* Analog Top Registers - Power and Clock Control */ ++#define WSA885X_ANA_TOP_PON_CKSK_CTL_0 0x800d ++#define WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG 0x8024 ++#define WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG 0x8025 ++#define WSA885X_ANA_TOP_BG_TVP_OVRD_CTL 0x8034 ++ ++/* Analog PLL Registers */ ++#define WSA885X_ANA_PLL_DIV_CTL_0 0x8090 ++#define WSA885X_ANA_PLL_DIV_CTL_1 0x8091 ++#define WSA885X_ANA_TOP_PLL_VCO_CTL 0x8092 ++#define WSA885X_ANA_TOP_PLL_LOOPFILT_0 0x8093 ++#define WSA885X_ANA_TOP_PLL_OVRD_CTL 0x8098 ++#define WSA885X_ANA_TOP_PLL_STATUS_0 0x809a ++#define WSA885X_ANA_TOP_PLL_STATUS_1 0x809b ++ ++/* Analog Boost Control Registers */ ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL2 0x805b ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL3 0x805c ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL2 0x805e ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL3 0x805f ++#define WSA885X_ANA_TOP_BOOST_MISC 0x8063 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2 0x8065 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4 0x8067 ++ ++/* Analog IV Sense ADC Registers */ ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2 0x80ca ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3 0x80cb ++#define WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL 0x80cc ++#define WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2 0x80d0 ++ ++/* Analog Speaker Power Stage Registers */ ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3 0x8108 ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3 0x810b ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3 0x810e ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3 0x8111 ++#define WSA885X_ANA_TOP_SPK_TOP_SPARE3 0x813c ++#define WSA885X_SPK_TOP_LF_CH1_CTRL11 0x811c ++#define WSA885X_SPK_TOP_LF_CH1_TUNE1 0x811d ++#define WSA885X_SPK_TOP_LF_CH2_TUNE1 0x8129 ++#define WSA885X_SPK_TOP_LF_CH1_CTRL9 0x811a ++#define WSA885X_SPK_TOP_LF_CH2_CTRL9 0x8126 ++#define WSA885X_SPK_TOP_LF_CH2_CTRL11 0x8128 ++#define WSA885X_SPK_TOP_COMMON_CTRL2 0x8102 ++#define WSA885X_SPK_TOP_COMMON_TUNE1 0x8103 ++#define WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1 0x80ba ++#define WSA885X_DIG_CTRL0_TOP_CLK_CFG 0x8418 ++#define WSA885X_DIG_CTRL0_SDCA_COMMIT 0x8419 ++#define WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE 0x841a ++#define WSA885X_DIG_CTRL0_SYS_CLK_SEL 0x841b ++#define WSA885X_DIG_CTRL0_CDC_CLK_CTL 0x841c ++#define WSA885X_DIG_CTRL0_PA_FSM_CTL 0x8420 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL0 0x8423 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL1 0x8424 ++#define WSA885X_DIG_CTRL0_PA0_FSM_CTL1 0x842b ++#define WSA885X_DIG_CTRL0_PA1_FSM_CTL1 0x8435 ++#define WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL 0x8458 ++#define WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL 0x8470 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1 0x84b4 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1 0x84b7 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1 0x84A0 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1 0x84A4 ++ ++/* Digital Control 1 Registers - I2S/TDM Interface */ ++#define WSA885X_DIG_CTRL1_I2S_CTL0 0x85A0 ++#define WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX 0x85A2 ++#define WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX 0x85A3 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL0 0x85A7 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL1 0x85A9 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_RX 0x85AA ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_TX 0x85AB ++#define WSA885X_DIG_CTRL1_I2S_RESET_CTL 0x85AE ++ ++/* CDC RX Path Registers - Audio Data Path */ ++#define WSA885X_CDC_RX0_RX_PATH_CFG0 0x8601 ++#define WSA885X_CDC_RX0_RX_PATH_CFG1 0x8602 ++#define WSA885X_CDC_RX0_RX_PATH_CTL 0x8606 ++#define WSA885X_RX0_RX_PATH_DSMDEM_CTL 0x8613 ++#define WSA885X_CDC_RX1_RX_PATH_CFG0 0x8621 ++#define WSA885X_CDC_RX1_RX_PATH_CFG1 0x8622 ++#define WSA885X_CDC_RX1_RX_PATH_CTL 0x8626 ++#define WSA885X_RX1_RX_PATH_DSMDEM_CTL 0x8633 ++ ++/* CDC Compander Registers - Dynamic Range Control */ ++#define WSA885X_CDC_COMPANDER0_CTL0 0x8640 ++#define WSA885X_CDC_COMPANDER0_CTL7 0x8647 ++#define WSA885X_CDC_COMPANDER1_CTL0 0x8660 ++#define WSA885X_CDC_COMPANDER1_CTL7 0x8667 ++ ++/* CDC Speaker Protection Registers - IV Sense */ ++#define WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL 0x86A1 ++#define WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL 0x86B1 ++#define WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL 0x86A9 ++#define WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL 0x86B9 ++ ++/* CDC Class-H Registers - Headroom Control */ ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL1 0x86CD ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL0 0x86CC ++#define WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0 0x86C7 ++#define WSA885X_CDC_CLSH_CLSH_V_HD_PA 0x86C3 ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL2 0x86CE ++ ++/* Driver Constants */ ++#define WSA885X_CLK_RATE_FIXED 73728000 ++#define WSA885X_NUM_REGS 0x03 ++ ++/* Interrupt Registers */ ++#define WSA885X_INTR_STATUS0 0x8584 ++#define WSA885X_INTR_MASK0 0x8581 ++#define WSA885X_INTR_CLEAR0 0x8587 ++ ++/* Power and PA FSM Control Registers */ ++#define WSA885X_PA0_FSM_CTL0 0x842A ++#define WSA885X_PA1_FSM_CTL0 0x8434 ++ ++/* Digital Control GPIO and Interrupt Registers */ ++#define WSA885X_DIG_CTRL1_PIN_CT 0x8510 ++#define WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL 0x8518 ++#define WSA885X_DIG_CTRL1_INTR_MODE 0x8580 ++ ++#define WSA885X_I2S_CTL0_PCM_RATE_MASK GENMASK(4, 1) ++#define WSA885X_I2S_CTL0_ENABLE_MASK BIT(0) ++#define WSA885X_I2S_CTL0_PCM_RATE(v) \ ++ FIELD_PREP(WSA885X_I2S_CTL0_PCM_RATE_MASK, (v)) ++#define WSA885X_I2S_CTL0_PCM_RATE_8KHZ 0x0 ++#define WSA885X_I2S_CTL0_PCM_RATE_16KHZ 0x1 ++#define WSA885X_I2S_CTL0_PCM_RATE_32KHZ 0x2 ++#define WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ 0x3 ++#define WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ 0x4 ++#define WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ 0x5 ++#define WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ 0x6 ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, (v)) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, (v)) ++#define WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK BIT(0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK GENMASK(3, 2) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 1) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 3) ++#define WSA885X_I2S_TDM_CH_TX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_TX_CH1_EN BIT(1) ++#define WSA885X_I2S_TDM_CH_TX_CH2_EN BIT(2) ++#define WSA885X_I2S_TDM_CH_TX_CH3_EN BIT(3) ++#define WSA885X_I2S_TDM_CH_RX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_RX_CH3_EN BIT(3) ++#define WSA885X_I2S_RESET_CTL_RESET_MASK BIT(0) ++#define WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK BIT(2) ++#define WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK BIT(3) ++#define WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK BIT(2) ++ ++#define WSA885X_I2S_TX_SLOT_ISENSE0 0x1 ++#define WSA885X_I2S_TX_SLOT_ISENSE1 0x2 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE0 0x5 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE1 0x6 ++ ++/* RX Sample Rate Index Values - Audio Playback Path */ ++#define WSA885X_RX_RATE_8000HZ 0x00 ++#define WSA885X_RX_RATE_16000HZ 0x01 ++#define WSA885X_RX_RATE_32000HZ 0x02 ++#define WSA885X_RX_RATE_44100HZ 0x03 ++#define WSA885X_RX_RATE_48000HZ 0x04 ++#define WSA885X_RX_RATE_96000HZ 0x05 ++#define WSA885X_RX_RATE_192000HZ 0x06 ++#define WSA885X_RX_RATE_384000HZ 0x07 ++ ++/* VI Sample Rate Index Values - Voltage/Current Sensing Path */ ++#define WSA885X_VI_RATE_8000HZ 0x00 ++#define WSA885X_VI_RATE_16000HZ 0x01 ++#define WSA885X_VI_RATE_44100HZ 0x02 ++#define WSA885X_VI_RATE_48000HZ 0x03 ++#define WSA885X_VI_RATE_96000HZ 0x04 ++#define WSA885X_VI_RATE_22050HZ 0x05 ++#define WSA885X_VI_RATE_24000HZ 0x06 ++#define WSA885X_VI_RATE_192000HZ 0x07 ++#define WSA885X_VI_RATE_384000HZ 0x08 ++ ++/* Channel Configuration Masks */ ++#define WSA885X_CHANNEL_STEREO 0x03 ++#define WSA885X_CHANNEL_MONO_LEFT 0x01 ++#define WSA885X_CHANNEL_MONO_RIGHT 0x02 ++ ++#define WSA885X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ ++ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 | \ ++ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) ++ ++#define WSA885X_PLL_LOCK_BIT BIT(0) ++ ++#define WSA885X_FU21_VOL_STEPS 124 ++#define WSA885X_USAGE_MODE_MAX 8 ++static const DECLARE_TLV_DB_SCALE(wsa885x_fu21_digital_gain, -8400, 100, 0); ++ ++static bool wsa885x_is_valid_rx_slot_mask(u32 mask) ++{ ++ return mask == WSA885X_CHANNEL_MONO_LEFT || ++ mask == WSA885X_CHANNEL_MONO_RIGHT || ++ mask == WSA885X_CHANNEL_STEREO; ++} ++ ++static const char *const wsa885x_supply_name[] = { ++ "vdd-1p8", ++ "vdd-io", ++}; ++ ++enum { ++ WSA885X_BATT_1S = 1, ++ WSA885X_BATT_2S, ++}; ++ ++enum { ++ WSA885X_IRQ_INT_SAF2WAR = 0, ++ WSA885X_IRQ_INT_WAR2SAF, ++ WSA885X_IRQ_INT_DISABLE, ++ WSA885X_IRQ_INT_PA0_OCP, ++ WSA885X_IRQ_INT_PA1_OCP, ++ WSA885X_IRQ_INT_CLIP0, ++ WSA885X_IRQ_INT_CLIP1, ++ WSA885X_IRQ_INT_CLK_WD, ++ WSA885X_IRQ_INT_INTR_GPIO1_PIN, ++ WSA885X_IRQ_INT_INTR_GPIO2_PIN, ++ WSA885X_IRQ_INT_UVLO, ++ WSA885X_IRQ_INT_BOP, ++ WSA885X_IRQ_INT_PA0_FSM_ERR, ++ WSA885X_IRQ_INT_PA1_FSM_ERR, ++ WSA885X_IRQ_INT_MAIN_FSM_ERR, ++ WSA885X_IRQ_INT_PCM_DATA0_WD, ++ WSA885X_IRQ_INT_PCM_DATA1_WD, ++ WSA885X_IRQ_INT_PCM_DATA0_DC, ++ WSA885X_IRQ_INT_PCM_DATA1_DC, ++ WSA885X_IRQ_INT_PLL_UNLOCKED, ++ WSA885X_IRQ_INT_PROT_MODE_CHANGE, ++ WSA885X_IRQ_INT_PB_CLOCK_VALID, ++ WSA885X_IRQ_INT_SENSE_CLOCK_VALID, ++ WSA885X_IRQ_MAX, ++}; ++ ++struct wsa885x_priv { ++ struct i2c_client *client; ++ struct regmap *regmap; ++ struct device *dev; ++ struct snd_soc_component *component; ++ struct gpio_desc *sd_n; ++ struct reset_control *sd_reset; ++ u32 usage_mode; ++ u32 rx_slot_mask; ++ u32 batt_conf; ++ int stereo_vol_db; ++ struct mutex state_lock; /* protects mutable control state */ ++}; ++ ++struct wsa885x_reg_update { ++ unsigned int reg; ++ unsigned int mask; ++ unsigned int val; ++}; ++ ++static const struct regmap_range_cfg wsa885x_regmap_ranges[] = { ++ { ++ .range_min = 0, ++ .range_max = 0x88ff, ++ .selector_reg = 0x0, ++ .selector_mask = 0xFF, ++ .selector_shift = 0, ++ .window_start = 0, ++ .window_len = 0x100, ++ }, ++}; ++ ++static const struct reg_default wsa885x_codec_reg_defaults[] = { ++ {WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, 0x04}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, 0x03}, ++ {WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x19}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x22}, ++ {WSA885X_ANA_PLL_DIV_CTL_0, 0x0c}, ++ {WSA885X_ANA_PLL_DIV_CTL_1, 0x50}, ++ {WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00}, ++ {WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xb4}, ++ {WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x03}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x3c}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc5}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x13}, ++ {WSA885X_ANA_TOP_BOOST_MISC, 0x79}, ++ {WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x00}, ++ {WSA885X_SPK_TOP_COMMON_CTRL2, 0x08}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_LF_CH1_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_COMMON_TUNE1, 0x03}, ++ {WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1, 0x00}, ++ {WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0e}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x40}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0xff}, ++ {WSA885X_ANA_TOP_PLL_STATUS_0, 0x00}, ++ {WSA885X_ANA_TOP_PLL_STATUS_1, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x84}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x02}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0xe0}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0xc9}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0xc9}, ++ {WSA885X_DIG_CTRL0_TOP_CLK_CFG, 0x00}, ++ {WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x00}, ++ {WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00}, ++ {WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00}, ++ {WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00}, ++ {WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x7f}, ++ {WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL1_I2S_CTL0, 0x06}, ++ {WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, WSA885X_I2S_TDM_CH_RX_CH3_EN}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX0_RX_PATH_CTL, 0x24}, ++ {WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX1_RX_PATH_CTL, 0x04}, ++ {WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL7, 0x2a}, ++ {WSA885X_CDC_COMPANDER1_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER1_CTL7, 0x2a}, ++ {WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c}, ++ {WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d}, ++ {WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05}, ++}; ++ ++static void wsa885x_multi_update_bits(struct regmap *regmap, ++ const struct wsa885x_reg_update *updates, ++ size_t num_updates) ++{ ++ size_t i; ++ ++ for (i = 0; i < num_updates; i++) ++ regmap_update_bits(regmap, updates[i].reg, ++ updates[i].mask, updates[i].val); ++} ++ ++static void wsa885x_toggle_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++} ++ ++static void wsa885x_pulse_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++} ++ ++static int wsa885x_tdm_ctl0_slot_num_val(int slots, unsigned int *slot_num_val) ++{ ++ if (!slot_num_val) ++ return -EINVAL; ++ ++ switch (slots) { ++ case 2: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2; ++ return 0; ++ case 4: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4; ++ return 0; ++ case 8: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int wsa885x_reg_update_sequence(struct regmap *regmap, int slots) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x15 }, ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x11 }, ++ }; ++ unsigned int slot_num_val; ++ int ret; ++ ++ if (!regmap) ++ return -EINVAL; ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) ++ return ret; ++ ++ regmap_multi_reg_write(regmap, regs, ARRAY_SIZE(regs)); ++ ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, ++ slot_num_val); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK); ++ regmap_write(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH0_EN); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN); ++ ++ return 0; ++} ++ ++static int wsa885x_wait_for_pll_lock(struct wsa885x_priv *wsa885x) ++{ ++ unsigned int status = 0; ++ int cnt = 0, ret = 0; ++ ++ do { ++ usleep_range(1000, 1100); ++ ret = regmap_read(wsa885x->regmap, WSA885X_ANA_TOP_PLL_STATUS_0, &status); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL status read failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (status & WSA885X_PLL_LOCK_BIT) ++ return 0; ++ } while (++cnt < 20); ++ ++ dev_warn(wsa885x->dev, "PLL lock timeout after 20ms, status=0x%x\n", status); ++ return -ETIMEDOUT; ++} ++ ++static int wsa885x_2s_conf(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_SPK_TOP_COMMON_TUNE1, 0x26 }, ++ { WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x0d }, ++ { WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x0d }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x71 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0xAA }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static const struct reg_sequence wsa885x_reg_init[] = { ++ { WSA885X_CDC_RX0_RX_PATH_CTL, 0x24 }, ++ { WSA885X_CDC_RX1_RX_PATH_CTL, 0x24 }, ++ { WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_CDC_COMPANDER0_CTL0, 0x01 }, ++ { WSA885X_CDC_COMPANDER1_CTL0, 0x01 }, ++ { WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0f }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x4f }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x02 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x85 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0c }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0e }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x0c }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01 }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x82 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x34 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x41 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0x7f }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c }, ++ { WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d }, ++ { WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03 }, ++ { WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05 }, ++ { WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x20 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0x45 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0x45 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x35 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x21 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc7 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x11 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0x80 }, ++ { WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x08 }, ++ { WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x47 }, ++ { WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x47 }, ++ { WSA885X_CDC_COMPANDER0_CTL7, 0x34 }, ++ { WSA885X_CDC_COMPANDER1_CTL7, 0x34 }, ++ { WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x79 }, ++}; ++ ++static int wsa885x_hw_init(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e }, ++ { WSA885X_DIG_CTRL1_INTR_MODE, 0x01 }, ++ { WSA885X_DIG_CTRL1_PIN_CT, 0x04 }, ++ }; ++ int ret; ++ ++ ret = regmap_multi_reg_write(wsa885x->regmap, wsa885x_reg_init, ++ ARRAY_SIZE(wsa885x_reg_init)); ++ if (ret) ++ return ret; ++ ++ if (wsa885x->batt_conf == WSA885X_BATT_2S) { ++ ret = wsa885x_2s_conf(wsa885x); ++ if (ret) ++ return ret; ++ } ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_unmask_interrupts(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_INTR_MASK0, 0x00 }, ++ { WSA885X_INTR_MASK0 + 1, 0x00 }, ++ { WSA885X_INTR_MASK0 + 2, 0xf8 }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_wait_for_pde_state(struct wsa885x_priv *wsa885x, int ps) ++{ ++ unsigned int act_ps = 0, clock_valid = 0; ++ int rc = 0, cnt = 0; ++ ++ if (ps < 0 || ps > 3) ++ return -EINVAL; ++ ++ do { ++ usleep_range(1000, 1500); ++ rc = regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, ++ &act_ps); ++ if (rc) { ++ dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc); ++ return rc; ++ } ++ if (act_ps == ps) ++ return 0; ++ } while (++cnt < 5); ++ ++ if (regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, ++ &clock_valid)) ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid read failed\n", ++ ps, act_ps); ++ else ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid:%d\n", ++ ps, act_ps, clock_valid); ++ ++ return -ETIMEDOUT; ++} ++ ++static void wsa885x_program_stereo_volume(struct wsa885x_priv *wsa885x, ++ int stereo_vol_db, bool commit) ++{ ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00); ++ ++ if (commit) ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++} ++ ++static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct wsa885x_priv *wsa885x; ++ u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ switch (params_rate(params)) { ++ case 8000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_8KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_8000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_8000HZ; ++ break; ++ case 16000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_16KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_16000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_16000HZ; ++ break; ++ case 32000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_32KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_32000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 44100: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_44100HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_44100HZ; ++ break; ++ case 48000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_48000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 88200: ++ case 96000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_96000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_96000HZ; ++ break; ++ case 176400: ++ case 192000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_192000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_192000HZ; ++ break; ++ case 352800: ++ case 384000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ; ++ break; ++ default: ++ dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params)); ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_PCM_RATE_MASK | ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_PCM_RATE(pcm_rate) | ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, ++ cs21_sample_rate_idx); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, ++ cs24_sample_rate_idx); ++ ++ mutex_lock(&wsa885x->state_lock); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_slot_mask, ++ unsigned int rx_slot_mask, int slots, ++ int slot_width) ++{ ++ static const struct wsa885x_reg_update stereo_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT2(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT3(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ static const struct wsa885x_reg_update mono_left_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ }; ++ static const struct wsa885x_reg_update mono_right_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ unsigned int slot_num_val; ++ u32 mask; ++ int ret; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) { ++ dev_err(wsa885x->dev, "%s: unsupported slot count %d\n", ++ __func__, slots); ++ return ret; ++ } ++ ++ if (rx_slot_mask && !wsa885x_is_valid_rx_slot_mask(rx_slot_mask)) { ++ dev_err(wsa885x->dev, ++ "%s: unsupported rx_slot_mask 0x%x\n", ++ __func__, rx_slot_mask); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (rx_slot_mask) ++ wsa885x->rx_slot_mask = rx_slot_mask; ++ else if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mask = wsa885x->rx_slot_mask; ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, ++ WSA885X_I2S_RESET_CTL_RESET_MASK); ++ ++ if (mask == WSA885X_CHANNEL_STEREO) { ++ wsa885x_multi_update_bits(wsa885x->regmap, stereo_updates, ++ ARRAY_SIZE(stereo_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN); ++ } else if (mask == WSA885X_CHANNEL_MONO_LEFT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_left_updates, ++ ARRAY_SIZE(mono_left_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } else if (mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_right_updates, ++ ARRAY_SIZE(mono_right_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, 0); ++ ++ ret = 0; ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, ++ unsigned int freq, int dir) ++{ ++ static const struct reg_sequence pll_prep[] = { ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x03 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x04 }, ++ { WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xB4 }, ++ { WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00 }, ++ { WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence pll_cleanup[] = { ++ { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 }, ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ u32 pll_div; ++ int ret = 0; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (!freq) ++ return -EINVAL; ++ if (WSA885X_CLK_RATE_FIXED % freq) ++ return -EINVAL; ++ pll_div = WSA885X_CLK_RATE_FIXED / freq; ++ if (pll_div > 0xff) ++ return -EINVAL; ++ ++ regmap_multi_reg_write(wsa885x->regmap, pll_prep, ARRAY_SIZE(pll_prep)); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_PLL_DIV_CTL_0, pll_div); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x02); ++ ++ ret = wsa885x_wait_for_pll_lock(wsa885x); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL lock failed, aborting sysclk configuration\n"); ++ regmap_multi_reg_write(wsa885x->regmap, pll_cleanup, ++ ARRAY_SIZE(pll_cleanup)); ++ return ret; ++ } ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream) ++{ ++ static const struct reg_sequence mute_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03 }, ++ }; ++ static const struct reg_sequence mute_commit_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_prep_head_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_prep_tail_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_volume_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_commit_regs[] = { ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_finish_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x00 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ int ret = 0, ps0 = 0, ps3 = 3; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ ret = -EINVAL; ++ goto exit_unlock; ++ } ++ ++ if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ ++ if (mute) { ++ regmap_multi_reg_write(wsa885x->regmap, mute_regs, ++ ARRAY_SIZE(mute_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps3); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "PS3 transition failed: %d\n", ret); ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, mute_commit_regs, ++ ARRAY_SIZE(mute_commit_regs)); ++ } ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs, ++ ARRAY_SIZE(unmute_prep_head_regs)); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, ++ wsa885x->usage_mode); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs, ++ ARRAY_SIZE(unmute_prep_tail_regs)); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_volume_regs, ++ ARRAY_SIZE(unmute_volume_regs)); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_commit_regs, ++ ARRAY_SIZE(unmute_commit_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps0); ++ if (ret) ++ goto exit_unlock; ++ ++ if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN | ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02); ++ } ++ ++ regmap_multi_reg_write(wsa885x->regmap, unmute_finish_regs, ++ ARRAY_SIZE(unmute_finish_regs)); ++ } ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops wsa885x_dai_ops = { ++ .hw_params = wsa885x_codec_hw_params, ++ .set_tdm_slot = wsa885x_codec_set_tdm_slot, ++ .set_sysclk = wsa885x_codec_set_sysclk, ++ .mute_stream = wsa885x_codec_mute_stream, ++ .hw_free = wsa885x_codec_hw_free, ++}; ++ ++static struct snd_soc_dai_driver wsa885x_dai[] = { ++ { ++ .name = "wsa885x_dai_drv", ++ .playback = { ++ .stream_name = "WSA885X TDM Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = WSA885X_RATES, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S32_LE, ++ }, ++ .ops = &wsa885x_dai_ops, ++ }, ++}; ++ ++static void wsa885x_reset_assert(void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (wsa885x->sd_reset) ++ reset_control_assert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 1); ++} ++ ++static void wsa885x_reset_deassert(struct wsa885x_priv *wsa885x) ++{ ++ if (wsa885x->sd_reset) ++ reset_control_deassert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 0); ++} ++ ++static int wsa885x_get_reset(struct device *dev, struct wsa885x_priv *wsa885x) ++{ ++ wsa885x->sd_reset = devm_reset_control_get_optional_shared(dev, NULL); ++ if (IS_ERR(wsa885x->sd_reset)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_reset), ++ "Failed to get reset\n"); ++ else if (wsa885x->sd_reset) ++ return 0; ++ ++ wsa885x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); ++ if (IS_ERR(wsa885x->sd_n)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n), ++ "Shutdown Control GPIO not found\n"); ++ ++ return 0; ++} ++ ++static bool wsa885x_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_DIG_CTRL0_SDCA_COMMIT: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_INTR_CLEAR0: ++ case WSA885X_INTR_CLEAR0 + 1: ++ case WSA885X_INTR_CLEAR0 + 2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool wsa885x_readable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg == WSA885X_INTR_CLEAR0 || ++ reg == WSA885X_INTR_CLEAR0 + 1 || ++ reg == WSA885X_INTR_CLEAR0 + 2) ++ return false; ++ return reg <= 0x88ff; ++} ++ ++static bool wsa885x_writeable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg > 0x88ff) ++ return false; ++ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ return false; ++ default: ++ return true; ++ } ++} ++ ++static const struct regmap_config wsa885x_regmap_cfg = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0x88FF, ++ .ranges = wsa885x_regmap_ranges, ++ .num_ranges = ARRAY_SIZE(wsa885x_regmap_ranges), ++ .reg_defaults = wsa885x_codec_reg_defaults, ++ .num_reg_defaults = ARRAY_SIZE(wsa885x_codec_reg_defaults), ++ .volatile_reg = wsa885x_volatile_register, ++ .writeable_reg = wsa885x_writeable_register, ++ .readable_reg = wsa885x_readable_register, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int wsa885x_component_probe(struct snd_soc_component *component) ++{ ++ struct wsa885x_priv *wsa885x = ++ snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ wsa885x->component = component; ++ snd_soc_component_init_regmap(component, wsa885x->regmap); ++ ++ ret = wsa885x_hw_init(wsa885x); ++ if (ret) ++ return ret; ++ ++ return wsa885x_unmask_interrupts(wsa885x); ++} ++ ++static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ int val; ++ ++ mutex_lock(&wsa885x->state_lock); ++ val = wsa885x->stereo_vol_db + 84; ++ mutex_unlock(&wsa885x->state_lock); ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = val; ++ return 0; ++} ++ ++static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ long val; ++ int stereo_vol_db; ++ ++ val = ucontrol->value.integer.value[0]; ++ ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) { ++ dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val); ++ return -EINVAL; ++ } ++ ++ stereo_vol_db = (int)val - 84; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->stereo_vol_db == stereo_vol_db) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x_program_stereo_volume(wsa885x, stereo_vol_db, true); ++ wsa885x->stereo_vol_db = stereo_vol_db; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_usage_modes_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ mutex_unlock(&wsa885x->state_lock); ++ return -ERANGE; ++ } ++ ++ ucontrol->value.integer.value[0] = wsa885x->usage_mode; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static int wsa885x_usage_modes_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 val = ucontrol->value.integer.value[0]; ++ ++ if (val > WSA885X_USAGE_MODE_MAX) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode == val) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->usage_mode = val; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_rx_slot_mask_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask; ++ ++ mutex_lock(&wsa885x->state_lock); ++ mask = wsa885x->rx_slot_mask; ++ mutex_unlock(&wsa885x->state_lock); ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = mask; ++ ++ return 0; ++} ++ ++static int wsa885x_rx_slot_mask_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask = ucontrol->value.integer.value[0]; ++ ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->rx_slot_mask == mask) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->rx_slot_mask = mask; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static const struct snd_kcontrol_new wsa885x_snd_controls[] = { ++ SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0, ++ wsa885x_usage_modes_get, ++ wsa885x_usage_modes_put), ++ ++ SOC_SINGLE_EXT_TLV("Speaker Volume", SND_SOC_NOPM, ++ 0, WSA885X_FU21_VOL_STEPS, 0, ++ wsa885x_stereo_gain_offset_get, ++ wsa885x_stereo_gain_offset_put, ++ wsa885x_fu21_digital_gain), ++ ++ SOC_SINGLE_EXT("Rx Slot Mask", SND_SOC_NOPM, 0, 3, 0, ++ wsa885x_rx_slot_mask_get, ++ wsa885x_rx_slot_mask_put), ++}; ++ ++static const struct snd_soc_component_driver wsa885x_component = { ++ .name = "wsa885x", ++ .probe = wsa885x_component_probe, ++ .controls = wsa885x_snd_controls, ++ .num_controls = ARRAY_SIZE(wsa885x_snd_controls), ++}; ++ ++static irqreturn_t wsa885x_handle_irq(int irq_idx, void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (irq_idx < 0 || irq_idx >= WSA885X_IRQ_MAX) ++ return IRQ_NONE; ++ ++ switch (irq_idx) { ++ case WSA885X_IRQ_INT_SAF2WAR: ++ case WSA885X_IRQ_INT_WAR2SAF: ++ case WSA885X_IRQ_INT_DISABLE: ++ case WSA885X_IRQ_INT_INTR_GPIO1_PIN: ++ case WSA885X_IRQ_INT_INTR_GPIO2_PIN: ++ case WSA885X_IRQ_INT_PA0_OCP: ++ case WSA885X_IRQ_INT_PA1_OCP: ++ case WSA885X_IRQ_INT_CLIP0: ++ case WSA885X_IRQ_INT_CLIP1: ++ case WSA885X_IRQ_INT_CLK_WD: ++ case WSA885X_IRQ_INT_BOP: ++ case WSA885X_IRQ_INT_UVLO: ++ case WSA885X_IRQ_INT_PCM_DATA0_DC: ++ case WSA885X_IRQ_INT_PCM_DATA1_DC: ++ case WSA885X_IRQ_INT_PLL_UNLOCKED: ++ case WSA885X_IRQ_INT_PROT_MODE_CHANGE: ++ case WSA885X_IRQ_INT_PB_CLOCK_VALID: ++ case WSA885X_IRQ_INT_SENSE_CLOCK_VALID: ++ break; ++ case WSA885X_IRQ_INT_PCM_DATA0_WD: ++ case WSA885X_IRQ_INT_PCM_DATA1_WD: ++ if (irq_idx == WSA885X_IRQ_INT_PCM_DATA0_WD) ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ else ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ break; ++ case WSA885X_IRQ_INT_PA0_FSM_ERR: ++ case WSA885X_IRQ_INT_PA1_FSM_ERR: ++ case WSA885X_IRQ_INT_MAIN_FSM_ERR: ++ if (irq_idx == WSA885X_IRQ_INT_MAIN_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_DIG_CTRL0_POWER_FSM_CTL0, ++ WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA0_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA0_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA1_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA1_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t wsa885x_interrupt_handler(int irq, void *data) ++{ ++ static const unsigned int status_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_STATUS0, ++ WSA885X_INTR_STATUS0 + 1, ++ WSA885X_INTR_STATUS0 + 2, ++ }; ++ static const unsigned int clear_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_CLEAR0, ++ WSA885X_INTR_CLEAR0 + 1, ++ WSA885X_INTR_CLEAR0 + 2, ++ }; ++ unsigned int status[WSA885X_NUM_REGS] = { 0 }; ++ struct wsa885x_priv *wsa885x = data; ++ irqreturn_t handled = IRQ_NONE; ++ irqreturn_t irq_ret; ++ int i, bit, ret, irq_num; ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ ret = regmap_read(wsa885x->regmap, status_reg[i], &status[i]); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "Failed to read status_reg[%d] (0x%x): %d\n", ++ i, status_reg[i], ret); ++ status[i] = 0; ++ continue; ++ } ++ } ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ for (bit = 0; bit < 8; bit++) { ++ if (status[i] & BIT(bit)) { ++ irq_num = i * 8 + bit; ++ regmap_write(wsa885x->regmap, clear_reg[i], BIT(bit)); ++ regmap_write(wsa885x->regmap, clear_reg[i], 0); ++ if (irq_num >= WSA885X_IRQ_MAX) { ++ dev_warn_ratelimited(wsa885x->dev, ++ "Unexpected IRQ bit %d (reg %d)\n", ++ bit, i); ++ handled = IRQ_HANDLED; ++ continue; ++ } ++ irq_ret = wsa885x_handle_irq(irq_num, wsa885x); ++ if (irq_ret == IRQ_HANDLED) ++ handled = IRQ_HANDLED; ++ } ++ } ++ } ++ return handled; ++} ++ ++static int wsa885x_register_irq(struct wsa885x_priv *wsa885x) ++{ ++ if (!wsa885x->client->irq) ++ return dev_err_probe(wsa885x->dev, -EINVAL, ++ "IRQ is not configured\n"); ++ ++ return devm_request_threaded_irq(wsa885x->dev, wsa885x->client->irq, NULL, ++ wsa885x_interrupt_handler, ++ IRQF_ONESHOT, ++ dev_name(wsa885x->dev), wsa885x); ++} ++ ++static int wsa885x_probe(struct i2c_client *client) ++{ ++ struct wsa885x_priv *wsa885x; ++ const struct snd_soc_component_driver *component_driver = &wsa885x_component; ++ const char *battery_config; ++ unsigned int i; ++ int ret; ++ struct device *dev = &client->dev; ++ ++ wsa885x = devm_kzalloc(dev, sizeof(*wsa885x), GFP_KERNEL); ++ if (!wsa885x) ++ return -ENOMEM; ++ ++ wsa885x->client = client; ++ wsa885x->dev = dev; ++ wsa885x->stereo_vol_db = -84; ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mutex_init(&wsa885x->state_lock); ++ wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg); ++ ++ if (IS_ERR(wsa885x->regmap)) ++ return PTR_ERR(wsa885x->regmap); ++ ++ ret = device_property_read_string(dev, "qcom,battery-config", ++ &battery_config); ++ if (ret) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "1s")) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "2s")) { ++ wsa885x->batt_conf = WSA885X_BATT_2S; ++ } else { ++ return dev_err_probe(dev, -EINVAL, ++ "Invalid battery config %s (expected 1s or 2s)\n", ++ battery_config); ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(wsa885x_supply_name); i++) { ++ ret = devm_regulator_get_enable(dev, wsa885x_supply_name[i]); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "Failed to enable regulator %s\n", ++ wsa885x_supply_name[i]); ++ } ++ ++ ret = wsa885x_get_reset(dev, wsa885x); ++ if (ret) ++ return ret; ++ ++ wsa885x_reset_deassert(wsa885x); ++ usleep_range(5000, 5500); ++ ++ ret = devm_add_action_or_reset(dev, wsa885x_reset_assert, wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n"); ++ ++ i2c_set_clientdata(client, wsa885x); ++ ++ ret = wsa885x_register_irq(wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "wsa885x irq registration failed\n"); ++ ++ ret = devm_snd_soc_register_component(dev, component_driver, ++ wsa885x_dai, ++ ARRAY_SIZE(wsa885x_dai)); ++ if (ret) ++ return dev_err_probe(dev, ret, "Codec component registration failed\n"); ++ ++ return 0; ++} ++ ++static const struct of_device_id wsa885x_dt_match[] = { ++ { ++ .compatible = "qcom,wsa8855", ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, wsa885x_dt_match); ++ ++static const struct i2c_device_id wsa885x_id[] = { ++ { ++ .name = "wsa885x", ++ .driver_data = 0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, wsa885x_id); ++ ++static struct i2c_driver wsa885x_driver = { ++ .driver = { ++ .name = "wsa885x", ++ .of_match_table = wsa885x_dt_match, ++ }, ++ .probe = wsa885x_probe, ++ .id_table = wsa885x_id, ++}; ++ ++module_i2c_driver(wsa885x_driver); ++ ++MODULE_DESCRIPTION("ASoC WSA885X Stereo Smart PA Codec Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c +--- a/sound/soc/codecs/zl38060.c ++++ b/sound/soc/codecs/zl38060.c +@@ -162,7 +162,7 @@ static int zl38_fw_send_xaddr(struct regmap *regmap, const void *data) + static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + { + const struct ihex_binrec *rec; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + u32 addr; + u16 len; + int err; +@@ -180,7 +180,7 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + return err; + err = zl38_fw_enter_boot_mode(regmap); + if (err) +- goto out; ++ return err; + rec = (const struct ihex_binrec *)fw->data; + while (rec) { + addr = be32_to_cpu(rec->addr); +@@ -195,15 +195,12 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + err = -EINVAL; + } + if (err) +- goto out; ++ return err; + /* next ! */ + rec = ihex_next_binrec(rec); + } +- err = zl38_fw_go(regmap); + +-out: +- release_firmware(fw); +- return err; ++ return zl38_fw_go(regmap); + } + + +diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c +--- a/sound/soc/dwc/dwc-i2s.c ++++ b/sound/soc/dwc/dwc-i2s.c +@@ -955,7 +955,9 @@ static int dw_i2s_probe(struct platform_device *pdev) + } + + irq = platform_get_irq_optional(pdev, 0); +- if (irq >= 0) { ++ if (irq == -EPROBE_DEFER) ++ return irq; ++ if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { +diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c +--- a/sound/soc/fsl/fsl-asoc-card.c ++++ b/sound/soc/fsl/fsl-asoc-card.c +@@ -69,7 +69,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + + /** + * struct codec_priv - CODEC private data +- * @mclk: Main clock of the CODEC + * @mclk_freq: Clock rate of MCLK + * @free_freq: Clock rate of MCLK for hw_free() + * @mclk_id: MCLK (or main clock) id for set_sysclk() +@@ -80,7 +79,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + * to stay within PLL frequency limits + */ + struct codec_priv { +- struct clk *mclk; + unsigned long mclk_freq; + unsigned long free_freq; + u32 mclk_id; +@@ -109,12 +107,58 @@ struct cpu_priv { + u32 slot_num; + }; + ++struct fsl_asoc_card_priv; ++ ++/* ++ * struct fsl_asoc_card_pdata - per-compatible static card description ++ * @sysclk_dir: initial CPU SYSCLK direction override (0 = leave default IN) ++ * @sysclk_ratio: SYSCLK ratio on sample rate (0 = not used) ++ * @slot_width: TDM slot width (0 = not TDM) ++ * @codec_dai_name: name of the codec DAI ++ * @codec_mclk_id: MCLK id passed to set_sysclk() for the codec ++ * @codec_fll_id: FLL id; only valid when has_pll is true ++ * @codec_pll_id: PLL id; only valid when has_pll is true ++ * @codec_pll_ratio_s24: PLL output ratio for S24_LE ++ * @has_pll: codec uses PLL/FLL; codec_fll_id and codec_pll_id are valid ++ * @dai_fmt: DAI format flags ++ * @playback_only: restrict card to playback direction ++ * @capture_only: restrict card to capture direction ++ * @dapm_routes: DAPM route table override ++ * @num_dapm_routes: number of entries in dapm_routes ++ * @exclude_format: PCM format bitmask excluded (for SAI + WM8960/WM8962) ++ * @codec_init: codec-specific init run after mclk_freq is populated ++ * @probe_init: optional DT-driven init run at end of probe() (e.g. SPDIF codec discovery) ++ */ ++struct fsl_asoc_card_pdata { ++ u32 sysclk_dir[2]; ++ u32 sysclk_ratio[2]; ++ u32 slot_width; ++ const char *codec_dai_name; ++ u32 codec_mclk_id; ++ int codec_fll_id; ++ int codec_pll_id; ++ int codec_pll_ratio_s24; ++ bool has_pll; ++ bool playback_only; ++ bool capture_only; ++ u32 dai_fmt; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ u64 exclude_format; ++ int (*codec_init)(struct fsl_asoc_card_priv *priv); ++ int (*probe_init)(struct device_node *codec_np[], ++ struct device_node *cpu_np, ++ const char *codec_dai_name[], ++ struct fsl_asoc_card_priv *priv); ++}; ++ + /** + * struct fsl_asoc_card_priv - Freescale Generic ASOC card private data + * @dai_link: DAI link structure including normal one and DPCM link + * @hp_jack: Headphone Jack structure + * @mic_jack: Microphone Jack structure + * @pdev: platform device pointer ++ * @pdata: pointer to the per-compatible card platform data + * @codec_priv: CODEC private data + * @cpu_priv: CPU private data + * @card: ASoC card structure +@@ -135,6 +179,7 @@ struct fsl_asoc_card_priv { + struct simple_util_jack hp_jack; + struct simple_util_jack mic_jack; + struct platform_device *pdev; ++ const struct fsl_asoc_card_pdata *pdata; + struct codec_priv codec_priv[2]; + struct cpu_priv cpu_priv; + struct snd_soc_card card; +@@ -609,6 +654,192 @@ static int fsl_asoc_card_spdif_init(struct device_node *codec_np[], + return 0; + } + ++static int fsl_asoc_card_cs42888_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ unsigned long mclk_freq = priv->codec_priv[0].mclk_freq; ++ ++ /* ++ * Set CPU sysclk frequency from codec MCLK only if not already ++ * set by the CPU DAI init (e.g. ESAI extal clock takes precedence). ++ */ ++ if (!priv->cpu_priv.sysclk_freq[TX]) ++ priv->cpu_priv.sysclk_freq[TX] = mclk_freq; ++ if (!priv->cpu_priv.sysclk_freq[RX]) ++ priv->cpu_priv.sysclk_freq[RX] = mclk_freq; ++ ++ priv->constraint_channels = &cs42888_channel_constraints; ++ if (mclk_freq % 12288000 == 0) ++ priv->constraint_rates = &cs42888_rate_48k_constraints; ++ else if (mclk_freq % 11289600 == 0) ++ priv->constraint_rates = &cs42888_rate_44k_constraints; ++ else ++ dev_warn(&priv->pdev->dev, ++ "Unknown MCLK frequency %lu, no rate constraints\n", ++ mclk_freq); ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_wm8958_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; ++ return 0; ++} ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs42888_pdata = { ++ .codec_dai_name = "cs42888", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .slot_width = 32, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++ .codec_init = fsl_asoc_card_cs42888_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs427x_pdata = { ++ .codec_dai_name = "cs4271-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = CS427x_SYSCLK_MCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_sgtl5000_pdata = { ++ .codec_dai_name = "sgtl5000", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = SGTL5000_SYSCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic32x4_pdata = { ++ .codec_dai_name = "tlv320aic32x4-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic31xx_pdata = { ++ .codec_dai_name = "tlv320dac31xx-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8962_pdata = { ++ .codec_dai_name = "wm8962", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8962_SYSCLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8962_SYSCLK_FLL, ++ .codec_pll_id = WM8962_FLL, ++ /* ++ * WM8962 has same BCLK generation limitations as WM8960. ++ * See WM8960 section for detailed explanation. ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8960_pdata = { ++ .codec_dai_name = "wm8960-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .has_pll = true, ++ .codec_fll_id = WM8960_SYSCLK_AUTO, ++ .codec_pll_id = WM8960_SYSCLK_AUTO, ++ /* ++ * WM8960 in master mode cannot generate exact 1.92 MHz BCLK ++ * required for S20_3LE (48kHz x 2ch x 20bit). Closest available ++ * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. ++ * ++ * In SAI master mode, SAI derive BCLK from MCLK using integer ++ * dividers only. S20_3LE requires non-integer divider ratios ++ * with standard MCLK frequencies. For example, 48kHz stereo ++ * needs 1.920 MHz BCLK, which requires a divider of 6.4 from ++ * 12.288 MHz MCLK (not an integer). ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_ac97_pdata = { ++ .codec_dai_name = "ac97-hifi", ++ .dai_fmt = SND_SOC_DAIFMT_AC97, ++ .dapm_routes = audio_map_ac97, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_ac97), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_mqs_pdata = { ++ .codec_dai_name = "fsl-mqs-dai", ++ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBC_CFC | ++ SND_SOC_DAIFMT_NB_NF, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8524_pdata = { ++ .codec_dai_name = "wm8524-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ /* RX=0, TX=1: set TX (index 1) to CLOCK_OUT, RX stays at default IN */ ++ .sysclk_dir = { 0, SND_SOC_CLOCK_OUT }, ++ .sysclk_ratio = { 0, 256 }, ++ .slot_width = 32, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_si476x_pdata = { ++ .codec_dai_name = "si476x-codec", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .dapm_routes = audio_map_rx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_rx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8958_pdata = { ++ .codec_dai_name = "wm8994-aif1", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8994_FLL_SRC_MCLK1, ++ .has_pll = true, ++ .codec_fll_id = WM8994_SYSCLK_FLL1, ++ .codec_pll_id = WM8994_FLL1, ++ .codec_init = fsl_asoc_card_wm8958_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_nau8822_pdata = { ++ .codec_dai_name = "nau8822-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = NAU8822_CLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = NAU8822_CLK_PLL, ++ .codec_pll_id = NAU8822_CLK_PLL, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8904_pdata = { ++ .codec_dai_name = "wm8904-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8904_FLL_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8904_CLK_FLL, ++ .codec_pll_id = WM8904_FLL_MCLK, ++ .codec_pll_ratio_s24 = 192, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_spdif_pdata = { ++ .codec_dai_name = "spdif", ++ .dai_fmt = DAI_FMT_BASE, ++ .probe_init = fsl_asoc_card_spdif_init, ++}; ++ + static int hp_jack_event(struct notifier_block *nb, unsigned long event, + void *data) + { +@@ -643,17 +874,173 @@ static struct notifier_block mic_jack_nb = { + .notifier_call = mic_jack_event, + }; + +-static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++/* ++ * fsl_asoc_card_init_cpu - configure CPU DAI-specific settings. ++ * ++ * Called from late_probe() when the CPU DAI component is guaranteed bound. ++ */ ++static int fsl_asoc_card_init_cpu(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) + { + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); +- struct snd_soc_pcm_runtime *rtd = list_first_entry( +- &card->rtd_list, struct snd_soc_pcm_runtime, list); ++ struct device_node *np = priv->pdev->dev.of_node; ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ const char *comp_drv_name = cpu_dai->component->driver->name; ++ struct device *dev = card->dev; ++ int ret; ++ ++ if (!strcmp(comp_drv_name, "fsl-ssi")) { ++ /* Only SSI needs to configure AUDMUX */ ++ ret = fsl_asoc_card_audmux_init(np, priv); ++ if (ret) { ++ dev_err(dev, "failed to init audmux\n"); ++ return ret; ++ } ++ } else if (!strcmp(comp_drv_name, "fsl-esai")) { ++ struct clk *esai_clk = clk_get(cpu_dai->dev, "extal"); ++ ++ if (!IS_ERR(esai_clk)) { ++ priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); ++ priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); ++ clk_put(esai_clk); ++ } else { ++ dev_warn(dev, "failed to get ESAI extal clock: %ld\n", PTR_ERR(esai_clk)); ++ } ++ ++ priv->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL; ++ priv->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL; ++ } else if (!strcmp(comp_drv_name, "fsl-sai")) { ++ priv->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1; ++ priv->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1; ++ ++ if (priv->pdata->exclude_format) ++ priv->exclude_format = priv->pdata->exclude_format; ++ } ++ ++ return 0; ++} ++ ++/* ++ * fsl_asoc_card_init_codecs - read codec MCLK rates and set codec sysclk. ++ * ++ * Called from late_probe() after all components are bound. ++ */ ++static int fsl_asoc_card_init_codecs(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ const struct fsl_asoc_card_pdata *pdata = priv->pdata; + struct snd_soc_dai *codec_dai; + struct codec_priv *codec_priv; + struct device *dev = card->dev; + int codec_idx; + int ret; + ++ /* Read MCLK rate from each bound codec component */ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ struct clk *codec_clk = clk_get(codec_dai->component->dev, NULL); ++ ++ codec_priv = &priv->codec_priv[codec_idx]; ++ if (!IS_ERR(codec_clk)) { ++ codec_priv->mclk_freq = clk_get_rate(codec_clk); ++ clk_put(codec_clk); ++ } ++ } ++ ++ if (pdata->codec_init) { ++ ret = pdata->codec_init(priv); ++ if (ret) ++ return ret; ++ } ++ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ codec_priv = &priv->codec_priv[codec_idx]; ++ ++ ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, ++ codec_priv->mclk_freq, SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) { ++ dev_err(dev, "failed to set sysclk in %s\n", __func__); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static void fsl_asoc_card_free_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ ++ if (priv->hp_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->hp_jack.jack, &hp_jack_nb); ++ snd_soc_jack_free_gpios(&priv->hp_jack.jack, 1, &priv->hp_jack.gpio); ++ priv->hp_jack.gpio.desc = NULL; ++ } ++ ++ if (priv->mic_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->mic_jack.jack, &mic_jack_nb); ++ snd_soc_jack_free_gpios(&priv->mic_jack.jack, 1, &priv->mic_jack.gpio); ++ priv->mic_jack.gpio.desc = NULL; ++ } ++} ++ ++/* ++ * fsl_asoc_card_init_jack - register optional headphone and mic jacks. ++ * ++ * Called from late_probe() once per card bind cycle. ++ */ ++static int fsl_asoc_card_init_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct device_node *np = priv->pdev->dev.of_node; ++ int ret; ++ ++ /* ++ * Properties "hp-det-gpios" and "mic-det-gpios" are optional. ++ * simple_util_init_jack() checks for the GPIO property and ++ * does nothing if it is absent. ++ */ ++ if (of_property_present(np, "hp-det-gpios") || ++ of_property_present(np, "hp-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->hp_jack, ++ 1, NULL, "Headphone Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); ++ } ++ ++ if (of_property_present(np, "mic-det-gpios") || ++ of_property_present(np, "mic-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->mic_jack, ++ 0, NULL, "Mic Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); ++ } ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct snd_soc_pcm_runtime *rtd; ++ int ret; ++ ++ /* Use the first rtd which carries the CPU+codec DAIs */ ++ rtd = list_first_entry(&card->rtd_list, ++ struct snd_soc_pcm_runtime, list); ++ ++ ret = fsl_asoc_card_init_jack(card); ++ if (ret) ++ goto jack_fail; ++ ++ ret = fsl_asoc_card_init_cpu(card, rtd); ++ if (ret) ++ goto jack_fail; ++ + if (fsl_asoc_card_is_ac97(priv)) { + #if IS_ENABLED(CONFIG_SND_AC97_CODEC) + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; +@@ -671,19 +1058,20 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) + return 0; + } + +- for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { +- codec_priv = &priv->codec_priv[codec_idx]; ++ ret = fsl_asoc_card_init_codecs(card, rtd); ++ if (ret) ++ goto jack_fail; + +- ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, +- codec_priv->mclk_freq, SND_SOC_CLOCK_IN); +- if (ret && ret != -ENOTSUPP) { +- dev_err(dev, "failed to set sysclk in %s\n", __func__); +- return ret; +- } ++ return 0; + +- if (!IS_ERR_OR_NULL(codec_priv->mclk)) +- clk_prepare_enable(codec_priv->mclk); +- } ++jack_fail: ++ fsl_asoc_card_free_jack(card); ++ return ret; ++} ++ ++static int fsl_asoc_card_card_remove(struct snd_soc_card *card) ++{ ++ fsl_asoc_card_free_jack(card); + + return 0; + } +@@ -697,12 +1085,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + struct platform_device *asrc_pdev = NULL; + struct device_node *bitclkprovider = NULL; + struct device_node *frameprovider = NULL; +- struct platform_device *cpu_pdev; + struct fsl_asoc_card_priv *priv; +- struct device *codec_dev[2] = { NULL, NULL }; ++ const struct fsl_asoc_card_pdata *pdata; + struct snd_soc_dai_link_component *dlc; +- const char *codec_dai_name[2]; +- const char *codec_dev_name[2]; ++ const char *codec_dai_name[2] = { NULL, NULL }; + u32 asrc_fmt = 0; + int codec_idx; + u32 width; +@@ -714,6 +1100,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + + priv->pdev = pdev; + ++ pdata = of_device_get_match_data(&pdev->dev); ++ if (!pdata) { ++ dev_err(&pdev->dev, "unknown Device Tree compatible\n"); ++ return -EINVAL; ++ } ++ priv->pdata = pdata; ++ + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + /* Give a chance to old DT bindings */ + if (!cpu_np) +@@ -726,52 +1119,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto fail; + } + +- cpu_pdev = of_find_device_by_node(cpu_np); +- if (!cpu_pdev) { +- ret = dev_err_probe(&pdev->dev, -EPROBE_DEFER, +- "failed to find CPU DAI device\n"); +- goto fail; +- } +- + codec_np[0] = of_parse_phandle(np, "audio-codec", 0); + codec_np[1] = of_parse_phandle(np, "audio-codec", 1); + +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_np[codec_idx]) { +- struct platform_device *codec_pdev; +- struct i2c_client *codec_i2c; +- +- codec_i2c = of_find_i2c_device_by_node(codec_np[codec_idx]); +- if (codec_i2c) { +- codec_dev[codec_idx] = &codec_i2c->dev; +- codec_dev_name[codec_idx] = codec_i2c->name; +- } +- if (!codec_dev[codec_idx]) { +- codec_pdev = of_find_device_by_node(codec_np[codec_idx]); +- if (codec_pdev) { +- codec_dev[codec_idx] = &codec_pdev->dev; +- codec_dev_name[codec_idx] = codec_pdev->name; +- } +- } +- } +- } +- + asrc_np = of_parse_phandle(np, "audio-asrc", 0); + if (asrc_np) + asrc_pdev = of_find_device_by_node(asrc_np); + +- /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_dev[codec_idx]) { +- struct clk *codec_clk = clk_get(codec_dev[codec_idx], NULL); +- +- if (!IS_ERR(codec_clk)) { +- priv->codec_priv[codec_idx].mclk_freq = clk_get_rate(codec_clk); +- clk_put(codec_clk); +- } +- } +- } +- + /* Default sample rate and format, will be updated in hw_params() */ + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; +@@ -823,133 +1177,40 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + /* Diversify the card configurations */ +- if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) { +- codec_dai_name[0] = "cs42888"; +- priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.slot_width = 32; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->constraint_channels = &cs42888_channel_constraints; +- if (priv->codec_priv[0].mclk_freq % 12288000 == 0) +- priv->constraint_rates = &cs42888_rate_48k_constraints; +- else if (priv->codec_priv[0].mclk_freq % 11289600 == 0) +- priv->constraint_rates = &cs42888_rate_44k_constraints; +- else +- dev_warn(&pdev->dev, "Unknown MCLK frequency %lu, no rate constraints\n", +- priv->codec_priv[0].mclk_freq); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { +- codec_dai_name[0] = "cs4271-hifi"; +- priv->codec_priv[0].mclk_id = CS427x_SYSCLK_MCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { +- codec_dai_name[0] = "sgtl5000"; +- priv->codec_priv[0].mclk_id = SGTL5000_SYSCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic32x4")) { +- codec_dai_name[0] = "tlv320aic32x4-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic31xx")) { +- codec_dai_name[0] = "tlv320dac31xx-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; ++ priv->cpu_priv.sysclk_dir[TX] = pdata->sysclk_dir[TX]; ++ priv->cpu_priv.sysclk_dir[RX] = pdata->sysclk_dir[RX]; ++ priv->cpu_priv.sysclk_ratio[TX] = pdata->sysclk_ratio[TX]; ++ priv->cpu_priv.sysclk_ratio[RX] = pdata->sysclk_ratio[RX]; ++ priv->cpu_priv.slot_width = pdata->slot_width; ++ ++ codec_dai_name[0] = pdata->codec_dai_name; ++ priv->codec_priv[0].mclk_id = pdata->codec_mclk_id; ++ if (pdata->has_pll) { ++ priv->codec_priv[0].fll_id = pdata->codec_fll_id; ++ priv->codec_priv[0].pll_id = pdata->codec_pll_id; ++ } ++ if (pdata->codec_pll_ratio_s24) ++ priv->codec_priv[0].pll_ratio_s24 = pdata->codec_pll_ratio_s24; ++ ++ if (pdata->playback_only) { + priv->dai_link[1].playback_only = 1; + priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { +- codec_dai_name[0] = "wm8962"; +- priv->codec_priv[0].mclk_id = WM8962_SYSCLK_MCLK; +- priv->codec_priv[0].fll_id = WM8962_SYSCLK_FLL; +- priv->codec_priv[0].pll_id = WM8962_FLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8962 has same BCLK generation limitations as WM8960. +- * See WM8960 section for detailed explanation. +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { +- codec_dai_name[0] = "wm8960-hifi"; +- priv->codec_priv[0].fll_id = WM8960_SYSCLK_AUTO; +- priv->codec_priv[0].pll_id = WM8960_SYSCLK_AUTO; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8960 in master mode cannot generate exact 1.92 MHz BCLK +- * required for S20_3LE (48kHz × 2ch × 20bit). Closest available +- * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. +- * +- * In SAI master mode, SAI derive BCLK from MCLK using integer +- * dividers only. S20_3LE requires non-integer divider ratios +- * with standard MCLK frequencies. For example, 48kHz stereo +- * needs 1.920 MHz BCLK, which requires a divider of 6.4 from +- * 12.288 MHz MCLK (not an integer). +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { +- codec_dai_name[0] = "ac97-hifi"; +- priv->dai_fmt = SND_SOC_DAIFMT_AC97; +- priv->card.dapm_routes = audio_map_ac97; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) { +- codec_dai_name[0] = "fsl-mqs-dai"; +- priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J | +- SND_SOC_DAIFMT_CBC_CFC | +- SND_SOC_DAIFMT_NB_NF; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8524")) { +- codec_dai_name[0] = "wm8524-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.slot_width = 32; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_ratio[TX] = 256; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-si476x")) { +- codec_dai_name[0] = "si476x-codec"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->card.dapm_routes = audio_map_rx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8958")) { +- codec_dai_name[0] = "wm8994-aif1"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- priv->codec_priv[0].mclk_id = WM8994_FLL_SRC_MCLK1; +- priv->codec_priv[0].fll_id = WM8994_SYSCLK_FLL1; +- priv->codec_priv[0].pll_id = WM8994_FLL1; +- priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; +- priv->card.dapm_routes = NULL; +- priv->card.num_dapm_routes = 0; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-nau8822")) { +- codec_dai_name[0] = "nau8822-hifi"; +- priv->codec_priv[0].mclk_id = NAU8822_CLK_MCLK; +- priv->codec_priv[0].fll_id = NAU8822_CLK_PLL; +- priv->codec_priv[0].pll_id = NAU8822_CLK_PLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- if (codec_dev[0]) +- priv->codec_priv[0].mclk = devm_clk_get(codec_dev[0], NULL); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8904")) { +- codec_dai_name[0] = "wm8904-hifi"; +- priv->codec_priv[0].mclk_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].fll_id = WM8904_CLK_FLL; +- priv->codec_priv[0].pll_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].pll_ratio_s24 = 192; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-spdif")) { +- ret = fsl_asoc_card_spdif_init(codec_np, cpu_np, codec_dai_name, priv); ++ } ++ if (pdata->capture_only) { ++ priv->dai_link[1].capture_only = 1; ++ priv->dai_link[2].capture_only = 1; ++ } ++ ++ priv->dai_fmt = pdata->dai_fmt; ++ ++ priv->card.dapm_routes = pdata->dapm_routes; ++ priv->card.num_dapm_routes = pdata->num_dapm_routes; ++ ++ if (pdata->probe_init) { ++ ret = pdata->probe_init(codec_np, cpu_np, ++ codec_dai_name, priv); + if (ret) + goto asrc_fail; +- } else { +- dev_err(&pdev->dev, "unknown Device Tree compatible\n"); +- ret = -EINVAL; +- goto asrc_fail; + } + + /* +@@ -995,51 +1256,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + of_node_put(bitclkprovider); + of_node_put(frameprovider); + +- if (!fsl_asoc_card_is_ac97(priv) && !codec_dev[0] +- && codec_dai_name[0] != snd_soc_dummy_dlc.dai_name) { +- dev_dbg(&pdev->dev, "failed to find codec device\n"); +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- /* Common settings for corresponding Freescale CPU DAI driver */ +- if (of_node_name_eq(cpu_np, "ssi")) { +- /* Only SSI needs to configure AUDMUX */ +- ret = fsl_asoc_card_audmux_init(np, priv); +- if (ret) { +- dev_err(&pdev->dev, "failed to init audmux\n"); +- goto asrc_fail; +- } +- } else if (of_node_name_eq(cpu_np, "esai")) { +- struct clk *esai_clk = clk_get(&cpu_pdev->dev, "extal"); +- +- if (!IS_ERR(esai_clk)) { +- priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); +- priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); +- clk_put(esai_clk); +- } else if (PTR_ERR(esai_clk) == -EPROBE_DEFER) { +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; +- priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; +- } else if (of_node_name_eq(cpu_np, "sai")) { +- priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; +- priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; +- } +- + /* Initialize sound card */ + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) { +- snprintf(priv->name, sizeof(priv->name), "%s-audio", +- fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name[0]); +- priv->card.name = priv->name; ++ /* ++ * "model" is required by the DT binding. Enforce it here so ++ * the driver fails with a clear message. ++ */ ++ dev_err(&pdev->dev, "Error parsing card name: %d\n", ret); ++ goto asrc_fail; + } + priv->card.dai_link = priv->dai_link; + priv->card.late_probe = fsl_asoc_card_late_probe; ++ priv->card.remove = fsl_asoc_card_card_remove; + priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); + +@@ -1146,39 +1377,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto asrc_fail; + } + +- /* +- * Properties "hp-det-gpios" and "mic-det-gpios" are optional, and +- * simple_util_init_jack() uses these properties for creating +- * Headphone Jack and Microphone Jack. +- * +- * The notifier is initialized in snd_soc_card_jack_new(), then +- * snd_soc_jack_notifier_register can be called. +- */ +- if (of_property_present(np, "hp-det-gpios") || +- of_property_present(np, "hp-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->hp_jack, +- 1, NULL, "Headphone Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); +- } +- +- if (of_property_present(np, "mic-det-gpios") || +- of_property_present(np, "mic-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->mic_jack, +- 0, NULL, "Mic Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); +- } +- + asrc_fail: + of_node_put(asrc_np); + of_node_put(codec_np[0]); + of_node_put(codec_np[1]); +- put_device(&cpu_pdev->dev); + fail: + of_node_put(cpu_np); + +@@ -1186,21 +1388,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id fsl_asoc_card_dt_ids[] = { +- { .compatible = "fsl,imx-audio-ac97", }, +- { .compatible = "fsl,imx-audio-cs42888", }, +- { .compatible = "fsl,imx-audio-cs427x", }, +- { .compatible = "fsl,imx-audio-tlv320aic32x4", }, +- { .compatible = "fsl,imx-audio-tlv320aic31xx", }, +- { .compatible = "fsl,imx-audio-sgtl5000", }, +- { .compatible = "fsl,imx-audio-wm8962", }, +- { .compatible = "fsl,imx-audio-wm8960", }, +- { .compatible = "fsl,imx-audio-mqs", }, +- { .compatible = "fsl,imx-audio-wm8524", }, +- { .compatible = "fsl,imx-audio-si476x", }, +- { .compatible = "fsl,imx-audio-wm8958", }, +- { .compatible = "fsl,imx-audio-nau8822", }, +- { .compatible = "fsl,imx-audio-wm8904", }, +- { .compatible = "fsl,imx-audio-spdif", }, ++ { .compatible = "fsl,imx-audio-ac97", .data = &fsl_asoc_ac97_pdata }, ++ { .compatible = "fsl,imx-audio-cs42888", .data = &fsl_asoc_cs42888_pdata }, ++ { .compatible = "fsl,imx-audio-cs427x", .data = &fsl_asoc_cs427x_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic32x4", .data = &fsl_asoc_tlv320aic32x4_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic31xx", .data = &fsl_asoc_tlv320aic31xx_pdata }, ++ { .compatible = "fsl,imx-audio-sgtl5000", .data = &fsl_asoc_sgtl5000_pdata }, ++ { .compatible = "fsl,imx-audio-wm8962", .data = &fsl_asoc_wm8962_pdata }, ++ { .compatible = "fsl,imx-audio-wm8960", .data = &fsl_asoc_wm8960_pdata }, ++ { .compatible = "fsl,imx-audio-mqs", .data = &fsl_asoc_mqs_pdata }, ++ { .compatible = "fsl,imx-audio-wm8524", .data = &fsl_asoc_wm8524_pdata }, ++ { .compatible = "fsl,imx-audio-si476x", .data = &fsl_asoc_si476x_pdata }, ++ { .compatible = "fsl,imx-audio-wm8958", .data = &fsl_asoc_wm8958_pdata }, ++ { .compatible = "fsl,imx-audio-nau8822", .data = &fsl_asoc_nau8822_pdata }, ++ { .compatible = "fsl,imx-audio-wm8904", .data = &fsl_asoc_wm8904_pdata }, ++ { .compatible = "fsl,imx-audio-spdif", .data = &fsl_asoc_spdif_pdata }, + {} + }; + MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); +diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c +--- a/sound/soc/fsl/fsl_asrc.c ++++ b/sound/soc/fsl/fsl_asrc.c +@@ -222,10 +222,9 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *asrc = pair->asrc; + struct device *dev = &asrc->pdev->dev; +- unsigned long lock_flags; + int i, ret = 0; + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { + if (asrc->pair[i] != NULL) +@@ -250,8 +249,6 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + pair->index = index; + } + +- spin_unlock_irqrestore(&asrc->lock, lock_flags); +- + return ret; + } + +@@ -265,19 +262,16 @@ static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) + { + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; +- unsigned long lock_flags; + + /* Make sure the pair is disabled */ + regmap_update_bits(asrc->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + asrc->channel_avail += pair->channels; + asrc->pair[index] = NULL; + pair->error = 0; +- +- spin_unlock_irqrestore(&asrc->lock, lock_flags); + } + + /** +diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h +--- a/sound/soc/fsl/fsl_asrc.h ++++ b/sound/soc/fsl/fsl_asrc.h +@@ -444,7 +444,7 @@ struct dma_block { + }; + + /** +- * fsl_asrc_soc_data: soc specific data ++ * struct fsl_asrc_soc_data - soc specific data + * + * @use_edma: using edma as dma device or not + * @channel_bits: width of ASRCNCR register for each pair +@@ -457,7 +457,7 @@ struct fsl_asrc_soc_data { + }; + + /** +- * fsl_asrc_pair_priv: ASRC Pair private data ++ * struct fsl_asrc_pair_priv - ASRC Pair private data + * + * @config: configuration profile + */ +@@ -466,7 +466,7 @@ struct fsl_asrc_pair_priv { + }; + + /** +- * fsl_asrc_priv: ASRC private data ++ * struct fsl_asrc_priv - ASRC private data + * + * @asrck_clk: clock sources to driver ASRC internal logic + * @soc: soc specific data +diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c +--- a/sound/soc/fsl/fsl_asrc_dma.c ++++ b/sound/soc/fsl/fsl_asrc_dma.c +@@ -219,7 +219,9 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, + */ + component_be = snd_soc_lookup_component_nolocked(dev_be, SND_DMAENGINE_PCM_DRV_NAME); + if (component_be) { +- be_chan = soc_component_to_pcm(component_be)->chan[substream->stream]; ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component_be); ++ ++ be_chan = pcm->chan[substream->stream]; + tmp_chan = be_chan; + } + if (!tmp_chan) { +diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c +--- a/sound/soc/fsl/fsl_audmix.c ++++ b/sound/soc/fsl/fsl_audmix.c +@@ -286,7 +286,6 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) + { + struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai); +- unsigned long lock_flags; + + /* Capture stream shall not be handled */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) +@@ -296,16 +295,14 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms |= BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms |= BIT(dai->driver->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms &= ~BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms &= ~BIT(dai->driver->id); + break; + default: + return -EINVAL; +diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c +--- a/sound/soc/fsl/fsl_dma.c ++++ b/sound/soc/fsl/fsl_dma.c +@@ -18,8 +18,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + +@@ -824,9 +822,34 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np; + struct resource res; ++ void __iomem *channel; + const uint32_t *iprop; ++ int irq; + int ret; + ++ channel = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(channel)) ++ return PTR_ERR(channel); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ dma = devm_kzalloc(&pdev->dev, sizeof(*dma), GFP_KERNEL); ++ if (!dma) ++ return -ENOMEM; ++ ++ dma->dai.name = DRV_NAME; ++ dma->dai.open = fsl_dma_open; ++ dma->dai.close = fsl_dma_close; ++ dma->dai.hw_params = fsl_dma_hw_params; ++ dma->dai.hw_free = fsl_dma_hw_free; ++ dma->dai.pointer = fsl_dma_pointer; ++ dma->dai.pcm_new = fsl_dma_new; ++ ++ dma->channel = channel; ++ dma->irq = irq; ++ + /* Find the SSI node that points to us. */ + ssi_np = find_ssi_node(np); + if (!ssi_np) { +@@ -842,55 +865,19 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + return ret; + } + +- dma = kzalloc_obj(*dma); +- if (!dma) { +- of_node_put(ssi_np); +- return -ENOMEM; +- } +- +- dma->dai.name = DRV_NAME; +- dma->dai.open = fsl_dma_open; +- dma->dai.close = fsl_dma_close; +- dma->dai.hw_params = fsl_dma_hw_params; +- dma->dai.hw_free = fsl_dma_hw_free; +- dma->dai.pointer = fsl_dma_pointer; +- dma->dai.pcm_new = fsl_dma_new; +- + /* Store the SSI-specific information that we need */ + dma->ssi_stx_phys = res.start + REG_SSI_STX0; + dma->ssi_srx_phys = res.start + REG_SSI_SRX0; + + iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL); ++ of_node_put(ssi_np); + if (iprop) + dma->ssi_fifo_depth = be32_to_cpup(iprop); + else + /* Older 8610 DTs didn't have the fifo-depth property */ + dma->ssi_fifo_depth = 8; + +- of_node_put(ssi_np); +- +- ret = devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register platform\n"); +- kfree(dma); +- return ret; +- } +- +- dma->channel = of_iomap(np, 0); +- dma->irq = irq_of_parse_and_map(np, 0); +- +- dev_set_drvdata(&pdev->dev, dma); +- +- return 0; +-} +- +-static void fsl_soc_dma_remove(struct platform_device *pdev) +-{ +- struct dma_object *dma = dev_get_drvdata(&pdev->dev); +- +- iounmap(dma->channel); +- irq_dispose_mapping(dma->irq); +- kfree(dma); ++ return devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); + } + + static const struct of_device_id fsl_soc_dma_ids[] = { +@@ -905,7 +892,6 @@ static struct platform_driver fsl_soc_dma_driver = { + .of_match_table = fsl_soc_dma_ids, + }, + .probe = fsl_soc_dma_probe, +- .remove = fsl_soc_dma_remove, + }; + + module_platform_driver(fsl_soc_dma_driver); +diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h +--- a/sound/soc/fsl/fsl_dma.h ++++ b/sound/soc/fsl/fsl_dma.h +@@ -92,7 +92,7 @@ static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x) + #define CCSR_DMA_ATR_SNOOP 0x00050000 + #define CCSR_DMA_ATR_ESAD_MASK 0x0000000F + +-/** ++/* + * List Descriptor for extended chaining mode DMA operations. + * + * The CLSDAR register points to the first (in a linked-list) List +@@ -107,7 +107,7 @@ struct fsl_dma_list_descriptor { + u8 res[8]; /* Reserved */ + } __attribute__ ((aligned(32), packed)); + +-/** ++/* + * Link Descriptor for basic and extended chaining mode DMA operations. + * + * A Link Descriptor points to a single DMA buffer. Each link descriptor +diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c +--- a/sound/soc/fsl/fsl_easrc.c ++++ b/sound/soc/fsl/fsl_easrc.c +@@ -1025,7 +1025,6 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; + struct device *dev; +- unsigned long lock_flags; + int ret; + + if (!easrc) +@@ -1053,9 +1052,8 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + if (ret) + return ret; + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- ret = fsl_easrc_config_slot(easrc, ctx->index); +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ ret = fsl_easrc_config_slot(easrc, ctx->index); + if (ret) + return ret; + +@@ -1301,13 +1299,12 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *easrc = ctx->asrc; + struct device *dev; +- unsigned long lock_flags; + int ret = 0; + int i; + + dev = &easrc->pdev->dev; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->pair[i]) +@@ -1331,8 +1328,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + easrc->channel_avail -= channels; + } + +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- + return ret; + } + +@@ -1343,7 +1338,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + */ + static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + { +- unsigned long lock_flags; + struct fsl_asrc *easrc; + + if (!ctx) +@@ -1351,14 +1345,12 @@ static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + + easrc = ctx->asrc; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + fsl_easrc_release_slot(easrc, ctx->index); + + easrc->channel_avail += ctx->channels; + easrc->pair[ctx->index] = NULL; +- +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + } + + /* +@@ -2292,15 +2284,13 @@ static int fsl_easrc_runtime_suspend(struct device *dev) + { + struct fsl_asrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_priv *easrc_priv = easrc->private; +- unsigned long lock_flags; + + regcache_cache_only(easrc->regmap, true); + + clk_disable_unprepare(easrc->mem_clk); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- easrc_priv->firmware_loaded = 0; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ easrc_priv->firmware_loaded = 0; + + return 0; + } +@@ -2311,7 +2301,6 @@ static int fsl_easrc_runtime_resume(struct device *dev) + struct fsl_easrc_priv *easrc_priv = easrc->private; + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; +- unsigned long lock_flags; + int ret; + int i; + +@@ -2323,13 +2312,11 @@ static int fsl_easrc_runtime_resume(struct device *dev) + regcache_mark_dirty(easrc->regmap); + regcache_sync(easrc->regmap); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- if (easrc_priv->firmware_loaded) { +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- goto skip_load; ++ scoped_guard(spinlock_irqsave, &easrc->lock) { ++ if (easrc_priv->firmware_loaded) ++ return 0; ++ easrc_priv->firmware_loaded = 1; + } +- easrc_priv->firmware_loaded = 1; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + + ret = fsl_easrc_get_firmware(easrc); + if (ret) { +@@ -2377,9 +2364,7 @@ static int fsl_easrc_runtime_resume(struct device *dev) + goto disable_mem_clk; + } + +-skip_load: + return 0; +- + disable_mem_clk: + clk_disable_unprepare(easrc->mem_clk); + return ret; +diff --git a/sound/soc/fsl/fsl_easrc.h b/sound/soc/fsl/fsl_easrc.h +--- a/sound/soc/fsl/fsl_easrc.h ++++ b/sound/soc/fsl/fsl_easrc.h +@@ -584,7 +584,7 @@ struct fsl_easrc_slot { + }; + + /** +- * fsl_easrc_ctx_priv: EASRC context private data ++ * struct fsl_easrc_ctx_priv - EASRC context private data + * + * @in_params: input parameter + * @out_params: output parameter +@@ -625,7 +625,7 @@ struct fsl_easrc_ctx_priv { + }; + + /** +- * fsl_easrc_priv: EASRC private data ++ * struct fsl_easrc_priv - EASRC private data + * + * @slot: slot setting + * @firmware_hdr: the header of firmware +diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c +--- a/sound/soc/fsl/fsl_esai.c ++++ b/sound/soc/fsl/fsl_esai.c +@@ -709,10 +709,9 @@ static void fsl_esai_hw_reset(struct work_struct *work) + { + struct fsl_esai *esai_priv = container_of(work, struct fsl_esai, work); + bool tx = true, rx = false, enabled[2]; +- unsigned long lock_flags; + u32 tfcr, rfcr; + +- spin_lock_irqsave(&esai_priv->lock, lock_flags); ++ guard(spinlock_irqsave)(&esai_priv->lock); + /* Save the registers */ + regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr); + regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr); +@@ -750,8 +749,6 @@ static void fsl_esai_hw_reset(struct work_struct *work) + fsl_esai_trigger_start(esai_priv, tx); + if (enabled[rx]) + fsl_esai_trigger_start(esai_priv, rx); +- +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); + } + + static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, +@@ -759,7 +756,6 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + + esai_priv->channels[tx] = substream->runtime->channels; + +@@ -767,16 +763,14 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_start(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_start(esai_priv, tx); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_stop(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_stop(esai_priv, tx); + break; + default: + return -EINVAL; +diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c +--- a/sound/soc/fsl/fsl_spdif.c ++++ b/sound/soc/fsl/fsl_spdif.c +@@ -853,17 +853,15 @@ static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; + memcpy(&ucontrol->value.iec958.subcode[0], + &ctrl->subcode[idx], SPDIF_UBITS_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +@@ -885,17 +883,15 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; + memcpy(&ucontrol->value.bytes.data[0], + &ctrl->qsub[idx], SPDIF_QSUB_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c +--- a/sound/soc/fsl/fsl_ssi.c ++++ b/sound/soc/fsl/fsl_ssi.c +@@ -1218,13 +1218,13 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + if (reg > 0x7f) + return; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 write clk_prepare_enable failed: %d\n", + ret); +- goto ret_unlock; ++ return; + } + + lreg = reg << 12; +@@ -1238,9 +1238,6 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + udelay(100); + + clk_disable_unprepare(fsl_ac97_data->clk); +- +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + } + + static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, +@@ -1252,12 +1249,12 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned int lreg; + int ret; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 read clk_prepare_enable failed: %d\n", ret); +- goto ret_unlock; ++ return val; + } + + lreg = (reg & 0x7f) << 12; +@@ -1272,8 +1269,6 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + + clk_disable_unprepare(fsl_ac97_data->clk); + +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + return val; + } + +diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c +--- a/sound/soc/fsl/fsl_xcvr.c ++++ b/sound/soc/fsl/fsl_xcvr.c +@@ -797,10 +797,9 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + int ret = 0; + +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -812,7 +811,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DPTH_RESET(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to set DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -824,7 +823,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, "err updating isr %d\n", ret); +- goto release_lock; ++ return ret; + } + fallthrough; + case FSL_XCVR_MODE_SPDIF: +@@ -833,7 +832,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -844,14 +843,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL); + if (ret < 0) { + dev_err(dai->dev, "Error while setting IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + /* clear DPATH RESET */ +@@ -860,7 +859,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + break; +@@ -873,14 +872,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to disable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -891,7 +890,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + if (xcvr->soc_data->spdif_only) + break; +@@ -905,7 +904,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + if (ret < 0) { + dev_err(dai->dev, + "Err updating ISR %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -916,18 +915,16 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + break; + } + +-release_lock: +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + return ret; + } + + static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + { + struct device *dev = &xcvr->pdev->dev; +- const struct firmware *fw; + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET; + u32 mask, val; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev); + if (ret) { + dev_err(dev, "failed to request firmware.\n"); +@@ -939,7 +936,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */ + if (rem > 16384) { + dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem); +- release_firmware(fw); + return -ENOMEM; + } + +@@ -950,7 +946,7 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + if (ret < 0) { + dev_err(dev, "FW: failed to set page %d, err=%d\n", + page, ret); +- goto err_firmware; ++ return ret; + } + + off = page * size; +@@ -971,11 +967,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + } + } + +-err_firmware: +- release_firmware(fw); +- if (ret < 0) +- return ret; +- + /* configure watermarks */ + mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK; + val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX); +@@ -1448,11 +1439,10 @@ static void reset_rx_work(struct work_struct *work) + { + struct fsl_xcvr *xcvr = container_of(work, struct fsl_xcvr, work_rst); + struct device *dev = &xcvr->pdev->dev; +- unsigned long lock_flags; + u32 ext_ctrl; + + dev_dbg(dev, "reset rx path\n"); +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + regmap_read(xcvr->regmap, FSL_XCVR_EXT_CTRL, &ext_ctrl); + + if (!(ext_ctrl & FSL_XCVR_EXT_CTRL_DMA_RD_DIS)) { +@@ -1469,7 +1459,6 @@ static void reset_rx_work(struct work_struct *work) + FSL_XCVR_EXT_CTRL_RX_DPTH_RESET, + 0); + } +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + } + + static irqreturn_t irq0_isr(int irq, void *devid) +diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c +--- a/sound/soc/fsl/imx-audio-rpmsg.c ++++ b/sound/soc/fsl/imx-audio-rpmsg.c +@@ -22,7 +22,6 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data; + struct rpmsg_info *info; + struct rpmsg_msg *msg; +- unsigned long flags; + + if (!rpmsg->rpmsg_pdev) + return 0; +@@ -37,21 +36,21 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + /* TYPE C is notification from M core */ + switch (r_msg->header.cmd) { + case TX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[TX], flags); +- msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[TX]; +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[TX]; ++ } + info->callback[TX](info->callback_param[TX]); + break; + case RX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[RX], flags); +- msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[1]; +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[1]; ++ } + info->callback[RX](info->callback_param[RX]); + break; + default: +diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c +--- a/sound/soc/fsl/imx-pcm-rpmsg.c ++++ b/sound/soc/fsl/imx-pcm-rpmsg.c +@@ -39,10 +39,9 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + struct rpmsg_device *rpdev = info->rpdev; + int ret = 0; + +- mutex_lock(&info->msg_lock); ++ guard(mutex)(&info->msg_lock); + if (!rpdev) { + dev_err(info->dev, "rpmsg channel not ready\n"); +- mutex_unlock(&info->msg_lock); + return -EINVAL; + } + +@@ -55,15 +54,12 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + sizeof(struct rpmsg_s_msg)); + if (ret) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); +- mutex_unlock(&info->msg_lock); + return ret; + } + + /* No receive msg for TYPE_C command */ +- if (msg->s_msg.header.type == MSG_TYPE_C) { +- mutex_unlock(&info->msg_lock); ++ if (msg->s_msg.header.type == MSG_TYPE_C) + return 0; +- } + + /* wait response from rpmsg */ + ret = wait_for_completion_timeout(&info->cmd_complete, +@@ -71,7 +67,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + if (!ret) { + dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n", + msg->s_msg.header.cmd); +- mutex_unlock(&info->msg_lock); + return -ETIMEDOUT; + } + +@@ -100,8 +95,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd, + info->r_msg.param.resp); + +- mutex_unlock(&info->msg_lock); +- + return 0; + } + +@@ -109,14 +102,13 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + struct rpmsg_msg *msg, + struct rpmsg_info *info) + { +- unsigned long flags; + int ret = 0; + + /* + * Queue the work to workqueue. + * If the queue is full, drop the message. + */ +- spin_lock_irqsave(&info->wq_lock, flags); ++ guard(spinlock_irqsave)(&info->wq_lock); + if (info->work_write_index != info->work_read_index) { + int index = info->work_write_index; + +@@ -130,7 +122,6 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + info->msg_drop_count[substream->stream]++; + ret = -EPIPE; + } +- spin_unlock_irqrestore(&info->wq_lock, flags); + + return ret; + } +@@ -523,7 +514,6 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + snd_pcm_sframes_t avail; + struct timer_list *timer; + struct rpmsg_msg *msg; +- unsigned long flags; + int buffer_tail = 0; + int written_num; + +@@ -553,11 +543,11 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + msg->s_msg.param.buffer_tail = buffer_tail; + + /* The notification message is updated to latest */ +- spin_lock_irqsave(&info->lock[substream->stream], flags); +- memcpy(&info->notify[substream->stream], msg, +- sizeof(struct rpmsg_s_msg)); +- info->notify_updated[substream->stream] = true; +- spin_unlock_irqrestore(&info->lock[substream->stream], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[substream->stream]) { ++ memcpy(&info->notify[substream->stream], msg, ++ sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[substream->stream] = true; ++ } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + avail = snd_pcm_playback_hw_avail(runtime); +@@ -641,7 +631,7 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + bool is_notification = false; + struct rpmsg_info *info; + struct rpmsg_msg msg; +- unsigned long flags; ++ bool updated; + + work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); + info = work_of_rpmsg->info; +@@ -652,25 +642,26 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + * enough data in M core side, need to let M core know + * data is updated immediately. + */ +- spin_lock_irqsave(&info->lock[TX], flags); +- if (info->notify_updated[TX]) { +- memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[TX] = false; +- spin_unlock_irqrestore(&info->lock[TX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ updated = info->notify_updated[TX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[TX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); + +- spin_lock_irqsave(&info->lock[RX], flags); +- if (info->notify_updated[RX]) { +- memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[RX] = false; +- spin_unlock_irqrestore(&info->lock[RX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ updated = info->notify_updated[RX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[RX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); ++ + + /* Skip the notification message for it has been processed above */ + if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C && +@@ -682,10 +673,10 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + info->send_message(&work_of_rpmsg->msg, info); + + /* update read index */ +- spin_lock_irqsave(&info->wq_lock, flags); +- info->work_read_index++; +- info->work_read_index %= WORK_MAX_NUM; +- spin_unlock_irqrestore(&info->wq_lock, flags); ++ scoped_guard(spinlock_irqsave, &info->wq_lock) { ++ info->work_read_index++; ++ info->work_read_index %= WORK_MAX_NUM; ++ } + } + + static int imx_rpmsg_pcm_probe(struct platform_device *pdev) +@@ -819,9 +810,9 @@ static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = { + }; + + static const struct platform_device_id imx_rpmsg_pcm_id_table[] = { +- { .name = "rpmsg-audio-channel" }, +- { .name = "rpmsg-micfil-channel" }, +- { }, ++ { .name = "rpmsg-audio-channel" }, ++ { .name = "rpmsg-micfil-channel" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, imx_rpmsg_pcm_id_table); + +diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c +--- a/sound/soc/fsl/mpc5200_dma.c ++++ b/sound/soc/fsl/mpc5200_dma.c +@@ -77,18 +77,20 @@ static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) + { + struct psc_dma_stream *s = _psc_dma_stream; + +- spin_lock(&s->psc_dma->lock); +- /* For each finished period, dequeue the completed period buffer +- * and enqueue a new one in it's place. */ +- while (bcom_buffer_done(s->bcom_task)) { +- bcom_retrieve_buffer(s->bcom_task, NULL, NULL); ++ scoped_guard(spinlock, &s->psc_dma->lock) { ++ /* ++ * For each finished period, dequeue the completed period buffer ++ * and enqueue a new one in its place ++ */ ++ while (bcom_buffer_done(s->bcom_task)) { ++ bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + +- s->period_current = (s->period_current+1) % s->runtime->periods; +- s->period_count++; ++ s->period_current = (s->period_current+1) % s->runtime->periods; ++ s->period_count++; + +- psc_dma_bcom_enqueue_next_buffer(s); ++ psc_dma_bcom_enqueue_next_buffer(s); ++ } + } +- spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ +@@ -116,7 +118,6 @@ static int psc_dma_trigger(struct snd_soc_component *component, + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 imr; +- unsigned long flags; + int i; + + switch (cmd) { +@@ -135,19 +136,18 @@ static int psc_dma_trigger(struct snd_soc_component *component, + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ +- spin_lock_irqsave(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); + +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); ++ for (i = 0; i < runtime->periods; i++) ++ if (!bcom_queue_full(s->bcom_task)) ++ psc_dma_bcom_enqueue_next_buffer(s); + +- for (i = 0; i < runtime->periods; i++) +- if (!bcom_queue_full(s->bcom_task)) +- psc_dma_bcom_enqueue_next_buffer(s); +- +- bcom_enable(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ bcom_enable(s->bcom_task); ++ } + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + +@@ -158,13 +158,13 @@ static int psc_dma_trigger(struct snd_soc_component *component, + substream->pstr->stream, s->period_count); + s->active = 0; + +- spin_lock_irqsave(&psc_dma->lock, flags); +- bcom_disable(s->bcom_task); +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ bcom_disable(s->bcom_task); ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); ++ } + + break; + +@@ -314,35 +314,29 @@ int mpc5200_audio_dma_create(struct platform_device *op) + { + phys_addr_t fifo; + struct psc_dma *psc_dma; +- struct resource res; ++ struct resource *res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; +- int ret; ++ ++ regs = devm_platform_get_and_ioremap_resource(op, 0, &res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + /* Fetch the registers and IRQ of the PSC */ +- irq = irq_of_parse_and_map(op->dev.of_node, 0); +- if (of_address_to_resource(op->dev.of_node, 0, &res)) { +- dev_err(&op->dev, "Missing reg property\n"); +- return -ENODEV; +- } +- regs = devm_ioremap(&op->dev, res.start, resource_size(&res)); +- if (!regs) { +- dev_err(&op->dev, "Could not map registers\n"); +- return -ENODEV; +- } ++ irq = platform_get_irq(op, 0); ++ if (irq < 0) ++ return irq; + + /* Allocate and initialize the driver private data */ +- psc_dma = kzalloc_obj(*psc_dma); ++ psc_dma = devm_kzalloc(&op->dev, sizeof(*psc_dma), GFP_KERNEL); + if (!psc_dma) + return -ENOMEM; + + /* Get the PSC ID */ + prop = of_get_property(op->dev.of_node, "cell-index", &size); +- if (!prop || size < sizeof *prop) { +- ret = -ENODEV; +- goto out_free; +- } ++ if (!prop || size < sizeof *prop) ++ return -ENODEV; + + spin_lock_init(&psc_dma->lock); + mutex_init(&psc_dma->mutex); +@@ -357,7 +351,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ +- fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); ++ fifo = res->start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = +@@ -365,8 +359,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); +- ret = -ENODEV; +- goto out_free; ++ return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ +@@ -399,16 +392,14 @@ int mpc5200_audio_dma_create(struct platform_device *op) + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + +- rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, ++ rc = devm_request_irq(&op->dev, psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); +- rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); +- rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); +- if (rc) { +- ret = -ENODEV; +- goto out_irq; +- } ++ if (rc) ++ return -ENODEV; + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); +@@ -416,13 +407,6 @@ int mpc5200_audio_dma_create(struct platform_device *op) + /* Tell the ASoC OF helpers about it */ + return devm_snd_soc_register_component(&op->dev, + &mpc5200_audio_dma_component, NULL, 0); +-out_irq: +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +-out_free: +- kfree(psc_dma); +- return ret; + } + EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +@@ -435,12 +419,6 @@ int mpc5200_audio_dma_destroy(struct platform_device *op) + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + +- /* Release irqs */ +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +- +- kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c +--- a/sound/soc/fsl/mpc5200_psc_ac97.c ++++ b/sound/soc/fsl/mpc5200_psc_ac97.c +@@ -30,14 +30,13 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + int status; + unsigned int val; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command send status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + +@@ -53,19 +52,16 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + if (status == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + +- mutex_unlock(&psc_dma->mutex); + return (unsigned short) val; + } + +@@ -74,52 +70,46 @@ static void psc_ac97_write(struct snd_ac97 *ac97, + { + int status; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (write)\n"); +- goto out; ++ return; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); +- +- out: +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_warm_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + udelay(3); + out_be32(®s->sicr, psc_dma->sicr); +- +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_cold_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); +- dev_dbg(psc_dma->dev, "cold reset\n"); ++ scoped_guard(mutex, &psc_dma->mutex) { ++ dev_dbg(psc_dma->dev, "cold reset\n"); + +- mpc5200_psc_ac97_gpio_reset(psc_dma->id); ++ mpc5200_psc_ac97_gpio_reset(psc_dma->id); + +- /* Notify the PSC that a reset has occurred */ +- out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); ++ /* Notify the PSC that a reset has occurred */ ++ out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); + +- /* Re-enable RX and TX */ +- out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); +- +- mutex_unlock(&psc_dma->mutex); ++ /* Re-enable RX and TX */ ++ out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); ++ } + + usleep_range(1000, 2000); + psc_ac97_warm_reset(ac97); +@@ -286,7 +276,7 @@ static int psc_ac97_of_probe(struct platform_device *op) + return rc; + } + +- rc = snd_soc_register_component(&op->dev, &psc_ac97_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_ac97_component, + psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); +@@ -312,8 +302,6 @@ static int psc_ac97_of_probe(struct platform_device *op) + static void psc_ac97_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); +- snd_soc_set_ac97_ops(NULL); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c +--- a/sound/soc/fsl/mpc5200_psc_i2s.c ++++ b/sound/soc/fsl/mpc5200_psc_i2s.c +@@ -79,9 +79,12 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * ++ * @cpu_dai: DAI runtime data pointer + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +@@ -101,7 +104,10 @@ static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * ++ * @cpu_dai: DAI runtime data pointer + * @format: one of SND_SOC_DAIFMT_xxx ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + { +@@ -119,7 +125,7 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + */ + + /** +- * psc_i2s_dai_template: template CPU Digital Audio Interface ++ * var psc_i2s_dai_ops - template CPU Digital Audio Interface + */ + static const struct snd_soc_dai_ops psc_i2s_dai_ops = { + .hw_params = psc_i2s_hw_params, +@@ -166,7 +172,7 @@ static int psc_i2s_of_probe(struct platform_device *op) + if (rc != 0) + return rc; + +- rc = snd_soc_register_component(&op->dev, &psc_i2s_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_i2s_component, + psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); +@@ -213,7 +219,6 @@ static int psc_i2s_of_probe(struct platform_device *op) + static void psc_i2s_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c +--- a/sound/soc/fsl/p1022_ds.c ++++ b/sound/soc/fsl/p1022_ds.c +@@ -73,6 +73,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_ds_machine_probe: initialize the board + * +@@ -82,8 +84,7 @@ struct machine_data { + */ + static int p1022_ds_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -122,8 +123,7 @@ static int p1022_ds_machine_probe(struct snd_soc_card *card) + static int p1022_ds_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -156,8 +156,7 @@ static int p1022_ds_startup(struct snd_pcm_substream *substream) + */ + static int p1022_ds_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -399,8 +398,7 @@ static int p1022_ds_probe(struct platform_device *pdev) + static void p1022_ds_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c +--- a/sound/soc/fsl/p1022_rdk.c ++++ b/sound/soc/fsl/p1022_rdk.c +@@ -79,6 +79,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_rdk_machine_probe - initialize the board + * @card: ASoC card instance +@@ -91,8 +93,7 @@ struct machine_data { + */ + static int p1022_rdk_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -134,8 +135,7 @@ static int p1022_rdk_machine_probe(struct snd_soc_card *card) + static int p1022_rdk_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -169,8 +169,7 @@ static int p1022_rdk_startup(struct snd_pcm_substream *substream) + */ + static int p1022_rdk_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -361,8 +360,7 @@ static int p1022_rdk_probe(struct platform_device *pdev) + static void p1022_rdk_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c +--- a/sound/soc/generic/audio-graph-card.c ++++ b/sound/soc/generic/audio-graph-card.c +@@ -579,11 +579,11 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) + goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); + if (ret < 0) + goto end; + +@@ -594,6 +594,7 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -603,14 +604,13 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph_parse_of); + +diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c +--- a/sound/soc/generic/audio-graph-card2.c ++++ b/sound/soc/generic/audio-graph-card2.c +@@ -1303,11 +1303,11 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + struct graph2_custom_hooks *hooks) + { + struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; ++ int ret = -ENOMEM; + + struct link_info *li __free(kfree) = kzalloc_obj(*li); + if (!li) +- return -ENOMEM; ++ goto end; + + card->probe = graph_util_card_probe; + card->owner = THIS_MODULE; +@@ -1316,31 +1316,35 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if ((hooks) && (hooks)->hook_pre) { + ret = (hooks)->hook_pre(priv); + if (ret < 0) +- goto err; ++ goto end; + } + + ret = graph_for_each_link(priv, hooks, li, graph_count); + if (!li->link) + ret = -EINVAL; + if (ret < 0) +- goto err; ++ goto end; + + ret = simple_util_init_priv(priv, li); + if (ret < 0) +- goto err; ++ goto end; + + priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); + if (IS_ERR(priv->pa_gpio)) { + ret = PTR_ERR(priv->pa_gpio); + dev_err(dev, "failed to get amplifier gpio: %d\n", ret); +- goto err; ++ goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) +- goto err; ++ goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, NULL); + if (ret < 0) + goto err; + +@@ -1349,6 +1353,7 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -1363,15 +1368,13 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + + simple_util_debug_info(priv); + +- ret = snd_soc_of_parse_aux_devs(card, "aux-devs"); +- if (ret < 0) +- goto err; +- + ret = devm_snd_soc_register_card(dev, card); + err: +- if (ret < 0) +- dev_err_probe(dev, ret, "parse error\n"); +- ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } ++end: + return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph2_parse_of); +diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c +--- a/sound/soc/generic/simple-card-utils.c ++++ b/sound/soc/generic/simple-card-utils.c +@@ -216,6 +216,27 @@ int simple_util_set_dailink_name(struct simple_util_priv *priv, + } + EXPORT_SYMBOL_GPL(simple_util_set_dailink_name); + ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property) ++{ ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device_node *node = card->dev->of_node; ++ char prop[128]; ++ ++ if (!prefix) ++ prefix = ""; ++ ++ snprintf(prop, sizeof(prop), "%s%s", prefix, property); ++ ++ /* no property is not error */ ++ if (!of_property_present(node, prop)) ++ return 0; ++ ++ return func(card, prop); ++} ++EXPORT_SYMBOL_GPL(simple_util_parse_property); ++ + int simple_util_parse_card_name(struct simple_util_priv *priv, + char *prefix) + { +@@ -731,11 +752,12 @@ void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + } + EXPORT_SYMBOL_GPL(simple_util_canonicalize_cpu); + +-void simple_util_clean_reference(struct snd_soc_card *card) ++void simple_util_clean_reference(struct simple_util_priv *priv) + { + struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link_component *cpu; + struct snd_soc_dai_link_component *codec; ++ struct snd_soc_card *card = simple_priv_to_card(priv); + int i, j; + + for_each_card_prelinks(card, i, dai_link) { +@@ -747,57 +769,6 @@ void simple_util_clean_reference(struct snd_soc_card *card) + } + EXPORT_SYMBOL_GPL(simple_util_clean_reference); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "routing"); +- +- if (!of_property_present(node, prop)) +- return 0; +- +- return snd_soc_of_parse_audio_routing(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_routing); +- +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); +- +- if (of_property_present(node, prop)) +- return snd_soc_of_parse_audio_simple_widgets(card, prop); +- +- /* no widgets is not error */ +- return 0; +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_widgets); +- +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix) +-{ +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches"); +- +- return snd_soc_of_parse_pin_switches(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_pin_switches); +- + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, +@@ -854,9 +825,9 @@ int simple_util_init_jack(struct snd_soc_card *card, + } + EXPORT_SYMBOL_GPL(simple_util_init_jack); + +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, char *prefix) ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix) + { +- struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + int found_jack_index = 0; + int type = 0; +@@ -1026,8 +997,9 @@ EXPORT_SYMBOL_GPL(simple_util_init_priv); + void simple_util_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + +- simple_util_clean_reference(card); ++ simple_util_clean_reference(priv); + } + EXPORT_SYMBOL_GPL(simple_util_remove); + +diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c +--- a/sound/soc/generic/simple-card.c ++++ b/sound/soc/generic/simple-card.c +@@ -517,44 +517,6 @@ static int simple_populate_aux(struct simple_util_priv *priv) + return simple_ret(priv, ret); + } + +-static int simple_parse_of(struct simple_util_priv *priv, struct link_info *li) +-{ +- struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; +- +- ret = simple_util_parse_widgets(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_routing(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_pin_switches(card, PREFIX); +- if (ret < 0) +- goto end; +- +- /* Single/Muti DAI link(s) & New style of DT node */ +- memset(li, 0, sizeof(*li)); +- ret = simple_for_each_link(priv, li, +- simple_dai_link_of, +- simple_dai_link_of_dpcm); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_card_name(priv, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_populate_aux(priv); +- if (ret < 0) +- goto end; +- +- ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs"); +-end: +- return simple_ret(priv, ret); +-} +- + static int simple_count_noml(struct simple_util_priv *priv, + struct device_node *np, + struct device_node *codec, +@@ -699,28 +661,19 @@ static int simple_soc_probe(struct snd_soc_card *card) + if (ret < 0) + goto end; + +- ret = simple_util_init_aux_jacks(priv, PREFIX); ++ ret = simple_util_init_aux_jacks(card, PREFIX); + end: + return simple_ret(priv, ret); + } + +-static int simple_probe(struct platform_device *pdev) ++static int simple_parse_of(struct simple_util_priv *priv) + { +- struct simple_util_priv *priv; +- struct device *dev = &pdev->dev; +- struct snd_soc_card *card; +- int ret; ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device *dev = card->dev; ++ int ret = -EINVAL; + +- /* Allocate the private data and the DAI link array */ +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- card = simple_priv_to_card(priv); +- card->owner = THIS_MODULE; +- card->dev = dev; +- card->probe = simple_soc_probe; +- card->driver_name = "simple-card"; ++ if (!dev) ++ return simple_ret(priv, ret); + + ret = -ENOMEM; + struct link_info *li __free(kfree) = kzalloc_obj(*li); +@@ -739,25 +692,71 @@ static int simple_probe(struct platform_device *pdev) + if (ret < 0) + goto end; + +- ret = simple_parse_of(priv, li); +- if (ret < 0) { +- dev_err_probe(dev, ret, "parse error\n"); ++ ret = simple_util_parse_widgets(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_routing(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_pin_switches(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ /* Single/Muti DAI link(s) & New style of DT node */ ++ memset(li, 0, sizeof(*li)); ++ ret = simple_for_each_link(priv, li, ++ simple_dai_link_of, ++ simple_dai_link_of_dpcm); ++ if (ret < 0) ++ goto err; ++ ++ /* Card name should be set after simple_for_each_link() */ ++ ret = simple_util_parse_card_name(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ ret = simple_populate_aux(priv); ++ if (ret < 0) + goto err; +- } + + snd_soc_card_set_drvdata(card, priv); + + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return simple_ret(priv, ret); ++} ++ ++static int simple_probe(struct platform_device *pdev) ++{ ++ struct simple_util_priv *priv; ++ struct device *dev = &pdev->dev; ++ struct snd_soc_card *card; ++ ++ /* Allocate the private data and the DAI link array */ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ card = simple_priv_to_card(priv); ++ card->owner = THIS_MODULE; ++ card->dev = dev; ++ card->probe = simple_soc_probe; ++ card->driver_name = "simple-card"; ++ ++ return simple_parse_of(priv); + } + + static const struct of_device_id simple_of_match[] = { +diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c +--- a/sound/soc/intel/atom/sst-atom-controls.c ++++ b/sound/soc/intel/atom/sst-atom-controls.c +@@ -14,6 +14,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -73,14 +74,10 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) + { +- int ret; ++ guard(mutex)(&drv->lock); + +- mutex_lock(&drv->lock); +- ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, +- task_id, pipe_id, cmd_data, len); +- mutex_unlock(&drv->lock); +- +- return ret; ++ return sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, ++ task_id, pipe_id, cmd_data, len); + } + + /* +@@ -167,7 +164,7 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + unsigned int val, mux; + u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + val = 1 << ctl_no; + /* search which slot/channel has this bit set - there should be only one */ + for (mux = e->max; mux > 0; mux--) +@@ -175,7 +172,6 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + break; + + ucontrol->value.enumerated.item[0] = mux; +- mutex_unlock(&drv->lock); + + dev_dbg(c->dev, "%s - %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", +@@ -235,7 +231,7 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + if (mux > e->max - 1) + return -EINVAL; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + /* first clear all registers of this bit */ + for (i = 0; i < e->max; i++) + map[i] &= ~val; +@@ -244,7 +240,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + /* kctl set to 'none' and we reset the bits so send IPC */ + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -258,7 +253,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -354,13 +348,12 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name); +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(bc->params, ucontrol->value.bytes.data, bc->max); + break; + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; +@@ -368,7 +361,6 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + /*if pipe is enabled, need to send the algo params from here*/ + if (bc->w && bc->w->power) + ret = sst_send_algo_cmd(drv, bc); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -475,7 +467,7 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + + switch (mc->type) { + case SST_GAIN_TLV: +@@ -497,7 +489,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + break; + + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; +@@ -506,7 +497,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + if (mc->w && mc->w->power) + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, 0); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -521,10 +511,9 @@ static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + sst_find_and_send_pipe_algo(drv, w->name, ids); + sst_set_pipe_gain(ids, drv, 0); +- mutex_unlock(&drv->lock); + + return 0; + } +@@ -761,27 +750,29 @@ int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) + return ret; + } + +- mutex_lock(&drv->lock); +- if (enable) +- timer_usage++; +- else +- timer_usage--; +- +- /* +- * Send the command only if this call is the first enable or last +- * disable +- */ +- if ((enable && (timer_usage == 1)) || +- (!enable && (timer_usage == 0))) { +- ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, +- SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, +- sizeof(cmd.header) + cmd.header.length); +- if (ret && enable) { ++ scoped_guard(mutex, &drv->lock) { ++ if (enable) ++ timer_usage++; ++ else + timer_usage--; +- enable = false; ++ ++ /* ++ * Send the command only if this call is the first enable or last ++ * disable ++ */ ++ if ((enable && timer_usage == 1) || ++ (!enable && timer_usage == 0)) { ++ ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, ++ SST_FLAG_BLOCKED, ++ SST_TASK_SBA, 0, &cmd, ++ sizeof(cmd.header) + ++ cmd.header.length); ++ if (ret && enable) { ++ timer_usage--; ++ enable = false; ++ } + } + } +- mutex_unlock(&drv->lock); + + if (!enable) + sst->ops->power(sst->dev, false); +diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +--- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c ++++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +@@ -11,6 +11,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -32,7 +33,7 @@ int sst_register_dsp(struct sst_device *dev) + return -EINVAL; + if (!try_module_get(dev->dev->driver->owner)) + return -ENODEV; +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + if (sst) { + dev_err(dev->dev, "we already have a device %s\n", sst->name); + module_put(dev->dev->driver->owner); +@@ -41,7 +42,7 @@ int sst_register_dsp(struct sst_device *dev) + } + dev_dbg(dev->dev, "registering device %s\n", dev->name); + sst = dev; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_register_dsp); +@@ -53,17 +54,15 @@ int sst_unregister_dsp(struct sst_device *dev) + if (dev != sst) + return -EINVAL; + +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + +- if (!sst) { +- mutex_unlock(&sst_lock); ++ if (!sst) + return -EIO; +- } + + module_put(sst->dev->driver->owner); + dev_dbg(dev->dev, "unreg %s\n", sst->name); + sst = NULL; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_unregister_dsp); +@@ -103,21 +102,14 @@ static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) + void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) + { +- unsigned long flags; +- spin_lock_irqsave(&stream->status_lock, flags); ++ guard(spinlock_irqsave)(&stream->status_lock); + stream->stream_status = state; +- spin_unlock_irqrestore(&stream->status_lock, flags); + } + + static inline int sst_get_stream_status(struct sst_runtime_stream *stream) + { +- int state; +- unsigned long flags; +- +- spin_lock_irqsave(&stream->status_lock, flags); +- state = stream->stream_status; +- spin_unlock_irqrestore(&stream->status_lock, flags); +- return state; ++ guard(spinlock_irqsave)(&stream->status_lock); ++ return stream->stream_status; + } + + static void sst_fill_alloc_params(struct snd_pcm_substream *substream, +@@ -244,12 +236,7 @@ static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, + + stream->stream_info.str_id = str_params.stream_id; + +- ret_val = stream->ops->open(sst->dev, &str_params); +- if (ret_val <= 0) +- return ret_val; +- +- +- return ret_val; ++ return stream->ops->open(sst->dev, &str_params); + } + + static void sst_period_elapsed(void *arg) +@@ -304,7 +291,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + { + int ret_val = 0; + struct snd_pcm_runtime *runtime = substream->runtime; +- struct sst_runtime_stream *stream; ++ struct sst_runtime_stream *stream __free(kfree) = NULL; + + stream = kzalloc_obj(*stream); + if (!stream) +@@ -312,15 +299,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ +- mutex_lock(&sst_lock); +- if (!sst || +- !try_module_get(sst->dev->driver->owner)) { +- dev_err(dai->dev, "no device available to run\n"); +- ret_val = -ENODEV; +- goto out_ops; ++ scoped_guard(mutex, &sst_lock) { ++ if (!sst || ++ !try_module_get(sst->dev->driver->owner)) { ++ dev_err(dai->dev, "no device available to run\n"); ++ return -ENODEV; ++ } ++ stream->ops = sst->ops; + } +- stream->ops = sst->ops; +- mutex_unlock(&sst_lock); + + stream->stream_info.str_id = 0; + +@@ -330,7 +316,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + + ret_val = power_up_sst(stream); + if (ret_val < 0) +- goto out_power_up; ++ return ret_val; + + /* + * Make sure the period to be multiple of 1ms to align the +@@ -347,12 +333,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + +- return snd_pcm_hw_constraint_integer(runtime, +- SNDRV_PCM_HW_PARAM_PERIODS); +-out_ops: +- mutex_unlock(&sst_lock); +-out_power_up: +- kfree(stream); ++ ret_val = snd_pcm_hw_constraint_integer(runtime, ++ SNDRV_PCM_HW_PARAM_PERIODS); ++ ++ if (ret_val < 0) ++ return ret_val; ++ ++ stream = NULL; ++ + return ret_val; + } + +diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c +--- a/sound/soc/intel/atom/sst/sst_ipc.c ++++ b/sound/soc/intel/atom/sst/sst_ipc.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -180,9 +181,8 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; +- unsigned long irq_flags; + +- spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); ++ guard(spinlock_irqsave)(&sst_drv_ctx->ipc_spin_lock); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + +@@ -200,7 +200,6 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); +- spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + } + + +diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c +--- a/sound/soc/intel/atom/sst/sst_pvt.c ++++ b/sound/soc/intel/atom/sst/sst_pvt.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -64,9 +65,8 @@ u64 sst_shim_read64(void __iomem *addr, int offset) + void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state) + { +- mutex_lock(&sst_drv_ctx->sst_lock); ++ guard(mutex)(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; +- mutex_unlock(&sst_drv_ctx->sst_lock); + } + + /* +@@ -302,18 +302,17 @@ int sst_assign_pvt_id(struct intel_sst_drv *drv) + { + int local; + +- spin_lock(&drv->block_lock); ++ guard(spinlock)(&drv->block_lock); + /* find first zero index from lsb */ + local = ffz(drv->pvt_id); + dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); + if (local >= SST_MAX_BLOCKS){ +- spin_unlock(&drv->block_lock); + dev_err(drv->dev, "PVT _ID error: no free id blocks "); + return -EINVAL; + } + /* toggle the index */ + change_bit(local, &drv->pvt_id); +- spin_unlock(&drv->block_lock); ++ + return local; + } + +diff --git a/sound/soc/intel/avs/apl.c b/sound/soc/intel/avs/apl.c +--- a/sound/soc/intel/avs/apl.c ++++ b/sound/soc/intel/avs/apl.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -190,7 +191,7 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + { + struct avs_path *path; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Any gateway without buffer allocated in LP area disqualifies D0IX. */ + list_for_each_entry(path, &adev->path_list, node) { + struct avs_path_pipeline *ppl; +@@ -210,14 +211,11 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + if (cfg->copier.dma_type == INVALID_OBJECT_ID) + continue; + +- if (!mod->gtw_attrs.lp_buffer_alloc) { +- spin_unlock(&adev->path_list_lock); ++ if (!mod->gtw_attrs.lp_buffer_alloc) + return false; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return true; + } +diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h +--- a/sound/soc/intel/avs/avs.h ++++ b/sound/soc/intel/avs/avs.h +@@ -336,14 +336,13 @@ int avs_icl_load_basefw(struct avs_dev *adev, struct firmware *fw); + /* Soc component members */ + + struct avs_soc_component { +- struct snd_soc_component base; ++ struct snd_soc_component *base; + struct avs_tplg *tplg; + + struct list_head node; + }; + +-#define to_avs_soc_component(comp) \ +- container_of(comp, struct avs_soc_component, base) ++#define to_avs_soc_component(comp) snd_soc_component_to_priv(comp) + + extern const struct snd_soc_dai_ops avs_dai_fe_ops; + +diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c +--- a/sound/soc/intel/avs/boards/da7219.c ++++ b/sound/soc/intel/avs/boards/da7219.c +@@ -175,7 +175,6 @@ static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + if (!dl || !platform) + return -ENOMEM; + +- dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +@@ -259,10 +258,8 @@ static int avs_da7219_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_da7219_driver_ids[] = { +- { +- .name = "avs_da7219", +- }, +- {}, ++ { .name = "avs_da7219" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_da7219_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c +--- a/sound/soc/intel/avs/boards/dmic.c ++++ b/sound/soc/intel/avs/boards/dmic.c +@@ -104,10 +104,8 @@ static int avs_dmic_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_dmic_driver_ids[] = { +- { +- .name = "avs_dmic", +- }, +- {}, ++ { .name = "avs_dmic" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_dmic_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/es8336.c b/sound/soc/intel/avs/boards/es8336.c +--- a/sound/soc/intel/avs/boards/es8336.c ++++ b/sound/soc/intel/avs/boards/es8336.c +@@ -309,10 +309,8 @@ static int avs_es8336_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_es8336_driver_ids[] = { +- { +- .name = "avs_es8336", +- }, +- {}, ++ { .name = "avs_es8336" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_es8336_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -231,10 +231,8 @@ static int avs_hdaudio_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_hdaudio_driver_ids[] = { +- { +- .name = "avs_hdaudio", +- }, +- {}, ++ { .name = "avs_hdaudio" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_hdaudio_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/i2s_test.c b/sound/soc/intel/avs/boards/i2s_test.c +--- a/sound/soc/intel/avs/boards/i2s_test.c ++++ b/sound/soc/intel/avs/boards/i2s_test.c +@@ -107,10 +107,8 @@ static int avs_i2s_test_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_i2s_test_driver_ids[] = { +- { +- .name = "avs_i2s_test", +- }, +- {}, ++ { .name = "avs_i2s_test" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_i2s_test_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c +--- a/sound/soc/intel/avs/boards/max98357a.c ++++ b/sound/soc/intel/avs/boards/max98357a.c +@@ -136,10 +136,8 @@ static int avs_max98357a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98357a_driver_ids[] = { +- { +- .name = "avs_max98357a", +- }, +- {}, ++ { .name = "avs_max98357a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98357a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c +--- a/sound/soc/intel/avs/boards/max98373.c ++++ b/sound/soc/intel/avs/boards/max98373.c +@@ -191,10 +191,8 @@ static int avs_max98373_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98373_driver_ids[] = { +- { +- .name = "avs_max98373", +- }, +- {}, ++ { .name = "avs_max98373" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98373_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98927.c b/sound/soc/intel/avs/boards/max98927.c +--- a/sound/soc/intel/avs/boards/max98927.c ++++ b/sound/soc/intel/avs/boards/max98927.c +@@ -188,10 +188,8 @@ static int avs_max98927_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98927_driver_ids[] = { +- { +- .name = "avs_max98927", +- }, +- {}, ++ { .name = "avs_max98927" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98927_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c +--- a/sound/soc/intel/avs/boards/nau8825.c ++++ b/sound/soc/intel/avs/boards/nau8825.c +@@ -293,10 +293,8 @@ static int avs_nau8825_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_nau8825_driver_ids[] = { +- { +- .name = "avs_nau8825", +- }, +- {}, ++ { .name = "avs_nau8825" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_nau8825_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/pcm3168a.c b/sound/soc/intel/avs/boards/pcm3168a.c +--- a/sound/soc/intel/avs/boards/pcm3168a.c ++++ b/sound/soc/intel/avs/boards/pcm3168a.c +@@ -132,10 +132,8 @@ static int avs_pcm3168a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_pcm3168a_driver_ids[] = { +- { +- .name = "avs_pcm3168a", +- }, +- {}, ++ { .name = "avs_pcm3168a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_pcm3168a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/probe.c b/sound/soc/intel/avs/boards/probe.c +--- a/sound/soc/intel/avs/boards/probe.c ++++ b/sound/soc/intel/avs/boards/probe.c +@@ -64,7 +64,7 @@ static const struct platform_device_id avs_probe_mb_driver_ids[] = { + { + .name = "avs_probe_mb", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_probe_mb_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c +--- a/sound/soc/intel/avs/boards/rt274.c ++++ b/sound/soc/intel/avs/boards/rt274.c +@@ -261,7 +261,7 @@ static const struct platform_device_id avs_rt274_driver_ids[] = { + { + .name = "avs_rt274", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt274_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c +--- a/sound/soc/intel/avs/boards/rt286.c ++++ b/sound/soc/intel/avs/boards/rt286.c +@@ -231,7 +231,7 @@ static const struct platform_device_id avs_rt286_driver_ids[] = { + { + .name = "avs_rt286", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt286_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c +--- a/sound/soc/intel/avs/boards/rt298.c ++++ b/sound/soc/intel/avs/boards/rt298.c +@@ -250,7 +250,7 @@ static const struct platform_device_id avs_rt298_driver_ids[] = { + { + .name = "avs_rt298", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt298_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5514.c b/sound/soc/intel/avs/boards/rt5514.c +--- a/sound/soc/intel/avs/boards/rt5514.c ++++ b/sound/soc/intel/avs/boards/rt5514.c +@@ -178,7 +178,7 @@ static const struct platform_device_id avs_rt5514_driver_ids[] = { + { + .name = "avs_rt5514", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5514_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5640.c b/sound/soc/intel/avs/boards/rt5640.c +--- a/sound/soc/intel/avs/boards/rt5640.c ++++ b/sound/soc/intel/avs/boards/rt5640.c +@@ -252,7 +252,7 @@ static const struct platform_device_id avs_rt5640_driver_ids[] = { + { + .name = "avs_rt5640", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5640_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5663.c b/sound/soc/intel/avs/boards/rt5663.c +--- a/sound/soc/intel/avs/boards/rt5663.c ++++ b/sound/soc/intel/avs/boards/rt5663.c +@@ -249,7 +249,7 @@ static const struct platform_device_id avs_rt5663_driver_ids[] = { + { + .name = "avs_rt5663", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5663_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c +--- a/sound/soc/intel/avs/boards/rt5682.c ++++ b/sound/soc/intel/avs/boards/rt5682.c +@@ -325,7 +325,7 @@ static const struct platform_device_id avs_rt5682_driver_ids[] = { + { + .name = "avs_rt5682", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5682_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c +--- a/sound/soc/intel/avs/boards/ssm4567.c ++++ b/sound/soc/intel/avs/boards/ssm4567.c +@@ -180,7 +180,7 @@ static const struct platform_device_id avs_ssm4567_driver_ids[] = { + { + .name = "avs_ssm4567", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_ssm4567_driver_ids); + +diff --git a/sound/soc/intel/avs/control.c b/sound/soc/intel/avs/control.c +--- a/sound/soc/intel/avs/control.c ++++ b/sound/soc/intel/avs/control.c +@@ -27,7 +27,7 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + struct avs_path_pipeline *ppl; + struct avs_path_module *mod; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + list_for_each_entry(path, &adev->path_list, node) { + list_for_each_entry(ppl, &path->ppl_list, node) { + list_for_each_entry(mod, &ppl->mod_list, node) { +@@ -35,14 +35,11 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + + if ((guid_equal(type, &AVS_PEAKVOL_MOD_UUID) || + guid_equal(type, &AVS_GAIN_MOD_UUID)) && +- mod->template->ctl_id == id) { +- spin_unlock(&adev->path_list_lock); ++ mod->template->ctl_id == id) + return mod; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return NULL; + } +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -14,6 +14,7 @@ + // foundation of this driver + // + ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + if (snd_hdac_bus_handle_stream_irq(bus, status, hdac_update_stream)) + ret = IRQ_HANDLED; + +- spin_lock_irq(&bus->reg_lock); ++ guard(spinlock_irq)(&bus->reg_lock); + /* Clear RIRB interrupt. */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { +@@ -283,7 +284,6 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + ret = IRQ_HANDLED; + } + +- spin_unlock_irq(&bus->reg_lock); + return ret; + } + +diff --git a/sound/soc/intel/avs/debug.h b/sound/soc/intel/avs/debug.h +--- a/sound/soc/intel/avs/debug.h ++++ b/sound/soc/intel/avs/debug.h +@@ -9,6 +9,7 @@ + #ifndef __SOUND_SOC_INTEL_AVS_DEBUG_H + #define __SOUND_SOC_INTEL_AVS_DEBUG_H + ++#include + #include "messages.h" + #include "registers.h" + +@@ -26,14 +27,9 @@ struct avs_dev; + + static inline int avs_log_buffer_status_locked(struct avs_dev *adev, union avs_notify_msg *msg) + { +- unsigned long flags; +- int ret; ++ guard(spinlock_irqsave)(&adev->trace_lock); + +- spin_lock_irqsave(&adev->trace_lock, flags); +- ret = avs_dsp_op(adev, log_buffer_status, msg); +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- +- return ret; ++ return avs_dsp_op(adev, log_buffer_status, msg); + } + + struct avs_apl_log_buffer_layout { +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -6,9 +6,9 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include +-#include + #include + #include + #include +@@ -236,20 +236,15 @@ static int strace_open(struct inode *inode, struct file *file) + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + +- if (kfifo_initialized(&adev->trace_fifo)) { +- ret = -EBUSY; +- goto err; +- } ++ if (kfifo_initialized(&adev->trace_fifo)) ++ return -EBUSY; + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) +- goto err; ++ return ret; + + file->private_data = adev; + return 0; +-err: +- module_put(adev->dev->driver->owner); +- return ret; + } + + static int strace_release(struct inode *inode, struct file *file) +@@ -257,24 +252,22 @@ static int strace_release(struct inode *inode, struct file *file) + union avs_notify_msg msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS); + struct avs_dev *adev = file->private_data; + unsigned long resource_mask; +- unsigned long flags, i; ++ unsigned long i; + u32 num_cores; + + resource_mask = adev->logged_resources; + num_cores = adev->hw_cfg.dsp_cores; + +- spin_lock_irqsave(&adev->trace_lock, flags); ++ scoped_guard(spinlock_irqsave, &adev->trace_lock) { ++ /* Gather any remaining logs. */ ++ for_each_set_bit(i, &resource_mask, num_cores) { ++ msg.log.core = i; ++ avs_dsp_op(adev, log_buffer_status, &msg); ++ } + +- /* Gather any remaining logs. */ +- for_each_set_bit(i, &resource_mask, num_cores) { +- msg.log.core = i; +- avs_dsp_op(adev, log_buffer_status, &msg); ++ kfifo_free(&adev->trace_fifo); + } + +- kfifo_free(&adev->trace_fifo); +- +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- + module_put(adev->dev->driver->owner); + return 0; + } +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -99,39 +100,39 @@ static void avs_dsp_recovery(struct avs_dev *adev) + unsigned int core_mask; + int ret; + +- mutex_lock(&adev->comp_list_mutex); +- /* disconnect all running streams */ +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct snd_soc_pcm_runtime *rtd; +- struct snd_soc_card *card; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ /* disconnect all running streams */ ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct snd_soc_pcm_runtime *rtd; ++ struct snd_soc_card *card; + +- card = acomp->base.card; +- if (!card) +- continue; +- +- for_each_card_rtds(card, rtd) { +- struct snd_pcm *pcm; +- int dir; +- +- pcm = rtd->pcm; +- if (!pcm || rtd->dai_link->no_pcm) ++ card = acomp->base->card; ++ if (!card) + continue; + +- for_each_pcm_streams(dir) { +- struct snd_pcm_substream *substream; ++ for_each_card_rtds(card, rtd) { ++ struct snd_pcm *pcm; ++ int dir; + +- substream = pcm->streams[dir].substream; +- if (!substream || !substream->runtime) ++ pcm = rtd->pcm; ++ if (!pcm || rtd->dai_link->no_pcm) + continue; + +- /* No need for _irq() as we are in nonatomic context. */ +- snd_pcm_stream_lock(substream); +- snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); +- snd_pcm_stream_unlock(substream); ++ for_each_pcm_streams(dir) { ++ struct snd_pcm_substream *substream; ++ ++ substream = pcm->streams[dir].substream; ++ if (!substream || !substream->runtime) ++ continue; ++ ++ /* No need for _irq() as we are in nonatomic context. */ ++ snd_pcm_stream_lock(substream); ++ snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); ++ snd_pcm_stream_unlock(substream); ++ } + } + } + } +- mutex_unlock(&adev->comp_list_mutex); + + /* forcibly shutdown all cores */ + core_mask = GENMASK(adev->hw_cfg.dsp_cores - 1, 0); +@@ -397,7 +398,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + if (!ipc->ready) + return -EPERM; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); +@@ -412,7 +413,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + /* Same treatment as on exception, just stack_dump=0. */ + avs_dsp_exception_caught(adev, &msg); + } +- goto exit; ++ return ret; + } + + ret = ipc->rx.rsp.status; +@@ -436,8 +437,6 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + memcpy(reply->data, ipc->rx.data, reply->size); + } + +-exit: +- mutex_unlock(&ipc->msg_mutex); + return ret; + } + +@@ -501,7 +500,7 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + struct avs_ipc *ipc = adev->ipc; + int ret; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, NULL); +@@ -522,8 +521,6 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + dev_err(adev->dev, "%s (0x%08x 0x%08x) failed: %d\n", + name, request->glb.primary, request->glb.ext.val, ret); + +- mutex_unlock(&ipc->msg_mutex); +- + return ret; + } + +diff --git a/sound/soc/intel/avs/loader.c b/sound/soc/intel/avs/loader.c +--- a/sound/soc/intel/avs/loader.c ++++ b/sound/soc/intel/avs/loader.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -630,15 +631,15 @@ static int avs_load_firmware(struct avs_dev *adev, bool purge) + if (ret) + goto reenable_gating; + +- mutex_lock(&adev->comp_list_mutex); +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct avs_tplg *tplg = acomp->tplg; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct avs_tplg *tplg = acomp->tplg; + +- ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); +- if (ret < 0) +- break; ++ ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); ++ if (ret < 0) ++ break; ++ } + } +- mutex_unlock(&adev->comp_list_mutex); + + reenable_gating: + avs_hda_l1sen_enable(adev, true); +diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c +--- a/sound/soc/intel/avs/path.c ++++ b/sound/soc/intel/avs/path.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -69,16 +70,13 @@ avs_path_find_path(struct avs_dev *adev, const char *name, u32 template_id) + if (!template) + return NULL; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Only one variant of given path template may be instantiated at a time. */ + list_for_each_entry(path, &adev->path_list, node) { +- if (path->template->owner == template) { +- spin_unlock(&adev->path_list_lock); ++ if (path->template->owner == template) + return path; +- } + } + +- spin_unlock(&adev->path_list_lock); + return NULL; + } + +@@ -1300,7 +1298,7 @@ void avs_path_free(struct avs_path *path) + struct avs_path *cpath, *csave; + struct avs_dev *adev = path->owner; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Free all condpaths this path spawned. */ + list_for_each_entry_safe(cpath, csave, &path->source_list, source_node) +@@ -1309,8 +1307,6 @@ void avs_path_free(struct avs_path *path) + avs_condpath_free(path->owner, cpath); + + avs_path_free_unlocked(path); +- +- mutex_unlock(&adev->path_mutex); + } + + struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, +@@ -1329,13 +1325,13 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + } + + /* Serialize path and its components creation. */ +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + /* Satisfy needs of avs_path_find_tplg(). */ +- mutex_lock(&adev->comp_list_mutex); ++ guard(mutex)(&adev->comp_list_mutex); + + path = avs_path_create_unlocked(adev, dma_id, variant); + if (IS_ERR(path)) +- goto exit; ++ return path; + + ret = avs_condpaths_walk_all(adev, path); + if (ret) { +@@ -1343,10 +1339,6 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + path = ERR_PTR(ret); + } + +-exit: +- mutex_unlock(&adev->comp_list_mutex); +- mutex_unlock(&adev->path_mutex); +- + return path; + } + +@@ -1491,15 +1483,13 @@ static void avs_condpaths_pause(struct avs_dev *adev, struct avs_path *path) + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* If either source or sink stops, so do the attached conditional paths. */ + list_for_each_entry(cpath, &path->source_list, source_node) + avs_condpath_pause(adev, cpath); + list_for_each_entry(cpath, &path->sink_list, sink_node) + avs_condpath_pause(adev, cpath); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_pause(struct avs_path *path) +@@ -1555,7 +1545,7 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Run conditional paths only if source and sink are both running. */ + list_for_each_entry(cpath, &path->source_list, source_node) +@@ -1567,8 +1557,6 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + if (cpath->source->state == AVS_PPL_STATE_RUNNING && + cpath->sink->state == AVS_PPL_STATE_RUNNING) + avs_condpath_run(adev, cpath, trigger); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_run(struct avs_path *path, int trigger) +diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c +--- a/sound/soc/intel/avs/pcm.c ++++ b/sound/soc/intel/avs/pcm.c +@@ -959,7 +959,7 @@ static const struct file_operations topology_name_fops = { + static int avs_component_load_libraries(struct avs_soc_component *acomp) + { + struct avs_tplg *tplg = acomp->tplg; +- struct avs_dev *adev = to_avs_dev(acomp->base.dev); ++ struct avs_dev *adev = to_avs_dev(acomp->base->dev); + int ret; + + if (!tplg->num_libs) +@@ -1393,25 +1393,28 @@ int avs_register_component(struct device *dev, const char *name, + struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) + { + struct avs_soc_component *acomp; +- int ret; ++ const char *comp_name; + + acomp = devm_kzalloc(dev, sizeof(*acomp), GFP_KERNEL); + if (!acomp) + return -ENOMEM; + +- acomp->base.name = devm_kstrdup(dev, name, GFP_KERNEL); +- if (!acomp->base.name) ++ acomp->base = snd_soc_component_alloc(dev); ++ if (!acomp->base) ++ return -ENOMEM; ++ ++ comp_name = devm_kstrdup(dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + + INIT_LIST_HEAD(&acomp->node); + + drv->use_dai_pcm_id = !obsolete_card_names; + +- ret = snd_soc_component_initialize(&acomp->base, drv, dev); +- if (ret < 0) +- return ret; ++ snd_soc_component_set_name(acomp->base, comp_name); ++ snd_soc_component_set_priv(acomp->base, acomp); + +- return snd_soc_add_component(&acomp->base, cpu_dais, num_cpu_dais); ++ return snd_soc_register_component(acomp->base, drv, cpu_dais, num_cpu_dais); + } + + static struct snd_soc_dai_driver dmic_cpu_dais[] = { +diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c +--- a/sound/soc/intel/avs/probes.c ++++ b/sound/soc/intel/avs/probes.c +@@ -296,19 +296,19 @@ static const struct snd_soc_component_driver avs_probe_component_driver = { + int avs_register_probe_component(struct avs_dev *adev, const char *name) + { + struct snd_soc_component *component; +- int ret; ++ const char *comp_name; + +- component = devm_kzalloc(adev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(adev->dev); + if (!component) + return -ENOMEM; + +- component->name = devm_kstrdup(adev->dev, name, GFP_KERNEL); +- if (!component->name) ++ comp_name = devm_kstrdup(adev->dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &avs_probe_component_driver, adev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, comp_name); + +- return snd_soc_add_component(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); ++ return snd_soc_register_component(component, ++ &avs_probe_component_driver, ++ probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); + } +diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c +--- a/sound/soc/intel/avs/topology.c ++++ b/sound/soc/intel/avs/topology.c +@@ -2229,7 +2229,7 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) + + int avs_load_topology(struct snd_soc_component *comp, const char *filename) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, filename, comp->dev); +@@ -2242,7 +2242,6 @@ int avs_load_topology(struct snd_soc_component *comp, const char *filename) + if (ret < 0) + dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); + +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c +--- a/sound/soc/intel/avs/utils.c ++++ b/sound/soc/intel/avs/utils.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -48,13 +49,12 @@ int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_mo + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_entry_index(adev, uuid); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -62,13 +62,12 @@ int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_modu + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -86,13 +85,12 @@ bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id) + bool ret = false; + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + ret = ida_is_empty(adev->mod_idas[idx]); + +- mutex_unlock(&adev->modres_mutex); + return ret; + } + +@@ -163,68 +161,57 @@ int avs_module_info_init(struct avs_dev *adev, bool purge) + if (ret) + return AVS_IPC_RET(ret); + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + ret = avs_module_ida_alloc(adev, info, purge); + if (ret < 0) { + dev_err(adev->dev, "initialize module idas failed: %d\n", ret); +- goto exit; ++ return ret; + } + + /* Refresh current information with newly received table. */ + kfree(adev->mods_info); + adev->mods_info = info; + +-exit: +- mutex_unlock(&adev->modres_mutex); + return ret; + } + + void avs_module_info_free(struct avs_dev *adev) + { +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + avs_module_ida_destroy(adev); + kfree(adev->mods_info); + adev->mods_info = NULL; +- +- mutex_unlock(&adev->modres_mutex); + } + + int avs_module_id_alloc(struct avs_dev *adev, u16 module_id) + { +- int ret, idx, max_id; ++ int idx, max_id; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); +- ret = -EINVAL; +- goto exit; ++ return -EINVAL; + } + max_id = adev->mods_info->entries[idx].instance_max_count - 1; +- ret = ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); +-exit: +- mutex_unlock(&adev->modres_mutex); +- return ret; ++ ++ return ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); + } + + void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id) + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); +- if (idx == -ENOENT) { ++ if (idx == -ENOENT) + dev_err(adev->dev, "invalid module id: %d", module_id); +- goto exit; +- } +- +- ida_free(adev->mod_idas[idx], instance_id); +-exit: +- mutex_unlock(&adev->modres_mutex); ++ else ++ ida_free(adev->mod_idas[idx], instance_id); + } + + /* +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -1483,12 +1483,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -1507,13 +1507,13 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + if (mach->mach_params.dmic_num) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:dmic cfg-mics:%d", + card->components, + mach->mach_params.dmic_num); +@@ -1524,7 +1524,7 @@ static int mc_probe(struct platform_device *pdev) + /* Register the card */ + ret = devm_snd_soc_register_card(card->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -1542,8 +1542,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "sof_sdw", }, +- {} ++ { .name = "sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h +--- a/sound/soc/intel/catpt/core.h ++++ b/sound/soc/intel/catpt/core.h +@@ -139,9 +139,7 @@ int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request, + + int catpt_first_boot_firmware(struct catpt_dev *cdev); + int catpt_boot_firmware(struct catpt_dev *cdev, bool restore); +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan); ++int catpt_store_firmware_context(struct catpt_dev *cdev); + int catpt_coredump(struct catpt_dev *cdev); + + #include +diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c +--- a/sound/soc/intel/catpt/device.c ++++ b/sound/soc/intel/catpt/device.c +@@ -28,44 +28,17 @@ + static int catpt_do_suspend(struct device *dev) + { + struct catpt_dev *cdev = dev_get_drvdata(dev); +- struct dma_chan *chan; + int ret; + +- chan = catpt_dma_request_config_chan(cdev); +- if (IS_ERR(chan)) +- return PTR_ERR(chan); +- + memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx)); + ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx); +- if (ret) { +- ret = CATPT_IPC_RET(ret); +- goto release_dma_chan; +- } +- +- ret = catpt_dsp_stall(cdev, true); + if (ret) +- goto release_dma_chan; ++ return CATPT_IPC_RET(ret); + +- ret = catpt_store_memdumps(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store memdumps failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_module_states(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store module states failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_streams_context(cdev, chan); +- if (ret) +- dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); +- +-release_dma_chan: +- dma_release_channel(chan); ++ ret = catpt_store_firmware_context(cdev); + if (ret) + return ret; ++ + return catpt_dsp_power_down(cdev); + } + +diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c +--- a/sound/soc/intel/catpt/loader.c ++++ b/sound/soc/intel/catpt/loader.c +@@ -26,7 +26,7 @@ struct catpt_fw_hdr { + u32 reserved[4]; + } __packed; + +-struct catpt_fw_mod_hdr { ++struct catpt_fw_module_hdr { + char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; +@@ -81,7 +81,7 @@ catpt_request_region(struct resource *root, resource_size_t size) + return __request_region(root, addr, size, NULL, 0); + } + +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { + struct catpt_stream_runtime *stream; + +@@ -108,7 +108,7 @@ int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -138,7 +138,7 @@ int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -171,6 +171,39 @@ int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + ++int catpt_store_firmware_context(struct catpt_dev *cdev) ++{ ++ struct dma_chan *chan; ++ int ret; ++ ++ chan = catpt_dma_request_config_chan(cdev); ++ if (IS_ERR(chan)) ++ return PTR_ERR(chan); ++ ++ ret = catpt_dsp_stall(cdev, true); ++ if (ret) ++ goto exit; ++ ++ ret = catpt_store_dram_data(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store memdumps failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_module_states(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store module states failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_streams_context(cdev, chan); ++ if (ret) ++ dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); ++exit: ++ dma_release_channel(chan); ++ return ret; ++} ++ + static int + catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { +@@ -199,7 +232,7 @@ catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_restore_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -234,9 +267,9 @@ static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_fwimage(struct catpt_dev *cdev, +- struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_block_hdr *blk) ++static int catpt_restore_dram_rodata(struct catpt_dev *cdev, ++ struct dma_chan *chan, dma_addr_t paddr, ++ struct catpt_fw_block_hdr *blk) + { + struct resource r1 = {}; + int i; +@@ -324,28 +357,26 @@ static int catpt_load_block(struct catpt_dev *cdev, + + static int catpt_restore_basefw(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *basefw) ++ struct catpt_fw_module_hdr *basefw) + { +- u32 offset = sizeof(*basefw); ++ u32 off = sizeof(*basefw); + int ret, i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + basefw, sizeof(*basefw), false); + +- /* restore basefw image */ ++ /* Restore IRAM and .rodata for DRAM based on the firmware image. */ + for (i = 0; i < basefw->blocks; i++) { + struct catpt_fw_block_hdr *blk; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_IRAM: +- ret = catpt_load_block(cdev, chan, paddr + offset, +- blk, false); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, false); + break; + default: +- ret = catpt_restore_fwimage(cdev, chan, paddr + offset, +- blk); ++ ret = catpt_restore_dram_rodata(cdev, chan, paddr + off, blk); + break; + } + +@@ -354,11 +385,11 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + +- /* then proceed with memory dumps */ +- ret = catpt_restore_memdumps(cdev, chan); ++ /* Then proceed with DRAM .data saved before D3. */ ++ ret = catpt_restore_dram_data(cdev, chan); + if (ret) + dev_err(cdev->dev, "restore memdumps failed: %d\n", ret); + +@@ -367,9 +398,9 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + + static int catpt_restore_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -379,7 +410,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_INSTANCE: +@@ -390,7 +421,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + ALIGN(blk->size, 4)); + break; + default: +- ret = catpt_load_block(cdev, chan, paddr + offset, ++ ret = catpt_load_block(cdev, chan, paddr + off, + blk, false); + break; + } +@@ -400,7 +431,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + return 0; +@@ -408,10 +439,10 @@ static int catpt_restore_module(struct catpt_dev *cdev, + + static int catpt_load_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { + struct catpt_module_type *type; +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -423,9 +454,9 @@ static int catpt_load_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + +- ret = catpt_load_block(cdev, chan, paddr + offset, blk, true); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, true); + if (ret) { + dev_err(cdev->dev, "load block failed: %d\n", ret); + return ret; +@@ -440,7 +471,7 @@ static int catpt_load_module(struct catpt_dev *cdev, + type->state_size = blk->size; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + /* init module type static info */ +@@ -457,17 +488,17 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -479,12 +510,10 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + + switch (mod->module_id) { + case CATPT_MODID_BASE_FW: +- ret = catpt_restore_basefw(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_basefw(cdev, chan, paddr + off, mod); + break; + default: +- ret = catpt_restore_module(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_module(cdev, chan, paddr + off, mod); + break; + } + +@@ -493,7 +522,7 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; +@@ -503,17 +532,17 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -523,44 +552,40 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + if (mod->module_id > CATPT_MODID_LAST) + return -EINVAL; + +- ret = catpt_load_module(cdev, chan, paddr + offset, mod); ++ ret = catpt_load_module(cdev, chan, paddr + off, mod); + if (ret) { + dev_err(cdev->dev, "load module failed: %d\n", ret); + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; + } + +-static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, +- const char *name, const char *signature, +- bool restore) ++static int catpt_request_load_firmware(struct catpt_dev *cdev, struct dma_chan *chan, ++ const char *name, bool restore) + { + struct catpt_fw_hdr *fw; +- struct firmware *img; + dma_addr_t paddr; + void *vaddr; + int ret; + +- ret = request_firmware((const struct firmware **)&img, name, cdev->dev); ++ const struct firmware *img __free(firmware) = NULL; ++ ret = request_firmware(&img, name, cdev->dev); + if (ret) + return ret; + + fw = (struct catpt_fw_hdr *)img->data; +- if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) { ++ if (strncmp(fw->signature, FW_SIGNATURE, FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "firmware signature mismatch\n"); +- ret = -EINVAL; +- goto release_fw; ++ return -EINVAL; + } + + vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL); +- if (!vaddr) { +- ret = -ENOMEM; +- goto release_fw; +- } ++ if (!vaddr) ++ return -ENOMEM; + + memcpy(vaddr, img->data, img->size); + fw = (struct catpt_fw_hdr *)vaddr; +@@ -570,12 +595,10 @@ static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, + ret = catpt_load_firmware(cdev, chan, paddr, fw); + + dma_free_coherent(cdev->dev, img->size, vaddr, paddr); +-release_fw: +- release_firmware(img); + return ret; + } + +-static int catpt_load_images(struct catpt_dev *cdev, bool restore) ++static int catpt_request_dma_load_firmware(struct catpt_dev *cdev, bool restore) + { + struct dma_chan *chan; + int ret; +@@ -584,8 +607,7 @@ static int catpt_load_images(struct catpt_dev *cdev, bool restore) + if (IS_ERR(chan)) + return PTR_ERR(chan); + +- ret = catpt_load_image(cdev, chan, cdev->spec->fw_name, +- FW_SIGNATURE, restore); ++ ret = catpt_request_load_firmware(cdev, chan, cdev->spec->fw_name, restore); + if (ret) + goto release_dma_chan; + +@@ -605,7 +627,7 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + + catpt_dsp_stall(cdev, true); + +- ret = catpt_load_images(cdev, restore); ++ ret = catpt_request_dma_load_firmware(cdev, restore); + if (ret) { + dev_err(cdev->dev, "load binaries failed: %d\n", ret); + return ret; +@@ -619,10 +641,10 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + if (!ret) { + dev_err(cdev->dev, "firmware ready timeout\n"); + return -ETIMEDOUT; +- /* Wake up does not mean FW is ready, an exception could occur. */ +- } else if (!cdev->ipc.ready) { +- return -EREMOTEIO; + } ++ /* Wake up does not mean FW is ready, an exception could occur. */ ++ if (!cdev->ipc.ready) ++ return -EREMOTEIO; + + /* update sram pg & clock once done booting */ + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); +diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c +--- a/sound/soc/intel/catpt/pcm.c ++++ b/sound/soc/intel/catpt/pcm.c +@@ -75,8 +75,8 @@ static struct catpt_stream_template *catpt_topology[] = { + static struct catpt_stream_template * + catpt_get_stream_template(struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + enum catpt_stream_type type; + + type = cpu_dai->driver->id; +@@ -159,11 +159,11 @@ static void catpt_stream_read_position(struct catpt_dev *cdev, + static void catpt_arrange_page_table(struct snd_pcm_substream *substream, + struct snd_dma_buffer *pgtbl) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream); + int i, pages; + +- pages = snd_sgbuf_aligned_pages(rtm->dma_bytes); ++ pages = snd_sgbuf_aligned_pages(runtime->dma_bytes); + + for (i = 0; i < pages; i++) { + u32 pfn, offset; +@@ -386,7 +386,7 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dmab; + struct catpt_stream_runtime *stream; + struct catpt_audio_format afmt; +@@ -412,8 +412,8 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.page_table_addr = stream->pgtbl.addr; +- rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE); +- rinfo.size = rtm->dma_bytes; ++ rinfo.num_pages = DIV_ROUND_UP(runtime->dma_bytes, PAGE_SIZE); ++ rinfo.size = runtime->dma_bytes; + rinfo.offset = 0; + rinfo.ring_first_page_pfn = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0)); + +@@ -544,11 +544,11 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + struct catpt_notify_position *pos) + { + struct snd_pcm_substream *substream = stream->substream; +- struct snd_pcm_runtime *r = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t dsppos, newpos; + int ret; + +- dsppos = bytes_to_frames(r, pos->stream_position); ++ dsppos = bytes_to_frames(runtime, pos->stream_position); + + if (!stream->prepared) + goto exit; +@@ -556,8 +556,8 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + if (stream->template->type != CATPT_STRM_TYPE_RENDER) + goto exit; + +- if (dsppos >= r->buffer_size / 2) +- newpos = r->buffer_size / 2; ++ if (dsppos >= runtime->buffer_size / 2) ++ newpos = runtime->buffer_size / 2; + else + newpos = 0; + /* +@@ -565,7 +565,7 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + * (buffer half consumed) update wp to allow stream progression. + */ + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id, +- frames_to_bytes(r, newpos), ++ frames_to_bytes(runtime, newpos), + false, false); + if (ret) { + dev_err(cdev->dev, "update position for stream %d failed: %d\n", +@@ -600,11 +600,11 @@ static const struct snd_pcm_hardware catpt_pcm_hardware = { + }; + + static int catpt_component_pcm_new(struct snd_soc_component *component, +- struct snd_soc_pcm_runtime *rtm) ++ struct snd_soc_pcm_runtime *rtd) + { + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + +- snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG, ++ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV_SG, + cdev->dev, + catpt_pcm_hardware.buffer_bytes_max, + catpt_pcm_hardware.buffer_bytes_max); +@@ -615,9 +615,9 @@ static int catpt_component_pcm_new(struct snd_soc_component *component, + static int catpt_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + +- if (!rtm->dai_link->no_pcm) ++ if (!rtd->dai_link->no_pcm) + snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware); + return 0; + } +@@ -626,13 +626,13 @@ static snd_pcm_uframes_t + catpt_component_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + u32 pos; + +- if (rtm->dai_link->no_pcm) ++ if (rtd->dai_link->no_pcm) + return 0; + + stream = snd_soc_dai_get_dma_data(cpu_dai, substream); +@@ -650,10 +650,10 @@ static const struct snd_soc_dai_ops catpt_fe_dai_ops = { + .trigger = catpt_dai_trigger, + }; + +-static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm, ++static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai) + { +- struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtm, 0); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct catpt_ssp_device_format devfmt; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + int ret; +@@ -871,8 +871,7 @@ static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol) + return CATPT_IPC_RET(ret); + } + +-static int catpt_volume_info(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_info *uinfo) ++static int catpt_volume_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) + { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = CATPT_CHANNELS_MAX; +@@ -1073,18 +1072,14 @@ int catpt_arm_stream_templates(struct catpt_dev *cdev) + int catpt_register_plat_component(struct catpt_dev *cdev) + { + struct snd_soc_component *component; +- int ret; + +- component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(cdev->dev); + if (!component) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &catpt_comp_driver, +- cdev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, catpt_comp_driver.name); + +- component->name = catpt_comp_driver.name; +- return snd_soc_add_component(component, dai_drivers, +- ARRAY_SIZE(dai_drivers)); ++ return snd_soc_register_component(component, ++ &catpt_comp_driver, ++ dai_drivers, ARRAY_SIZE(dai_drivers)); + } +diff --git a/sound/soc/intel/common/soc-acpi-intel-arl-match.c b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-arl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +@@ -572,6 +572,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), +@@ -600,12 +601,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x1, /* link0 required */ + .links = arl_sdca_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -717,24 +717,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l23.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l2_cs35l56x6_l13, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l2-cs35l56x6-l13.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -748,12 +752,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2) | BIT(3), + .links = lnl_712_only, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt712-l2-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -766,19 +772,22 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1320_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1), + .links = lnl_sdw_rt713_l0_rt1318_l1, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg" ++ .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +@@ -1321,60 +1321,70 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = tac5572_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5572.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5672_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5672.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5682_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5682.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2783_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2783.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2883_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2883.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1316_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1318_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = mtl_rt713_l0_rt1318_l1_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l1-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = mtl_rt713_l0_rt1316_l12, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3) | BIT(0), + .links = mtl_712_l0_1712_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt712-l0-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1395,7 +1405,8 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = mtl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), +@@ -1455,12 +1466,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x9, /* 2 active links required */ + .links = mtl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1474,18 +1487,21 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = rt5682_link2_max98373_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-rt5682-l2-max98373-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = cs42l42_link0_max98363_link2, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-cs42l42-l0-max98363-l2.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +@@ -492,7 +492,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rt722_l0_rt1320_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt722-l0-rt1320-l23.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, ++ .machine_check = snd_soc_acpi_intel_no_function_topology, + }, + { + .link_mask = BIT(1) | BIT(2), +@@ -522,14 +522,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_sdw_rt712_vb_l3_rt1320_l3, + .drv_name = "sof_sdw", +- .machine_check = snd_soc_acpi_intel_sdca_is_device_rt712_vb, ++ .machine_check = snd_soc_acpi_intel_rt712_vb_no_function_topology, + .sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,14 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) ++{ ++ return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && ++ snd_soc_acpi_intel_no_function_topology(arg); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_rt712_vb_no_function_topology, ++ "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + MODULE_DESCRIPTION("ASoC ACPI Intel SDCA quirks"); + MODULE_LICENSE("GPL"); + MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,5 +11,6 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c +--- a/sound/soc/intel/keembay/kmb_platform.c ++++ b/sound/soc/intel/keembay/kmb_platform.c +@@ -873,6 +873,8 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) + + if (kmb_i2s->use_pio) { + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0, + pdev->name, kmb_i2s); +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -2,24 +2,131 @@ + // + // Loongson ASoC Audio Machine driver + // +-// Copyright (C) 2023 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // Author: Yingkun Meng ++// Binbin Zhou + // + ++#include ++#include + #include ++#include ++#include ++#include + #include + #include +-#include +-#include +-#include + + static char codec_name[SND_ACPI_I2C_ID_LEN]; + + struct loongson_card_data { + struct snd_soc_card snd_card; + unsigned int mclk_fs; ++ struct gpio_desc *gpiod_hp_det; ++ struct gpio_desc *gpiod_hp_ctl; ++ struct gpio_desc *gpiod_spkr_en; ++ const struct loongson_card_config *cfg; + }; + ++struct loongson_card_config { ++ unsigned int fmt; ++ bool add_hp_jack; ++ bool add_dapm_widgets; ++ bool add_dapm_routes; ++}; ++ ++static const struct loongson_card_config ls2k1000_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_forever_pi_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = true, ++ .add_dapm_widgets = true, ++ .add_dapm_routes = true, ++}; ++ ++static int tegra_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); ++ struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Speaker")) ++ gpiod_set_value_cansleep(priv->gpiod_spkr_en, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Headphone")) ++ gpiod_set_value_cansleep(priv->gpiod_hp_ctl, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", tegra_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++}; ++ ++/* Headphones Jack */ ++ ++static struct snd_soc_jack loongson_asoc_hp_jack; ++ ++static struct snd_soc_jack_pin loongson_asoc_hp_jack_pins[] = { ++ { ++ .pin = "Headphone", ++ .mask = SND_JACK_HEADPHONE ++ }, ++ { ++ .pin = "Speaker", ++ .mask = SND_JACK_HEADPHONE, ++ .invert = 1 ++ }, ++}; ++ ++static struct snd_soc_jack_gpio loongson_asoc_hp_jack_gpio = { ++ .name = "Headphones detection", ++ .report = SND_JACK_HEADPHONE, ++ .debounce_time = 150, ++}; ++ ++static int loongson_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_card *card = rtd->card; ++ struct loongson_card_data *ls_priv = snd_soc_card_get_drvdata(card); ++ int ret = 0; ++ ++ if (!ls_priv->cfg->add_hp_jack || !ls_priv->gpiod_hp_det) ++ return 0; ++ ++ ret = snd_soc_card_jack_new_pins(card, "Headphones Jack", ++ SND_JACK_HEADPHONE, ++ &loongson_asoc_hp_jack, ++ loongson_asoc_hp_jack_pins, ++ ARRAY_SIZE(loongson_asoc_hp_jack_pins)); ++ if (ret) { ++ dev_err(rtd->dev, "Headphones Jack creation failed: %d\n", ret); ++ return ret; ++ } ++ ++ loongson_asoc_hp_jack_gpio.desc = ls_priv->gpiod_hp_det; ++ ++ ret = snd_soc_jack_add_gpios(&loongson_asoc_hp_jack, 1, &loongson_asoc_hp_jack_gpio); ++ if (ret) ++ dev_err(rtd->dev, "Headphone GPIO not added: %d\n", ret); ++ ++ return ret; ++} ++ + static int loongson_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +@@ -45,7 +152,7 @@ static int loongson_card_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, ls_card->cfg->fmt); + } + + static const struct snd_soc_ops loongson_ops = { +@@ -61,8 +168,7 @@ static struct snd_soc_dai_link loongson_dai_links[] = { + { + .name = "Loongson Audio Port", + .stream_name = "Loongson Audio", +- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF +- | SND_SOC_DAIFMT_CBC_CFC, ++ .init = loongson_asoc_machine_init, + SND_SOC_DAILINK_REG(analog), + .ops = &loongson_ops, + }, +@@ -123,16 +229,35 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) + + static int loongson_card_parse_of(struct loongson_card_data *data) + { +- struct device_node *cpu, *codec; + struct snd_soc_card *card = &data->snd_card; ++ struct device_node *cpu, *codec; + struct device *dev = card->dev; + int ret, i; + ++ data->gpiod_hp_det = devm_gpiod_get_optional(dev, "hp-det", GPIOD_IN); ++ if (IS_ERR(data->gpiod_hp_det)) ++ return PTR_ERR(data->gpiod_hp_det); ++ ++ data->gpiod_hp_ctl = devm_gpiod_get_optional(dev, "hp-ctl", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_hp_ctl)) ++ return PTR_ERR(data->gpiod_hp_ctl); ++ ++ data->gpiod_spkr_en = devm_gpiod_get_optional(dev, "spkr-en", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_spkr_en)) ++ return PTR_ERR(data->gpiod_spkr_en); ++ ++ if (data->cfg->add_dapm_routes) { ++ ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); ++ if (ret) ++ return ret; ++ } ++ + cpu = of_get_child_by_name(dev->of_node, "cpu"); + if (!cpu) { + dev_err(dev, "platform property missing or invalid\n"); + return -EINVAL; + } ++ + codec = of_get_child_by_name(dev->of_node, "codec"); + if (!codec) { + dev_err(dev, "audio-codec property missing or invalid\n"); +@@ -177,12 +302,22 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + if (!ls_priv) + return -ENOMEM; + ++ ls_priv->cfg = (const struct loongson_card_config *)device_get_match_data(dev); ++ if (!ls_priv->cfg) ++ return -EINVAL; ++ + card = &ls_priv->snd_card; + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = loongson_dai_links; + card->num_links = ARRAY_SIZE(loongson_dai_links); ++ ++ if (ls_priv->cfg->add_dapm_widgets) { ++ card->dapm_widgets = loongson_aosc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ } ++ + snd_soc_card_set_drvdata(card, ls_priv); + + ret = device_property_read_string(dev, "model", &card->name); +@@ -202,7 +337,19 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_asoc_dt_ids[] = { +- { .compatible = "loongson,ls-audio-card" }, ++ /* Loongson-2K1000/Loongson-2K2000/LS7A */ ++ { ++ .compatible = "loongson,ls-audio-card", ++ .data = &ls2k1000_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-forever-pi-audio-card", ++ .data = &ls2k0300_forever_pi_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-dl2k0300b-audio-card", ++ .data = &ls2k0300_dl2k0300b_card_config ++ }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids); +diff --git a/sound/soc/loongson/loongson_i2s_plat.c b/sound/soc/loongson/loongson_i2s_plat.c +--- a/sound/soc/loongson/loongson_i2s_plat.c ++++ b/sound/soc/loongson/loongson_i2s_plat.c +@@ -2,7 +2,7 @@ + // + // Loongson I2S controller master mode dirver(platform device) + // +-// Copyright (C) 2023-2024 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // + // Author: Yingkun Meng + // Binbin Zhou +@@ -21,6 +21,7 @@ + #include "loongson_i2s.h" + #include "loongson_dma.h" + ++/* Loongson-2K1000 APBDMA routing */ + #define LOONGSON_I2S_RX_DMA_OFFSET 21 + #define LOONGSON_I2S_TX_DMA_OFFSET 18 + +@@ -30,6 +31,11 @@ + #define LOONGSON_DMA3_CONF 0x3 + #define LOONGSON_DMA4_CONF 0x4 + ++struct loongson_i2s_plat_config { ++ int rev_id; ++ int (*i2s_dma_config)(struct platform_device *pdev); ++}; ++ + static int loongson_i2s_apbdma_config(struct platform_device *pdev) + { + int val; +@@ -47,8 +53,18 @@ static int loongson_i2s_apbdma_config(struct platform_device *pdev) + return 0; + } + ++static const struct loongson_i2s_plat_config ls2k0300_i2s_plat_config = { ++ .rev_id = 1, ++}; ++ ++static const struct loongson_i2s_plat_config ls2k1000_i2s_plat_config = { ++ .rev_id = 0, ++ .i2s_dma_config = loongson_i2s_apbdma_config, ++}; ++ + static int loongson_i2s_plat_probe(struct platform_device *pdev) + { ++ const struct loongson_i2s_plat_config *plat_config; + struct device *dev = &pdev->dev; + struct loongson_i2s *i2s; + struct resource *res; +@@ -59,12 +75,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (!i2s) + return -ENOMEM; + +- ret = loongson_i2s_apbdma_config(pdev); +- if (ret) +- return ret; ++ plat_config = device_get_match_data(dev); ++ if (!plat_config) ++ return -EINVAL; + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- i2s->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (plat_config->i2s_dma_config) { ++ ret = plat_config->i2s_dma_config(pdev); ++ if (ret) ++ return ret; ++ } ++ ++ i2s->reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->reg_base)) + return dev_err_probe(dev, PTR_ERR(i2s->reg_base), + "devm_ioremap_resource failed\n"); +@@ -87,11 +108,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (IS_ERR(i2s_clk)) + return dev_err_probe(dev, PTR_ERR(i2s_clk), "clock property invalid\n"); + i2s->clk_rate = clk_get_rate(i2s_clk); ++ i2s->rev_id = plat_config->rev_id; + + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + dev_set_name(dev, LS_I2S_DRVNAME); + dev_set_drvdata(dev, i2s); + ++ if (i2s->rev_id == 1) { ++ regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET, I2S_CTRL_RESET); ++ fsleep(200); ++ } ++ + ret = devm_snd_soc_register_component(dev, &loongson_i2s_edma_component, + &loongson_i2s_dai, 1); + if (ret) +@@ -102,7 +129,8 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_i2s_ids[] = { +- { .compatible = "loongson,ls2k1000-i2s" }, ++ { .compatible = "loongson,ls2k0300-i2s", .data = &ls2k0300_i2s_plat_config }, ++ { .compatible = "loongson,ls2k1000-i2s", .data = &ls2k1000_i2s_plat_config }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_i2s_ids); +diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig +--- a/sound/soc/mediatek/Kconfig ++++ b/sound/soc/mediatek/Kconfig +@@ -7,7 +7,7 @@ config SND_SOC_MEDIATEK + + config SND_SOC_MT2701 + tristate "ASoC support for Mediatek MT2701 chip" +- depends on ARCH_MEDIATEK ++ depends on ARCH_MEDIATEK || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT2701 boards +diff --git a/sound/soc/mediatek/common/mtk-dsp-sof-common.c b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +--- a/sound/soc/mediatek/common/mtk-dsp-sof-common.c ++++ b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +@@ -232,6 +232,7 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + const char *propname) + { + struct device_node *np = dev->of_node; ++ struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link *parsed_dai_link; + const char *dai_name = NULL; + int i, j, ret, num_links, parsed_num_links = 0; +@@ -254,9 +255,9 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + return ret; + } + dev_dbg(dev, "ASoC: Property get dai_name:%s\n", dai_name); +- for (j = 0; j < card->num_links; j++) { +- if (!strcmp(dai_name, card->dai_link[j].name)) { +- memcpy(&parsed_dai_link[parsed_num_links++], &card->dai_link[j], ++ for_each_card_prelinks(card, j, dai_link) { ++ if (!strcmp(dai_name, dai_link->name)) { ++ memcpy(&parsed_dai_link[parsed_num_links++], dai_link, + sizeof(struct snd_soc_dai_link)); + break; + } +diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +--- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c ++++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +@@ -315,7 +315,7 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) + &mt8173_rt5650_priv.pll_from); + if (ret) { + dev_err(&pdev->dev, +- "%s snd_soc_register_card fail %d\n", ++ "%s device_property_read_u32() fail %d\n", + __func__, ret); + } + } +diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +--- a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c ++++ b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +@@ -385,8 +385,6 @@ static int mtk_adda_pad_top_event(struct snd_soc_dapm_widget *w, + if (event == SND_SOC_DAPM_PRE_PMU) { + if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2_CLK_P2) + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB8); +- else if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2) +- regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + else + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + } +diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +--- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c ++++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +@@ -1019,7 +1019,7 @@ static struct snd_soc_card mt8192_mt6359_rt1015p_rt5682x_card = { + .num_dapm_routes = ARRAY_SIZE(mt8192_mt6359_rt1015p_rt5682x_routes), + }; + +-static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, ++static int mt8192_mt6359_card_set_be_link(struct device *dev, + struct snd_soc_dai_link *link, + struct device_node *node, + char *link_name) +@@ -1027,9 +1027,9 @@ static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, + int ret; + + if (node && strcmp(link->name, link_name) == 0) { +- ret = snd_soc_of_get_dai_link_codecs(card->dev, node, link); ++ ret = snd_soc_of_get_dai_link_codecs(dev, node, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, "get dai link codecs fail\n"); ++ dev_err_probe(dev, ret, "get dai link codecs fail\n"); + return ret; + } + } +@@ -1065,21 +1065,21 @@ static int mt8192_mt6359_legacy_probe(struct mtk_soc_card_data *soc_card_data) + } + + for_each_card_prelinks(card, i, dai_link) { +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, speaker_codec, "I2S3"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, speaker_codec, "I2S3"); + if (ret) { + dev_err_probe(dev, ret, "%s set speaker_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S8"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S8"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S9"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S9"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); +diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +--- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c ++++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +@@ -104,8 +104,6 @@ static const struct snd_kcontrol_new mt8196_nau8825_controls[] = { + + #define EXT_SPK_AMP_W_NAME "Ext_Speaker_Amp" + +-static struct snd_soc_card mt8196_nau8825_soc_card; +- + static const struct snd_soc_dapm_widget mt8196_nau8825_card_widgets[] = { + /* SOF Uplink */ + SND_SOC_DAPM_MIXER("SOF_DMA_UL0", SND_SOC_NOPM, 0, 0, NULL, 0), +diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +--- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c ++++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +@@ -2120,19 +2120,15 @@ static int mt8365_afe_pcm_dev_probe(struct platform_device *pdev) + spin_lock_init(&afe_priv->afe_ctrl_lock); + mutex_init(&afe_priv->afe_clk_mutex); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- afe->base_addr = devm_ioremap_resource(&pdev->dev, res); ++ afe->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 1); +- if (res) { +- afe_priv->afe_sram_vir_addr = +- devm_ioremap_resource(&pdev->dev, res); +- if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { +- afe_priv->afe_sram_phy_addr = res->start; +- afe_priv->afe_sram_size = resource_size(res); +- } ++ afe_priv->afe_sram_vir_addr = ++ devm_platform_get_and_ioremap_resource(pdev, 1, &res); ++ if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { ++ afe_priv->afe_sram_phy_addr = res->start; ++ afe_priv->afe_sram_size = resource_size(res); + } + + /* initial audio related clock */ +diff --git a/sound/soc/mediatek/mt8365/mt8365-mt6357.c b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +--- a/sound/soc/mediatek/mt8365/mt8365-mt6357.c ++++ b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +@@ -70,7 +70,8 @@ static const struct snd_soc_dapm_route mt8365_mt6357_routes[] = { + static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -101,7 +102,8 @@ static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + static void mt8365_mt6357_int_adda_shutdown(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -245,7 +247,8 @@ static struct snd_soc_dai_link mt8365_mt6357_dais[] = { + + static int mt8365_mt6357_gpio_probe(struct snd_soc_card *card) + { +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + struct device *dev = card->dev; + int ret, i; + +@@ -294,7 +297,6 @@ static int mt8365_mt6357_dev_probe(struct mtk_soc_card_data *soc_card_data, bool + struct mt8365_mt6357_priv *mach_priv; + int ret; + +- card->dev = dev; + ret = parse_dai_link_info(card); + if (ret) + goto err; +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -4,6 +4,8 @@ snd-soc-meson-aiu-y := aiu.o + snd-soc-meson-aiu-y += aiu-acodec-ctrl.o + snd-soc-meson-aiu-y += aiu-codec-ctrl.o + snd-soc-meson-aiu-y += aiu-encoder-i2s.o ++snd-soc-meson-aiu-y += gx-formatter.o ++snd-soc-meson-aiu-y += aiu-formatter-i2s.o + snd-soc-meson-aiu-y += aiu-encoder-spdif.o + snd-soc-meson-aiu-y += aiu-fifo.o + snd-soc-meson-aiu-y += aiu-fifo-i2s.o +diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c +--- a/sound/soc/meson/aiu-encoder-i2s.c ++++ b/sound/soc/meson/aiu-encoder-i2s.c +@@ -10,14 +10,11 @@ + #include + + #include "aiu.h" ++#include "gx-formatter.h" ++#include "gx-interface.h" + +-#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) +-#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) +-#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) + #define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11) +-#define AIU_RST_SOFT_I2S_FAST BIT(0) + +-#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) + #define AIU_CLK_CTRL_I2S_DIV_EN BIT(0) + #define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2) + #define AIU_CLK_CTRL_AOCLK_INVERT BIT(6) +@@ -35,49 +32,6 @@ static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component, + enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0); + } + +-static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) +-{ +- /* Always operate in split (classic interleaved) mode */ +- unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT; +- +- /* Reset required to update the pipeline */ +- snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST); +- snd_soc_component_read(component, AIU_I2S_SYNC); +- +- switch (params_physical_width(params)) { +- case 16: /* Nothing to do */ +- break; +- +- case 32: +- desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT); +- break; +- +- default: +- return -EINVAL; +- } +- +- switch (params_channels(params)) { +- case 2: /* Nothing to do */ +- break; +- case 8: +- desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; +- break; +- default: +- return -EINVAL; +- } +- +- snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC, +- AIU_I2S_SOURCE_DESC_MODE_8CH | +- AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT | +- AIU_I2S_SOURCE_DESC_MODE_SPLIT, +- desc); +- +- return 0; +-} +- + static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -108,6 +62,32 @@ static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + return 0; + } + ++/* ++ * Return true if the given combination of channels and sample width requires ++ * the bs quirk. Return false otherwise. ++ */ ++static bool aiu_encoder_is_bs_quirk(unsigned int channels, int width) ++{ ++ return (channels == 8) && (width == 16); ++} ++ ++static int aiu_encoder_check_bs_quirk(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); ++ ++ /* Nothing to do if the other stream doesn't exist or it's not configured yet. */ ++ if (!other_stream || !other_stream->channels) ++ return 0; ++ ++ if (aiu_encoder_is_bs_quirk(other_stream->channels, other_stream->width) != ++ aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -119,7 +99,7 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + * increased by 50% to get the correct output rate. + * No idea why ! + */ +- if (params_width(params) == 16 && params_channels(params) == 8) { ++ if (aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) { + if (bs % 2) { + dev_err(component->dev, + "Cannot increase i2s divider by 50%%\n"); +@@ -141,25 +121,23 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + return 0; + } + +-static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) ++static int aiu_encoder_i2s_set_clocks(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { ++ struct snd_soc_component *component = dai->component; + struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int srate = params_rate(params); + unsigned int fs, bs; + int ret; + + /* Get the oversampling factor */ +- fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate); ++ fs = DIV_ROUND_CLOSEST(iface->mclk_rate, srate); + +- if (fs % 64) ++ if ((fs % 64) || (fs == 0)) + return -EINVAL; + +- /* Send data MSB first */ +- snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG, +- AIU_I2S_DAC_CFG_MSB_FIRST, +- AIU_I2S_DAC_CFG_MSB_FIRST); +- + /* Set bclk to lrlck ratio */ + snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL, + AIU_CODEC_DAC_LRCLK_CTRL_DIV, +@@ -168,10 +146,22 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, + + bs = fs / 64; + +- if (aiu->platform->has_clk_ctrl_more_i2s_div) ++ if (aiu->platform->has_clk_ctrl_more_i2s_div) { ++ /* ++ * The hw rules added in startup() make this unreachable in the ++ * sequential case, but both streams may be refined concurrently ++ * before either commits its config, since only ops->hw_params ++ * runs under the card's pcm_mutex. Re-check against the committed ++ * state of the other stream, which is stable under that mutex. ++ */ ++ if (aiu_encoder_check_bs_quirk(substream, params, dai)) { ++ dev_err(dai->dev, "bclk requirements incompatible with other stream\n"); ++ return -EINVAL; ++ } + ret = aiu_encoder_i2s_set_more_div(component, params, bs); +- else ++ } else { + ret = aiu_encoder_i2s_set_legacy_div(component, params, bs); ++ } + + if (ret) + return ret; +@@ -188,23 +178,37 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ ret = aiu_encoder_i2s_set_clocks(substream, params, dai); ++ if (ret) { ++ dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret); ++ return ret; ++ } ++ ++ ts->physical_width = params_physical_width(params); ++ ts->width = params_width(params); ++ ts->channels = params_channels(params); ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + int ret; + +- /* Disable the clock while changing the settings */ +- aiu_encoder_i2s_divider_enable(component, false); ++ if (ts->clk_enabled) ++ return 0; + +- ret = aiu_encoder_i2s_setup_desc(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s desc failed\n"); ++ ret = clk_prepare_enable(ts->iface->mclk); ++ if (ret) + return ret; +- } + +- ret = aiu_encoder_i2s_set_clocks(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s clocks failed\n"); +- return ret; +- } ++ ts->clk_enabled = true; + + aiu_encoder_i2s_divider_enable(component, true); + +@@ -214,9 +218,24 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + +- aiu_encoder_i2s_divider_enable(component, false); ++ /* ++ * If this is the last substream being closed then disable the i2s ++ * clock divider. ++ */ ++ if (snd_soc_dai_active(dai) <= 1) ++ aiu_encoder_i2s_divider_enable(component, 0); ++ ++ if (ts->clk_enabled) { ++ clk_disable_unprepare(ts->iface->mclk); ++ ts->clk_enabled = false; ++ } ++ ++ ts->channels = 0; ++ ts->width = 0; ++ ts->physical_width = 0; + + return 0; + } +@@ -224,6 +243,8 @@ static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct snd_soc_component *component = dai->component; ++ struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; + unsigned int val = 0; + unsigned int skew; +@@ -255,9 +276,12 @@ static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + skew = 0; + break; + default: ++ dev_err(dai->dev, "unsupported dai format\n"); + return -EINVAL; + } + ++ iface->fmt = fmt; ++ + val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew); + snd_soc_component_update_bits(component, AIU_CLK_CTRL, + AIU_CLK_CTRL_LRCLK_INVERT | +@@ -272,6 +296,7 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_iface *iface = &aiu->i2s.iface; + int ret; + + if (WARN_ON(clk_id != 0)) +@@ -280,11 +305,15 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + if (dir == SND_SOC_CLOCK_IN) + return 0; + +- ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq); +- if (ret) +- dev_err(dai->dev, "Failed to set sysclk to %uHz", freq); ++ ret = clk_set_rate(iface->mclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Failed to set sysclk to %uHz: %d", freq, ret); ++ return ret; ++ } + +- return ret; ++ iface->mclk_rate = freq; ++ ++ return 0; + } + + static const unsigned int hw_channels[] = {2, 8}; +@@ -294,10 +323,45 @@ static const struct snd_pcm_hw_constraint_list hw_channel_constraints = { + .mask = 0, + }; + ++static int aiu_encoder_i2s_pcm_hw_rule(struct snd_pcm_hw_params *params, ++ struct snd_pcm_hw_rule *rule) ++{ ++ struct gx_stream *other = rule->private; ++ struct snd_interval *ch = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); ++ /* ++ * The quirk is technically based on the significant bits whereas here ++ * we're using the physical width for simplicity. This works because ++ * S16_LE is the only format supported by this encoder that has: ++ * significant bits = physical width = 16-bits ++ */ ++ struct snd_interval *phys_width = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); ++ struct snd_interval new_i; ++ ++ if (other->channels == 0) ++ return 0; ++ ++ snd_interval_any(&new_i); ++ ++ if (rule->var == SNDRV_PCM_HW_PARAM_CHANNELS) { ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 8; ++ else if (snd_interval_single(phys_width) && phys_width->min == 16) ++ new_i.max = 2; /* Force 2ch */ ++ } else { /* SNDRV_PCM_HW_PARAM_SAMPLE_BITS */ ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 16; ++ else if (snd_interval_single(ch) && ch->min == 8) ++ new_i.min = 17; /* Request physical width > 16 bits */ ++ } ++ ++ return snd_interval_refine(hw_param_interval(params, rule->var), &new_i); ++} ++ + static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); + int ret; + + /* Make sure the encoder gets either 2 or 8 channels */ +@@ -305,15 +369,79 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_channel_constraints); + if (ret) { +- dev_err(dai->dev, "adding channels constraints failed\n"); ++ dev_err(dai->dev, "adding channels constraints failed: %d\n", ret); + return ret; + } + +- ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks); +- if (ret) +- dev_err(dai->dev, "failed to enable i2s clocks\n"); ++ /* ++ * If DAI supports both playback and capture streams ensure the bs-quirk is ++ * handled correctly. ++ * This is only valid for GX platforms (has_clk_ctrl_more_i2s_div=true). ++ */ ++ if (aiu->platform->has_clk_ctrl_more_i2s_div && other_stream) { ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; + +- return ret; ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Enable only clocks which are required for the interface internal ++ * logic. MCLK is enabled/disabled from the formatter and the I2S ++ * divider is enabled/disabled in "hw_params"/"hw_free", respectively. ++ */ ++ ret = clk_prepare_enable(aiu->i2s.clks[PCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable PCLK: %d\n", ret); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable MIXER: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[AOCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable AOCLK: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ++ /* ++ * We're always operating in split mode for the playback stream. ++ * ++ * This setting arguably belong to the 'aiu-formatter', but it's kept ++ * here for backward compatibility reason. At reset the I2S encoder ++ * operates in normal mode which would only support 8ch, but by default ++ * only 2ch are enabled. If a playback stream is started without ++ * changing to split mode, then the I2S encoder doesn't consume audio ++ * samples and the playback fails. ++ * Moving this to 'aiu-formatter' would cause the split mode to be set ++ * only when the formatter is enabled, which doesn't happen at boot as ++ * the default value for "HDMI CTRL SRC" is "DISABLED". ++ */ ++ ret = snd_soc_component_update_bits(dai->component, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT); ++ if (ret < 0) ++ dev_err(dai->dev, "failed to update AIU_I2S_SOURCE_DESC: %d", ret); ++ ++ return 0; + } + + static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, +@@ -321,14 +449,89 @@ static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); + +- clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks); ++ clk_disable_unprepare(aiu->i2s.clks[AOCLK].clk); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++} ++ ++static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, ++ int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ ret = gx_stream_start(ts); ++ break; ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ case SNDRV_PCM_TRIGGER_STOP: ++ gx_stream_stop(ts); ++ ret = 0; ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai) ++{ ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ ts = snd_soc_dai_dma_data_get(dai, stream); ++ if (ts) ++ gx_stream_free(ts); ++ ++ snd_soc_dai_dma_data_set(dai, stream, NULL); ++ } ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai) ++{ ++ struct aiu *aiu = snd_soc_dai_get_drvdata(dai); ++ struct gx_iface *iface = &aiu->i2s.iface; ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ if (!snd_soc_dai_get_widget(dai, stream)) ++ continue; ++ ++ ts = gx_stream_alloc(iface); ++ if (!ts) { ++ aiu_encoder_i2s_remove_dai(dai); ++ return -ENOMEM; ++ } ++ snd_soc_dai_dma_data_set(dai, stream, ts); ++ } ++ ++ iface->mclk = aiu->i2s.clks[MCLK].clk; ++ iface->mclk_rate = clk_get_rate(iface->mclk); ++ ++ return 0; + } + + const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = { ++ .probe = aiu_encoder_i2s_probe_dai, ++ .remove = aiu_encoder_i2s_remove_dai, + .hw_params = aiu_encoder_i2s_hw_params, ++ .prepare = aiu_encoder_i2s_prepare, + .hw_free = aiu_encoder_i2s_hw_free, + .set_fmt = aiu_encoder_i2s_set_fmt, + .set_sysclk = aiu_encoder_i2s_set_sysclk, + .startup = aiu_encoder_i2s_startup, + .shutdown = aiu_encoder_i2s_shutdown, ++ .trigger = aiu_encoder_i2s_trigger, + }; +diff --git a/sound/soc/meson/aiu-formatter-i2s.c b/sound/soc/meson/aiu-formatter-i2s.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/aiu-formatter-i2s.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++ ++#include "aiu.h" ++#include "gx-formatter.h" ++ ++#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) ++#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) ++#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) ++ ++#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) ++ ++static struct snd_soc_dai * ++aiu_formatter_i2s_get_be(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dapm_path *p; ++ struct snd_soc_dai *be; ++ ++ snd_soc_dapm_widget_for_each_sink_path(w, p) { ++ if (!p->connect) ++ continue; ++ ++ if (p->sink->id == snd_soc_dapm_dai_in) ++ return (struct snd_soc_dai *)p->sink->priv; ++ ++ be = aiu_formatter_i2s_get_be(p->sink); ++ if (be) ++ return be; ++ } ++ ++ return NULL; ++} ++ ++static struct gx_stream * ++aiu_formatter_i2s_get_stream(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dai *be = aiu_formatter_i2s_get_be(w); ++ ++ if (!be) ++ return NULL; ++ ++ return snd_soc_dai_dma_data_get_playback(be); ++} ++ ++static int aiu_formatter_i2s_prepare(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts) ++{ ++ /* Always operate in split (classic interleaved) mode */ ++ unsigned int desc = 0; ++ ++ /* ++ * Pipeline reset is already implemented in aiu_fifo_i2s_trigger() at ++ * trigger time. ++ */ ++ ++ switch (ts->physical_width) { ++ case 16: /* Nothing to do */ ++ break; ++ ++ case 32: ++ desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ switch (ts->channels) { ++ case 2: /* Nothing to do */ ++ break; ++ case 8: ++ desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(map, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_8CH | ++ AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT, ++ desc); ++ ++ /* Send data MSB first */ ++ regmap_update_bits(map, AIU_I2S_DAC_CFG, ++ AIU_I2S_DAC_CFG_MSB_FIRST, ++ AIU_I2S_DAC_CFG_MSB_FIRST); ++ ++ return 0; ++} ++ ++const struct gx_formatter_ops aiu_formatter_i2s_ops = { ++ .get_stream = aiu_formatter_i2s_get_stream, ++ .prepare = aiu_formatter_i2s_prepare, ++}; +diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c +--- a/sound/soc/meson/aiu.c ++++ b/sound/soc/meson/aiu.c +@@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC, + static const struct snd_kcontrol_new aiu_spdif_encode_mux = + SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum); + +-static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { +- SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, +- &aiu_spdif_encode_mux), ++#define AIU_WIDGET_SPDIF_SRC_SEL 0 ++#define AIU_WIDGET_I2S_FORMATTER 1 ++ ++static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { ++ [AIU_WIDGET_SPDIF_SRC_SEL] = ++ SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, ++ &aiu_spdif_encode_mux), ++ [AIU_WIDGET_I2S_FORMATTER] = ++ SND_SOC_DAPM_PGA_E("I2S Formatter", SND_SOC_NOPM, 0, 0, NULL, 0, ++ gx_formatter_event, ++ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), + }; + + static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = { +- { "I2S Encoder Playback", NULL, "I2S FIFO Playback" }, ++ { "I2S Formatter", NULL, "I2S FIFO Playback" }, ++ { "I2S Encoder Playback", NULL, "I2S Formatter" }, + { "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" }, + { "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" }, + { "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" }, +@@ -145,6 +154,7 @@ static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = { + .formats = AIU_FORMATS, + }, + .ops = &aiu_encoder_i2s_dai_ops, ++ .symmetric_rate = 1, + }, + [CPU_SPDIF_ENCODER] = { + .name = "SPDIF Encoder", +@@ -172,6 +182,11 @@ static const struct regmap_config aiu_regmap_cfg = { + .max_register = 0x2ac, + }; + ++static const struct gx_formatter_driver aiu_formatter_i2s_drv = { ++ .regmap_cfg = &aiu_regmap_cfg, ++ .ops = &aiu_formatter_i2s_ops, ++}; ++ + static int aiu_clk_bulk_get(struct device *dev, + const char * const *ids, + unsigned int num, +@@ -282,6 +297,14 @@ static int aiu_probe(struct platform_device *pdev) + if (ret) + return ret; + ++ /* Allocate the aiu-formatter into its widget */ ++ ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER], ++ &aiu_formatter_i2s_drv, map); ++ if (ret) { ++ dev_err(dev, "Failed to allocate aiu formatter\n"); ++ goto err; ++ } ++ + /* Register the cpu component of the aiu */ + ret = snd_soc_register_component(dev, &aiu_cpu_component, + aiu_cpu_dai_drv, +@@ -310,12 +333,14 @@ static int aiu_probe(struct platform_device *pdev) + + return 0; + err: ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(dev); + return ret; + } + + static void aiu_remove(struct platform_device *pdev) + { ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(&pdev->dev); + } + +diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h +--- a/sound/soc/meson/aiu.h ++++ b/sound/soc/meson/aiu.h +@@ -7,6 +7,8 @@ + #ifndef _MESON_AIU_H + #define _MESON_AIU_H + ++#include "gx-formatter.h" ++ + struct clk; + struct clk_bulk_data; + struct device; +@@ -25,6 +27,7 @@ struct aiu_interface { + struct clk_bulk_data *clks; + unsigned int clk_num; + int irq; ++ struct gx_iface iface; + }; + + struct aiu_platform_data { +@@ -58,6 +61,7 @@ extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops; ++extern const struct gx_formatter_ops aiu_formatter_i2s_ops; + + #define AIU_IEC958_BPF 0x000 + #define AIU_958_MISC 0x010 +diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c +--- a/sound/soc/meson/axg-card.c ++++ b/sound/soc/meson/axg-card.c +@@ -107,6 +107,7 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + struct snd_soc_dai_link *pad; + struct snd_soc_dai_link *lb; + struct snd_soc_dai_link_component *dlc; ++ struct device *dev = card->dev; + int ret; + + /* extend links */ +@@ -117,11 +118,11 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + pad = &card->dai_link[*index]; + lb = &card->dai_link[*index + 1]; + +- lb->name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-lb", pad->name); ++ lb->name = devm_kasprintf(dev, GFP_KERNEL, "%s-lb", pad->name); + if (!lb->name) + return -ENOMEM; + +- dlc = devm_kzalloc(card->dev, sizeof(*dlc), GFP_KERNEL); ++ dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + +@@ -158,13 +159,14 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + struct device_node *node, + struct axg_dai_link_tdm_data *be) + { ++ struct device *dev = card->dev; + char propname[32]; + u32 tx, rx; + int i; + +- be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->tx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->tx_mask), GFP_KERNEL); +- be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->rx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->rx_mask), GFP_KERNEL); + if (!be->tx_mask || !be->rx_mask) + return -ENOMEM; +@@ -191,7 +193,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + + /* ... but the interface should at least have one direction */ + if (!tx && !rx) { +- dev_err(card->dev, "tdm link has no cpu slots\n"); ++ dev_err(dev, "tdm link has no cpu slots\n"); + return -EINVAL; + } + +@@ -207,7 +209,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + * Error if the slots can't accommodate the largest mask or + * if it is just too big + */ +- dev_err(card->dev, "bad slot number\n"); ++ dev_err(dev, "bad slot number\n"); + return -EINVAL; + } + +@@ -222,8 +224,9 @@ static int axg_card_parse_codecs_masks(struct snd_soc_card *card, + struct axg_dai_link_tdm_data *be) + { + struct axg_dai_link_tdm_mask *codec_mask; ++ struct device *dev = card->dev; + +- codec_mask = devm_kcalloc(card->dev, link->num_codecs, ++ codec_mask = devm_kcalloc(dev, link->num_codecs, + sizeof(*codec_mask), GFP_KERNEL); + if (!codec_mask) + return -ENOMEM; +@@ -249,10 +252,11 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct axg_dai_link_tdm_data *be; ++ struct device *dev = card->dev; + int ret; + + /* Allocate tdm link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -266,7 +270,7 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + + ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); + if (ret) { +- dev_err(card->dev, "error parsing tdm link slots\n"); ++ dev_err(dev, "error parsing tdm link slots\n"); + return ret; + } + +@@ -310,9 +314,10 @@ static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/g12a-toacodec.c b/sound/soc/meson/g12a-toacodec.c +--- a/sound/soc/meson/g12a-toacodec.c ++++ b/sound/soc/meson/g12a-toacodec.c +@@ -312,7 +312,7 @@ static int g12a_toacodec_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/g12a-tohdmitx.c b/sound/soc/meson/g12a-tohdmitx.c +--- a/sound/soc/meson/g12a-tohdmitx.c ++++ b/sound/soc/meson/g12a-tohdmitx.c +@@ -251,7 +251,7 @@ static int g12a_tohdmitx_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/gx-card.c b/sound/soc/meson/gx-card.c +--- a/sound/soc/meson/gx-card.c ++++ b/sound/soc/meson/gx-card.c +@@ -48,9 +48,10 @@ static int gx_card_parse_i2s(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct gx_dai_link_i2s_data *be; ++ struct device *dev = card->dev; + + /* Allocate i2s link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -81,9 +82,10 @@ static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.c +@@ -0,0 +1,282 @@ ++// SPDX-License-Identifier: (GPL-2.0 OR MIT) ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++#include ++ ++#include "gx-formatter.h" ++ ++struct gx_formatter { ++ struct list_head list; ++ struct gx_stream *stream; ++ const struct gx_formatter_driver *drv; ++ bool enabled; ++ struct regmap *map; ++}; ++ ++static int gx_formatter_enable(struct gx_formatter *formatter) ++{ ++ int ret; ++ ++ /* Do nothing if the formatter is already enabled */ ++ if (formatter->enabled) ++ return 0; ++ ++ /* Setup the stream parameter in the formatter */ ++ if (formatter->drv->ops->prepare) { ++ ret = formatter->drv->ops->prepare(formatter->map, ++ formatter->drv->quirks, ++ formatter->stream); ++ if (ret) ++ return ret; ++ } ++ ++ /* Finally, actually enable the formatter */ ++ if (formatter->drv->ops->enable) ++ formatter->drv->ops->enable(formatter->map); ++ ++ formatter->enabled = true; ++ ++ return 0; ++} ++ ++static void gx_formatter_disable(struct gx_formatter *formatter) ++{ ++ /* Do nothing if the formatter is already disabled */ ++ if (!formatter->enabled) ++ return; ++ ++ if (formatter->drv->ops->disable) ++ formatter->drv->ops->disable(formatter->map); ++ ++ formatter->enabled = false; ++} ++ ++static int gx_formatter_attach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Catch up if the stream is already running when we attach */ ++ if (ts->ready) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ list_add_tail(&formatter->list, &ts->formatter_list); ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++static void gx_formatter_detach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ ++ if (!ts) ++ return; ++ ++ mutex_lock(&ts->lock); ++ list_del(&formatter->list); ++ mutex_unlock(&ts->lock); ++ ++ gx_formatter_disable(formatter); ++} ++ ++static int gx_formatter_power_up(struct gx_formatter *formatter, ++ struct snd_soc_dapm_widget *w) ++{ ++ struct gx_stream *ts = formatter->drv->ops->get_stream(w); ++ int ret; ++ ++ /* ++ * If we don't get a stream at this stage, it would mean that the ++ * widget is powering up but is not attached to any backend DAI. ++ * It should not happen, ever ! ++ */ ++ if (WARN_ON(!ts)) ++ return -ENODEV; ++ ++ formatter->stream = ts; ++ INIT_LIST_HEAD(&formatter->list); ++ ret = gx_formatter_attach(formatter); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void gx_formatter_power_down(struct gx_formatter *formatter) ++{ ++ gx_formatter_detach(formatter); ++ formatter->stream = NULL; ++} ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event) ++{ ++ struct snd_soc_component *c; ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ c = snd_soc_dapm_to_component(w->dapm); ++ ++ if (w->priv) ++ formatter = w->priv; ++ else ++ formatter = snd_soc_component_get_drvdata(c); ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ ret = gx_formatter_power_up(formatter, w); ++ break; ++ ++ case SND_SOC_DAPM_PRE_PMD: ++ gx_formatter_power_down(formatter); ++ break; ++ ++ default: ++ dev_err(c->dev, "Unexpected event %d\n", event); ++ return -EINVAL; ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_event); ++ ++int gx_formatter_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct gx_formatter_driver *drv; ++ struct gx_formatter *formatter; ++ void __iomem *regs; ++ ++ drv = of_device_get_match_data(dev); ++ if (!drv) { ++ dev_err(dev, "failed to match device\n"); ++ return -ENODEV; ++ } ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ platform_set_drvdata(pdev, formatter); ++ formatter->drv = drv; ++ ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); ++ if (IS_ERR(formatter->map)) { ++ dev_err(dev, "failed to init regmap: %ld\n", ++ PTR_ERR(formatter->map)); ++ return PTR_ERR(formatter->map); ++ } ++ ++ return devm_snd_soc_register_component(dev, drv->component_drv, ++ NULL, 0); ++} ++EXPORT_SYMBOL_GPL(gx_formatter_probe); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap) ++{ ++ struct gx_formatter *formatter; ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ ++ formatter->drv = drv; ++ formatter->map = regmap; ++ ++ w->priv = formatter; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_create); ++ ++int gx_stream_start(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Start all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ ts->ready = true; ++ ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_stream_start); ++ ++void gx_stream_stop(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ ++ mutex_lock(&ts->lock); ++ ts->ready = false; ++ ++ /* Stop all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ gx_formatter_disable(formatter); ++ } ++ ++ mutex_unlock(&ts->lock); ++} ++EXPORT_SYMBOL_GPL(gx_stream_stop); ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface) ++{ ++ struct gx_stream *ts; ++ ++ ts = kzalloc(sizeof(*ts), GFP_KERNEL); ++ if (ts) { ++ INIT_LIST_HEAD(&ts->formatter_list); ++ mutex_init(&ts->lock); ++ ts->iface = iface; ++ } ++ ++ return ts; ++} ++EXPORT_SYMBOL_GPL(gx_stream_alloc); ++ ++void gx_stream_free(struct gx_stream *ts) ++{ ++ /* ++ * If the list is not empty, it would mean that one of the formatter ++ * widget is still powered and attached to the interface while we ++ * are removing the TDM DAI. It should not be possible ++ */ ++ WARN_ON(!list_empty(&ts->formatter_list)); ++ mutex_destroy(&ts->lock); ++ kfree(ts); ++} ++EXPORT_SYMBOL_GPL(gx_stream_free); ++ ++MODULE_DESCRIPTION("Amlogic GX formatter driver"); ++MODULE_AUTHOR("Valerio Setti "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/meson/gx-formatter.h b/sound/soc/meson/gx-formatter.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.h +@@ -0,0 +1,56 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_FORMATTER_H ++#define _MESON_GX_FORMATTER_H ++ ++#include "gx-interface.h" ++ ++struct platform_device; ++struct regmap; ++struct snd_soc_dapm_widget; ++struct snd_kcontrol; ++ ++struct gx_formatter_hw { ++ unsigned int skew_offset; ++}; ++ ++struct gx_formatter_ops { ++ struct gx_stream *(*get_stream)(struct snd_soc_dapm_widget *w); ++ void (*enable)(struct regmap *map); ++ void (*disable)(struct regmap *map); ++ int (*prepare)(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts); ++}; ++ ++struct gx_formatter_driver { ++ const struct snd_soc_component_driver *component_drv; ++ const struct regmap_config *regmap_cfg; ++ const struct gx_formatter_ops *ops; ++ const struct gx_formatter_hw *quirks; ++}; ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event); ++int gx_formatter_probe(struct platform_device *pdev); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap); ++ ++/* ++ * Formatter data is already freed when the associated device is removed, ++ * so we just need to remove the pointer from the widget. ++ */ ++static inline void gx_formatter_free(struct snd_soc_dapm_widget *w) ++{ ++ w->priv = NULL; ++} ++ ++#endif /* _MESON_GX_FORMATTER_H */ +diff --git a/sound/soc/meson/gx-interface.h b/sound/soc/meson/gx-interface.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-interface.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_INTERFACE_H ++#define _MESON_GX_INTERFACE_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++struct gx_iface { ++ struct clk *mclk; ++ unsigned long mclk_rate; ++ ++ /* format is common to all the DAIs of the iface */ ++ unsigned int fmt; ++}; ++ ++struct gx_stream { ++ struct gx_iface *iface; ++ struct list_head formatter_list; ++ struct mutex lock; ++ unsigned int channels; ++ unsigned int width; ++ unsigned int physical_width; ++ bool ready; ++ ++ /* For continuous clock tracking */ ++ bool clk_enabled; ++}; ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface); ++void gx_stream_free(struct gx_stream *ts); ++int gx_stream_start(struct gx_stream *ts); ++void gx_stream_stop(struct gx_stream *ts); ++ ++#endif /* _MESON_GX_INTERFACE_H */ +diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c +--- a/sound/soc/meson/meson-card-utils.c ++++ b/sound/soc/meson/meson-card-utils.c +@@ -71,6 +71,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + struct device_node *node, + struct snd_soc_dai_link_component *dlc) + { ++ struct device *dev = card->dev; + int ret; + + if (!dlc || !node) +@@ -78,7 +79,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + + ret = snd_soc_of_get_dlc(node, NULL, dlc, 0); + if (ret) +- return dev_err_probe(card->dev, ret, "can't parse dai\n"); ++ return dev_err_probe(dev, ret, "can't parse dai\n"); + + return ret; + } +@@ -89,7 +90,8 @@ static int meson_card_set_link_name(struct snd_soc_card *card, + struct device_node *node, + const char *prefix) + { +- char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", ++ struct device *dev = card->dev; ++ char *name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", + prefix, node->full_name); + if (!name) + return -ENOMEM; +@@ -132,16 +134,17 @@ int meson_card_set_be_link(struct snd_soc_card *card, + struct device_node *node) + { + struct snd_soc_dai_link_component *codec; ++ struct device *dev = card->dev; + int ret, num_codecs; + + num_codecs = of_get_child_count(node); + if (!num_codecs) { +- dev_err(card->dev, "be link %s has no codec\n", ++ dev_err(dev, "be link %s has no codec\n", + node->full_name); + return -EINVAL; + } + +- codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); ++ codec = devm_kcalloc(dev, num_codecs, sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + +@@ -158,7 +161,7 @@ int meson_card_set_be_link(struct snd_soc_card *card, + + ret = meson_card_set_link_name(card, link, node, "be"); + if (ret) +- dev_err(card->dev, "error setting %pOFn link name\n", node); ++ dev_err(dev, "error setting %pOFn link name\n", node); + + return ret; + } +@@ -189,12 +192,13 @@ EXPORT_SYMBOL_GPL(meson_card_set_fe_link); + static int meson_card_add_links(struct snd_soc_card *card) + { + struct meson_card *priv = snd_soc_card_get_drvdata(card); +- struct device_node *node = card->dev->of_node; ++ struct device *dev = card->dev; ++ struct device_node *node = dev->of_node; + int num, i, ret; + + num = of_get_child_count(node); + if (!num) { +- dev_err(card->dev, "card has no links\n"); ++ dev_err(dev, "card has no links\n"); + return -EINVAL; + } + +@@ -219,8 +223,10 @@ static int meson_card_parse_of_optional(struct snd_soc_card *card, + int (*func)(struct snd_soc_card *c, + const char *p)) + { ++ struct device *dev = card->dev; ++ + /* If property is not provided, don't fail ... */ +- if (!of_property_present(card->dev->of_node, propname)) ++ if (!of_property_present(dev->of_node, propname)) + return 0; + + /* ... but do fail if it is provided and the parsing fails */ +diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c +--- a/sound/soc/meson/t9015.c ++++ b/sound/soc/meson/t9015.c +@@ -78,8 +78,14 @@ static int t9015_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static const u64 t9015_dai_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops t9015_dai_ops = { + .set_fmt = t9015_dai_set_fmt, ++ .auto_selectable_formats = &t9015_dai_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver t9015_dai = { +@@ -265,10 +271,8 @@ static int t9015_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(priv->avdd), "failed to AVDD\n"); + + ret = device_reset(dev); +- if (ret) { +- dev_err(dev, "reset failed\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { +diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c +--- a/sound/soc/mxs/mxs-saif.c ++++ b/sound/soc/mxs/mxs-saif.c +@@ -826,12 +826,9 @@ static int mxs_saif_probe(struct platform_device *pdev) + mxs_saif[saif->id] = saif; + + saif->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(saif->clk)) { +- ret = PTR_ERR(saif->clk); +- dev_err(&pdev->dev, "Cannot get the clock: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(saif->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(saif->clk), ++ "Cannot get the clock\n"); + + saif->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(saif->base)) +@@ -844,10 +841,8 @@ static int mxs_saif_probe(struct platform_device *pdev) + saif->dev = &pdev->dev; + ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0, + dev_name(&pdev->dev), saif); +- if (ret) { +- dev_err(&pdev->dev, "failed to request irq\n"); ++ if (ret) + return ret; +- } + + platform_set_drvdata(pdev, saif); + +@@ -860,16 +855,12 @@ static int mxs_saif_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &mxs_saif_component, + &mxs_saif_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "register DAI failed\n"); ++ if (ret) + return ret; +- } + + ret = mxs_pcm_platform_register(&pdev->dev); +- if (ret) { +- dev_err(&pdev->dev, "register PCM failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c +--- a/sound/soc/mxs/mxs-sgtl5000.c ++++ b/sound/soc/mxs/mxs-sgtl5000.c +@@ -155,8 +155,6 @@ static int mxs_sgtl5000_probe(struct platform_device *pdev) + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio-routing (%d)\n", +- ret); + mxs_saif_put_mclk(0); + return ret; + } +diff --git a/sound/soc/pxa/pxa2xx-ac97-lib.c b/sound/soc/pxa/pxa2xx-ac97-lib.c +--- a/sound/soc/pxa/pxa2xx-ac97-lib.c ++++ b/sound/soc/pxa/pxa2xx-ac97-lib.c +@@ -316,10 +316,8 @@ int pxa2xx_ac97_hw_probe(struct platform_device *dev) + int irq; + + ac97_reg_base = devm_platform_ioremap_resource(dev, 0); +- if (IS_ERR(ac97_reg_base)) { +- dev_err(&dev->dev, "Missing MMIO resource\n"); ++ if (IS_ERR(ac97_reg_base)) + return PTR_ERR(ac97_reg_base); +- } + + if (cpu_is_pxa27x()) { + /* Assert reset using GPIOD_OUT_HIGH, because reset is GPIO_ACTIVE_LOW */ +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -23,6 +23,161 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { + SND_SOC_DAPM_SPK("DP7 Jack", NULL), + }; + ++static struct device_node *qcom_snd_get_link_node(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_card *card = rtd->card; ++ struct of_phandle_args args; ++ int ret; ++ ++ if (!card->dev || !card->dev->of_node) ++ return NULL; ++ ++ for_each_available_child_of_node_scoped(card->dev->of_node, np) { ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(np, "cpu"); ++ ++ if (!cpu_np) ++ continue; ++ ++ ret = of_parse_phandle_with_args(cpu_np, "sound-dai", "#sound-dai-cells", 0, ++ &args); ++ if (ret) ++ continue; ++ ++ if (args.np == rtd->dai_link->cpus[0].of_node && ++ args.args_count == 1 && args.args[0] == cpu_dai->id) { ++ of_node_put(args.np); ++ return of_node_get(np); ++ } ++ ++ of_node_put(args.np); ++ } ++ ++ return NULL; ++} ++ ++static int qcom_snd_parse_tdm_slot(struct device_node *np, ++ struct qcom_snd_tdm_slot_cfg *cfg) ++{ ++ memset(cfg, 0, sizeof(*cfg)); ++ ++ return snd_soc_of_parse_tdm_slot(np, &cfg->tx_mask, &cfg->rx_mask, ++ &cfg->slots, &cfg->slot_width); ++} ++ ++static int qcom_snd_normalize_tdm_slots(struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ unsigned int slots; ++ unsigned int slot_width; ++ ++ if (cpu_cfg->slots && codec_cfg->slots && cpu_cfg->slots != codec_cfg->slots) ++ return -EINVAL; ++ ++ if (cpu_cfg->slot_width && codec_cfg->slot_width && ++ cpu_cfg->slot_width != codec_cfg->slot_width) ++ return -EINVAL; ++ ++ slots = cpu_cfg->slots ?: codec_cfg->slots; ++ if (!slots) ++ return 0; ++ ++ slot_width = cpu_cfg->slot_width ?: codec_cfg->slot_width; ++ if (!slot_width) ++ return -EINVAL; ++ ++ cpu_cfg->slots = slots; ++ codec_cfg->slots = slots; ++ cpu_cfg->slot_width = slot_width; ++ codec_cfg->slot_width = slot_width; ++ ++ return 0; ++} ++ ++static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct device_node *link_np __free(device_node) = qcom_snd_get_link_node(rtd); ++ int ret; ++ ++ if (!link_np) ++ return -EINVAL; ++ ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(link_np, "cpu"); ++ struct device_node *codec_np __free(device_node) = ++ of_get_child_by_name(link_np, "codec"); ++ if (!cpu_np || !codec_np) ++ return -EINVAL; ++ ++ ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_parse_tdm_slot(codec_np, codec_cfg); ++} ++ ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ int ret; ++ ++ ret = qcom_snd_parse_dai_tdm_slots(rtd, cpu_cfg, codec_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_normalize_tdm_slots(cpu_cfg, codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_get_dai_tdm_slots); ++ ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ int i; ++ int ret; ++ ++ if (!cpu_cfg->slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_tdm_slot(cpu_dai, cpu_cfg->tx_mask, cpu_cfg->rx_mask, ++ cpu_cfg->slots, cpu_cfg->slot_width); ++ if (ret) ++ return ret; ++ ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_tdm_slot(codec_dai, ++ codec_cfg->tx_mask, ++ codec_cfg->rx_mask, ++ codec_cfg->slots, ++ codec_cfg->slot_width); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots_cfg); ++ ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ int ret; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots); ++ + int qcom_snd_parse_of(struct snd_soc_card *card) + { + struct device *dev = card->dev; +@@ -91,7 +246,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { +- dev_err(card->dev, "error getting codec dai_link name\n"); ++ dev_err(dev, "error getting codec dai_link name\n"); + return ret; + } + +@@ -109,7 +264,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); + if (ret) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: error getting cpu dai name\n", link->name); + return ret; + } +@@ -126,7 +281,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + "sound-dai", + 0); + if (!link->platforms->of_node) { +- dev_err(card->dev, "%s: platform dai not found\n", link->name); ++ dev_err(dev, "%s: platform dai not found\n", link->name); + return -EINVAL; + } + } else { +@@ -136,7 +291,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + if (codec) { + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: codec dai not found\n", link->name); + return ret; + } +@@ -215,6 +370,7 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + } + + switch (cpu_dai->id) { ++ case LPI_MI2S_RX_0: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: +diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h +--- a/sound/soc/qcom/common.h ++++ b/sound/soc/qcom/common.h +@@ -7,9 +7,23 @@ + #include + #include + +-#define LPASS_MAX_PORT (SENARY_MI2S_TX + 1) ++#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1) ++ ++struct qcom_snd_tdm_slot_cfg { ++ unsigned int tx_mask; ++ unsigned int rx_mask; ++ unsigned int slots; ++ unsigned int slot_width; ++}; + + int qcom_snd_parse_of(struct snd_soc_card *card); ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); + int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); + int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, +diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c +--- a/sound/soc/qcom/qdsp6/audioreach.c ++++ b/sound/soc/qcom/qdsp6/audioreach.c +@@ -152,6 +152,13 @@ struct apm_i2s_module_intf_cfg { + + #define APM_I2S_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_i2s_module_intf_cfg), 8) + ++struct apm_audio_if_module_intf_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_audio_if_intf_cfg cfg; ++} __packed; ++ ++#define APM_AUDIO_IF_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_audio_if_module_intf_cfg), 8) ++ + struct apm_module_hw_ep_mf_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_mf mf; +@@ -168,6 +175,13 @@ struct apm_module_frame_size_factor_cfg { + + #define APM_FS_CFG_PSIZE ALIGN(sizeof(struct apm_module_frame_size_factor_cfg), 8) + ++struct apm_module_hw_ep_frame_duration_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_hw_ep_frame_duration frame_duration; ++} __packed; ++ ++#define APM_HW_EP_FRAME_DURATION_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_frame_duration_cfg), 8) ++ + struct apm_module_hw_ep_power_mode_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_power_mode_cfg power_mode; +@@ -703,6 +717,7 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + int pm_sz = APM_HW_EP_PMODE_CFG_PSIZE; + int size = ic_sz + ep_sz + fs_sz + pm_sz; + void *p; ++ int i; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) +@@ -741,7 +756,12 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + + intf_cfg->cfg.lpaif_type = module->hw_interface_type; + intf_cfg->cfg.intf_index = module->hw_interface_idx; +- intf_cfg->cfg.active_channels_mask = (1 << cfg->num_channels) - 1; ++ intf_cfg->cfg.active_channels_mask = 0; ++ /* Convert the physical channel mapping into a bit field */ ++ for (i = 0; i < AR_PCM_MAX_NUM_CHANNEL; i++) ++ if (cfg->channel_map[i]) ++ intf_cfg->cfg.active_channels_mask |= BIT(i); ++ + p += ic_sz; + + pm_cfg = p; +@@ -840,7 +860,7 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + uint32_t num_channels = cfg->num_channels; + int payload_size = APM_MFC_CFG_PSIZE(media_format, num_channels) + + APM_MODULE_PARAM_DATA_SIZE; +- int i; ++ int i, j; + void *p; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); +@@ -860,8 +880,12 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + media_format->sample_rate = cfg->sample_rate; + media_format->bit_width = cfg->bit_width; + media_format->num_channels = cfg->num_channels; +- for (i = 0; i < num_channels; i++) +- media_format->channel_mapping[i] = cfg->channel_map[i]; ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!cfg->channel_map[i]) ++ continue; ++ media_format->channel_mapping[j++] = cfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1042,6 +1066,81 @@ static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } + ++static int audioreach_audio_if_set_media_format(struct q6apm_graph *graph, ++ const struct audioreach_module *module, ++ const struct audioreach_module_config *cfg) ++{ ++ struct apm_module_hw_ep_frame_duration_cfg *fd_cfg; ++ struct apm_module_param_data *param_data; ++ struct apm_audio_if_module_intf_cfg *intf_cfg; ++ struct apm_module_hw_ep_mf_cfg *hw_cfg; ++ int ic_sz = APM_AUDIO_IF_INTF_CFG_PSIZE; ++ int ep_sz = APM_HW_EP_CFG_PSIZE; ++ int fd_sz = APM_HW_EP_FRAME_DURATION_PSIZE; ++ int size = ic_sz + ep_sz + fd_sz; ++ u32 slot_mask = cfg->slot_mask ? cfg->slot_mask : module->slot_mask; ++ u16 nslots_per_frame = cfg->nslots_per_frame ? ++ (u16)cfg->nslots_per_frame : module->nslots_per_frame; ++ u16 slot_width = cfg->slot_width ? (u16)cfg->slot_width : module->slot_width; ++ void *p; ++ ++ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); ++ if (IS_ERR(pkt)) ++ return PTR_ERR(pkt); ++ ++ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; ++ intf_cfg = p; ++ ++ param_data = &intf_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_AUDIO_IF_INTF_CFG; ++ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; ++ intf_cfg->cfg.qaif_type = module->qaif_type; ++ intf_cfg->cfg.intf_idx = (u16)module->hw_interface_idx; ++ intf_cfg->cfg.intf_mode = module->intf_mode; ++ intf_cfg->cfg.ctrl_data_out_enable = module->ctrl_data_out_enable; ++ intf_cfg->cfg.active_slot_mask = slot_mask; ++ intf_cfg->cfg.nslots_per_frame = nslots_per_frame; ++ intf_cfg->cfg.slot_width = slot_width; ++ intf_cfg->cfg.active_lane_mask = module->active_lane_mask; ++ intf_cfg->cfg.frame_sync_rate = module->frame_sync_rate; ++ intf_cfg->cfg.frame_sync_src = module->sync_src; ++ intf_cfg->cfg.frame_sync_mode = module->sync_mode; ++ intf_cfg->cfg.invert_frame_sync_pulse = module->ctrl_invert_sync_pulse; ++ intf_cfg->cfg.frame_sync_data_delay = module->ctrl_sync_data_delay; ++ intf_cfg->cfg.bit_clk_type = module->bit_clk_type; ++ intf_cfg->cfg.inv_int_bit_clk = module->inv_int_bit_clk; ++ intf_cfg->cfg.inv_ext_bit_clk = module->inv_ext_bit_clk; ++ ++ p += ic_sz; ++ hw_cfg = p; ++ param_data = &hw_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_MF_CFG; ++ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; ++ ++ hw_cfg->mf.sample_rate = cfg->sample_rate; ++ hw_cfg->mf.bit_width = cfg->bit_width; ++ hw_cfg->mf.num_channels = cfg->num_channels; ++ hw_cfg->mf.data_format = module->data_format; ++ ++ p += ep_sz; ++ fd_cfg = p; ++ param_data = &fd_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_FRAME_DURATION; ++ param_data->param_size = fd_sz - APM_MODULE_PARAM_DATA_SIZE; ++ fd_cfg->frame_duration.frame_duration_in_us = AUDIO_IF_FRAME_DURATION_US; ++ fd_cfg->frame_duration.allow_frame_duration_normalization = AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE; ++ fd_cfg->frame_duration.min_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MIN_US; ++ fd_cfg->frame_duration.max_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MAX_US; ++ ++ return q6apm_send_cmd_sync(graph->apm, pkt, 0); ++} ++ + static int audioreach_logging_set_media_format(struct q6apm_graph *graph, + const struct audioreach_module *module) + { +@@ -1080,6 +1179,7 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + struct apm_pcm_module_media_fmt_cmd *cfg; + struct apm_module_param_data *param_data; + int payload_size; ++ int i, j; + + if (num_channels > 4) { + dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels); +@@ -1113,7 +1213,12 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + media_cfg->num_channels = mcfg->num_channels; + media_cfg->q_factor = mcfg->bit_width - 1; + media_cfg->bits_per_sample = mcfg->bit_width; +- memcpy(media_cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < mcfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ media_cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1163,6 +1268,7 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + struct payload_media_fmt_pcm *cfg; + struct media_format *header; + int rc, payload_size; ++ int i, j; + void *p; + + if (num_channels > 4) { +@@ -1202,7 +1308,12 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + cfg->q_factor = mcfg->bit_width - 1; + cfg->endianness = PCM_LITTLE_ENDIAN; + cfg->num_channels = mcfg->num_channels; +- memcpy(cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + } else { + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) +@@ -1279,7 +1390,7 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + struct apm_module_sp_vi_ex_mode_cfg *ex_cfg; + int op_sz, cm_sz, ex_sz; + struct apm_module_param_data *param_data; +- int rc, i, payload_size; ++ int rc, i, payload_size, j; + struct gpr_pkt *pkt; + void *p; + +@@ -1320,14 +1431,19 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + param_data->param_size = cm_sz - APM_MODULE_PARAM_DATA_SIZE; + + cm_cfg->cfg.num_channels = num_channels * 2; +- for (i = 0; i < num_channels; i++) { ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; + /* + * Map speakers into Vsense and then Isense of each channel. + * E.g. for PCM_CHANNEL_FL and PCM_CHANNEL_FR to: + * [1, 2, 3, 4] + */ +- cm_cfg->cfg.channel_mapping[2 * i] = (mcfg->channel_map[i] - 1) * 2 + 1; +- cm_cfg->cfg.channel_mapping[2 * i + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ cm_cfg->cfg.channel_mapping[2 * j] = (mcfg->channel_map[i] - 1) * 2 + 1; ++ cm_cfg->cfg.channel_mapping[2 * j + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ ++ ++j; + } + + p += cm_sz; +@@ -1411,6 +1527,10 @@ int audioreach_set_media_format(struct q6apm_graph *graph, + if (!rc) + rc = audioreach_module_enable(graph, module, true); + break; ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ case MODULE_ID_AUDIO_IF_SINK: ++ rc = audioreach_audio_if_set_media_format(graph, module, cfg); ++ break; + + default: + rc = 0; +diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h +--- a/sound/soc/qcom/qdsp6/audioreach.h ++++ b/sound/soc/qcom/qdsp6/audioreach.h +@@ -36,6 +36,8 @@ struct q6apm_graph; + #define MODULE_ID_SPEAKER_PROTECTION 0x070010E2 + #define MODULE_ID_SPEAKER_PROTECTION_VI 0x070010E3 + #define MODULE_ID_OPUS_DEC 0x07001174 ++#define MODULE_ID_AUDIO_IF_SINK 0x0700117C ++#define MODULE_ID_AUDIO_IF_SOURCE 0x0700117D + + #define APM_CMD_GET_SPF_STATE 0x01001021 + #define APM_CMD_RSP_GET_SPF_STATE 0x02001007 +@@ -544,6 +546,74 @@ struct param_id_i2s_intf_cfg { + #define PORT_ID_I2S_OUPUT 1 + #define I2S_STACK_SIZE 2048 + ++#define PARAM_ID_AUDIO_IF_INTF_CFG 0x08001B11 ++ ++/* ++ * struct param_id_audio_if_intf_cfg - Audio interface configuration ++ * @qaif_type: Audio interface type (e.g. QAIF, QAIF_VA) ++ * @intf_idx: Interface instance index ++ * @intf_mode: Interface operating mode (TDM/PCM/I2S) ++ * @ctrl_data_out_enable: Enable sharing of data-out signal with other masters ++ * @active_slot_mask: Bitmask indicating active slots ++ * @nslots_per_frame: Number of slots per audio frame ++ * @slot_width: Width of each slot in bits ++ * @active_lane_mask: Bitmask of active data lanes ++ * @frame_sync_rate: Frame sync rate in Hz ++ * @frame_sync_src: Frame sync source selection ++ * @frame_sync_mode: Frame sync mode configuration ++ * @invert_frame_sync_pulse: Invert frame sync polarity when set ++ * @frame_sync_data_delay: Data delay from frame sync in bit clocks ++ * @bit_clk_type: Bit clock type (internal / external) ++ * @inv_int_bit_clk: Invert internal bit clock when set ++ * @inv_ext_bit_clk: Invert external bit clock when set ++ * ++ * This structure defines configuration parameters for the Qualcomm ++ * Audio Interface (QAIF) block. It is used to program interface ++ * characteristics such as slot configuration, clocking and frame ++ * synchronization behaviour. ++ */ ++struct param_id_audio_if_intf_cfg { ++ uint16_t qaif_type; ++ uint16_t intf_idx; ++ uint16_t intf_mode; ++ uint16_t ctrl_data_out_enable; ++ uint32_t active_slot_mask; ++ uint16_t nslots_per_frame; ++ uint16_t slot_width; ++ uint32_t active_lane_mask; ++ uint32_t frame_sync_rate; ++ uint16_t frame_sync_src; ++ uint16_t frame_sync_mode; ++ uint16_t invert_frame_sync_pulse; ++ uint16_t frame_sync_data_delay; ++ uint16_t bit_clk_type; ++ uint8_t inv_int_bit_clk; ++ uint8_t inv_ext_bit_clk; ++} __packed; ++ ++#define PARAM_ID_HW_EP_FRAME_DURATION 0x08001B2F ++#define AUDIO_IF_FRAME_DURATION_US 1000 ++#define AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE 1 ++#define AUDIO_IF_FRAME_DURATION_MIN_US 1 ++#define AUDIO_IF_FRAME_DURATION_MAX_US 100000 ++ ++/** ++ * struct param_id_hw_ep_frame_duration - Hardware endpoint frame duration ++ * @frame_duration_in_us: Frame duration in microseconds. ++ * @allow_frame_duration_normalization: Permit SPF to normalize frame duration. ++ * @min_normalized_frame_dur_us: Minimum normalized frame duration in microseconds. ++ * @max_normalized_frame_dur_us: Maximum normalized frame duration in microseconds. ++ * ++ * This structure configures the frame duration for the Audio IF hardware ++ * endpoint and, when enabled, the allowed normalization range. ++ */ ++struct param_id_hw_ep_frame_duration { ++ uint32_t frame_duration_in_us; ++ uint32_t allow_frame_duration_normalization; ++ uint32_t min_normalized_frame_dur_us; ++ uint32_t max_normalized_frame_dur_us; ++} __packed; ++ + #define PARAM_ID_DISPLAY_PORT_INTF_CFG 0x08001154 + + struct param_id_display_port_intf_cfg { +@@ -877,6 +947,23 @@ struct audioreach_module { + uint32_t data_format; + uint32_t hw_interface_type; + ++ /* Audio IF module (TDM/PCM/I2S) */ ++ u32 slot_mask; ++ u32 active_lane_mask; ++ u32 frame_sync_rate; ++ u16 qaif_type; ++ u16 sync_src; ++ u16 ctrl_data_out_enable; ++ u16 nslots_per_frame; ++ u16 slot_width; ++ u16 intf_mode; ++ u16 sync_mode; ++ u16 ctrl_invert_sync_pulse; ++ u16 ctrl_sync_data_delay; ++ u16 bit_clk_type; ++ u8 inv_int_bit_clk; ++ u8 inv_ext_bit_clk; ++ + /* PCM module specific */ + uint32_t interleave_type; + +@@ -907,6 +994,9 @@ struct audioreach_module_config { + u32 channel_allocation; + u32 sd_line_mask; + int fmt; ++ u32 slot_mask; ++ u16 nslots_per_frame; ++ u16 slot_width; + struct snd_codec codec; + u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; + }; +diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c +--- a/sound/soc/qcom/qdsp6/q6afe-dai.c ++++ b/sound/soc/qcom/qdsp6/q6afe-dai.c +@@ -412,6 +412,8 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + rc = q6afe_i2s_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].i2s_cfg); + if (rc < 0) { +@@ -665,6 +667,21 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + + /* USB playback AFE port receives data for playback, hence use the RX port */ + {"USB Playback", NULL, "USB_RX"}, ++ ++ {"LPI RX0 MI2S Playback", NULL, "LPI_MI2S_RX_0"}, ++ {"LPI_MI2S_TX_0", NULL, "LPI TX0 MI2S Capture"}, ++ {"LPI RX1 MI2S Playback", NULL, "LPI_MI2S_RX_1"}, ++ {"LPI_MI2S_TX_1", NULL, "LPI TX1 MI2S Capture"}, ++ {"LPI RX2 MI2S Playback", NULL, "LPI_MI2S_RX_2"}, ++ {"LPI_MI2S_TX_2", NULL, "LPI TX2 MI2S Capture"}, ++ {"LPI RX3 MI2S Playback", NULL, "LPI_MI2S_RX_3"}, ++ {"LPI_MI2S_TX_3", NULL, "LPI TX3 MI2S Capture"}, ++ {"LPI RX4 MI2S Playback", NULL, "LPI_MI2S_RX_4"}, ++ {"LPI_MI2S_TX_4", NULL, "LPI TX4 MI2S Capture"}, ++ {"LPI RX5 MI2S Playback", NULL, "LPI_MI2S_RX_5"}, ++ {"LPI_MI2S_TX_5", NULL, "LPI TX5 MI2S Capture"}, ++ {"LPI RX6 MI2S Playback", NULL, "LPI_MI2S_RX_6"}, ++ {"LPI_MI2S_TX_6", NULL, "LPI TX6 MI2S Capture"}, + }; + + static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +@@ -1011,6 +1028,35 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("USB_RX", NULL, 0, SND_SOC_NOPM, 0, 0), ++ ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), + }; + + static const struct snd_soc_component_driver q6afe_dai_component = { +@@ -1045,6 +1091,8 @@ static void of_q6afe_parse_dai_data(struct device *dev, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + priv = &data->priv[id]; + ret = of_property_read_variable_u32_array(node, + "qcom,sd-lines", +diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c +--- a/sound/soc/qcom/qdsp6/q6afe.c ++++ b/sound/soc/qcom/qdsp6/q6afe.c +@@ -132,6 +132,20 @@ + #define AFE_PORT_ID_QUINARY_MI2S_TX 0x1017 + #define AFE_PORT_ID_SENARY_MI2S_RX 0x1018 + #define AFE_PORT_ID_SENARY_MI2S_TX 0x1019 ++#define AFE_PORT_ID_INT0_MI2S_RX 0x102e ++#define AFE_PORT_ID_INT0_MI2S_TX 0x102f ++#define AFE_PORT_ID_INT1_MI2S_RX 0x1030 ++#define AFE_PORT_ID_INT1_MI2S_TX 0x1031 ++#define AFE_PORT_ID_INT2_MI2S_RX 0x1032 ++#define AFE_PORT_ID_INT2_MI2S_TX 0x1033 ++#define AFE_PORT_ID_INT3_MI2S_RX 0x1034 ++#define AFE_PORT_ID_INT3_MI2S_TX 0x1035 ++#define AFE_PORT_ID_INT4_MI2S_RX 0x1036 ++#define AFE_PORT_ID_INT4_MI2S_TX 0x1037 ++#define AFE_PORT_ID_INT5_MI2S_RX 0x1038 ++#define AFE_PORT_ID_INT5_MI2S_TX 0x1039 ++#define AFE_PORT_ID_INT6_MI2S_RX 0x103a ++#define AFE_PORT_ID_INT6_MI2S_TX 0x103b + + /* Start of the range of port IDs for TDM devices. */ + #define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000 +@@ -931,6 +945,34 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { + [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7, + RX_CODEC_DMA_RX_7, 1, 1}, + [USB_RX] = { AFE_PORT_ID_USB_RX, USB_RX, 1, 1}, ++ [LPI_MI2S_RX_0] = { AFE_PORT_ID_INT0_MI2S_RX, ++ LPI_MI2S_RX_0, 1, 1}, ++ [LPI_MI2S_TX_0] = { AFE_PORT_ID_INT0_MI2S_TX, ++ LPI_MI2S_TX_0, 0, 1}, ++ [LPI_MI2S_RX_1] = { AFE_PORT_ID_INT1_MI2S_RX, ++ LPI_MI2S_RX_1, 1, 1}, ++ [LPI_MI2S_TX_1] = { AFE_PORT_ID_INT1_MI2S_TX, ++ LPI_MI2S_TX_1, 0, 1}, ++ [LPI_MI2S_RX_2] = { AFE_PORT_ID_INT2_MI2S_RX, ++ LPI_MI2S_RX_2, 1, 1}, ++ [LPI_MI2S_TX_2] = { AFE_PORT_ID_INT2_MI2S_TX, ++ LPI_MI2S_TX_2, 0, 1}, ++ [LPI_MI2S_RX_3] = { AFE_PORT_ID_INT3_MI2S_RX, ++ LPI_MI2S_RX_3, 1, 1}, ++ [LPI_MI2S_TX_3] = { AFE_PORT_ID_INT3_MI2S_TX, ++ LPI_MI2S_TX_3, 0, 1}, ++ [LPI_MI2S_RX_4] = { AFE_PORT_ID_INT4_MI2S_RX, ++ LPI_MI2S_RX_4, 1, 1}, ++ [LPI_MI2S_TX_4] = { AFE_PORT_ID_INT4_MI2S_TX, ++ LPI_MI2S_TX_4, 0, 1}, ++ [LPI_MI2S_RX_5] = { AFE_PORT_ID_INT5_MI2S_RX, ++ LPI_MI2S_RX_5, 1, 1}, ++ [LPI_MI2S_TX_5] = { AFE_PORT_ID_INT5_MI2S_TX, ++ LPI_MI2S_TX_5, 0, 1}, ++ [LPI_MI2S_RX_6] = { AFE_PORT_ID_INT6_MI2S_RX, ++ LPI_MI2S_RX_6, 1, 1}, ++ [LPI_MI2S_TX_6] = { AFE_PORT_ID_INT6_MI2S_TX, ++ LPI_MI2S_TX_6, 0, 1}, + }; + + static void q6afe_port_free(struct kref *ref) +@@ -1785,6 +1827,20 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) + case AFE_PORT_ID_QUINARY_MI2S_TX: + case AFE_PORT_ID_SENARY_MI2S_RX: + case AFE_PORT_ID_SENARY_MI2S_TX: ++ case AFE_PORT_ID_INT0_MI2S_RX: ++ case AFE_PORT_ID_INT0_MI2S_TX: ++ case AFE_PORT_ID_INT1_MI2S_RX: ++ case AFE_PORT_ID_INT1_MI2S_TX: ++ case AFE_PORT_ID_INT2_MI2S_RX: ++ case AFE_PORT_ID_INT2_MI2S_TX: ++ case AFE_PORT_ID_INT3_MI2S_RX: ++ case AFE_PORT_ID_INT3_MI2S_TX: ++ case AFE_PORT_ID_INT4_MI2S_RX: ++ case AFE_PORT_ID_INT4_MI2S_TX: ++ case AFE_PORT_ID_INT5_MI2S_RX: ++ case AFE_PORT_ID_INT5_MI2S_TX: ++ case AFE_PORT_ID_INT6_MI2S_RX: ++ case AFE_PORT_ID_INT6_MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +--- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c ++++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +@@ -2,10 +2,12 @@ + // Copyright (c) 2021, Linaro Limited + + #include ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -15,15 +17,54 @@ + #include "q6dsp-common.h" + #include "audioreach.h" + #include "q6apm.h" ++#include "q6prm.h" + + #define AUDIOREACH_BE_PCM_BASE 16 + ++struct q6apm_dai_priv_data { ++ struct clk *mclk; ++ struct clk *bclk; ++ bool mclk_enabled, bclk_enabled; ++}; ++ + struct q6apm_lpass_dai_data { + struct q6apm_graph *graph[APM_PORT_MAX]; + bool is_port_started[APM_PORT_MAX]; + struct audioreach_module_config module_config[APM_PORT_MAX]; ++ struct q6apm_dai_priv_data priv[APM_PORT_MAX]; + }; + ++static void q6apm_lpass_dai_disable_clocks(struct q6apm_lpass_dai_data *dai_data, int id) ++{ ++ if (dai_data->priv[id].mclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].mclk); ++ dai_data->priv[id].mclk_enabled = false; ++ } ++ ++ if (dai_data->priv[id].bclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].bclk); ++ dai_data->priv[id].bclk_enabled = false; ++ } ++} ++ ++static void q6apm_lpass_dai_put_clocks(struct q6apm_lpass_dai_data *dai_data) ++{ ++ int i; ++ ++ for (i = 0; i < APM_PORT_MAX; i++) { ++ q6apm_lpass_dai_disable_clocks(dai_data, i); ++ ++ if (dai_data->priv[i].mclk) { ++ clk_put(dai_data->priv[i].mclk); ++ dai_data->priv[i].mclk = NULL; ++ } ++ if (dai_data->priv[i].bclk) { ++ clk_put(dai_data->priv[i].bclk); ++ dai_data->priv[i].bclk = NULL; ++ } ++ } ++} ++ + static int q6dma_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, + const unsigned int *tx_ch_mask, +@@ -251,6 +292,62 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s + return 0; + } + ++static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ return q6apm_lpass_dai_startup(substream, dai); ++} ++ ++static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ ++ q6apm_lpass_dai_shutdown(substream, dai); ++ q6apm_lpass_dai_disable_clocks(dai_data, dai->id); ++} ++ ++static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct clk *sysclk = NULL; ++ bool *enabled = NULL; ++ int ret = 0; ++ ++ switch (clk_id) { ++ case LPAIF_MI2S_MCLK: ++ sysclk = dai_data->priv[dai->id].mclk; ++ enabled = &dai_data->priv[dai->id].mclk_enabled; ++ break; ++ case LPAIF_MI2S_BCLK: ++ sysclk = dai_data->priv[dai->id].bclk; ++ enabled = &dai_data->priv[dai->id].bclk_enabled; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (sysclk) { ++ ret = clk_set_rate(sysclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to set rate (%d) for sysclk %d\n", ++ freq, clk_id); ++ return ret; ++ } ++ ++ if (*enabled) ++ return 0; ++ ++ ret = clk_prepare_enable(sysclk); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id); ++ return ret; ++ } ++ ++ *enabled = true; ++ } ++ ++ return ret; ++} ++ + static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); +@@ -261,6 +358,53 @@ static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_mask, ++ unsigned int rx_mask, ++ int slots, int slot_width) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; ++ unsigned int cap_mask, slot_mask; ++ ++ if (slot_width != 16 && slot_width != 32) { ++ dev_err(dai->dev, "%s: invalid slot_width %d\n", __func__, slot_width); ++ return -EINVAL; ++ } ++ ++ switch (slots) { ++ case 2: ++ case 4: ++ case 8: ++ case 16: ++ cap_mask = GENMASK(slots - 1, 0); ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid slots %d\n", __func__, slots); ++ return -EINVAL; ++ } ++ ++ switch (dai->id) { ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; ++ if (slot_mask & ~cap_mask) { ++ dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", ++ __func__, slot_mask, slots); ++ return -EINVAL; ++ } ++ ++ cfg->nslots_per_frame = slots; ++ cfg->slot_width = slot_width; ++ cfg->slot_mask = slot_mask; ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid dai id 0x%x\n", __func__, dai->id); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + static const struct snd_soc_dai_ops q6dma_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, +@@ -272,11 +416,12 @@ static const struct snd_soc_dai_ops q6dma_ops = { + + static const struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6apm_lpass_dai_prepare, +- .startup = q6apm_lpass_dai_startup, +- .shutdown = q6apm_lpass_dai_shutdown, ++ .startup = q6i2s_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, + .set_channel_map = q6dma_set_channel_map, + .hw_params = q6dma_hw_params, + .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, + .trigger = q6apm_lpass_dai_trigger, + }; + +@@ -289,6 +434,17 @@ static const struct snd_soc_dai_ops q6hdmi_ops = { + .trigger = q6apm_lpass_dai_trigger, + }; + ++static const struct snd_soc_dai_ops q6tdm_ops = { ++ .prepare = q6apm_lpass_dai_prepare, ++ .startup = q6apm_lpass_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, ++ .set_tdm_slot = q6tdm_set_tdm_slot, ++ .hw_params = q6dma_hw_params, ++ .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, ++ .trigger = q6apm_lpass_dai_trigger, ++}; ++ + static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .name = "q6apm-be-dai-component", + .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, +@@ -297,6 +453,65 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .remove_order = SND_SOC_COMP_ORDER_FIRST, + }; + ++static int of_q6apm_parse_dai_data(struct device *dev, ++ struct q6apm_lpass_dai_data *data) ++{ ++ int ret; ++ ++ for_each_child_of_node_scoped(dev->of_node, node) { ++ struct q6apm_dai_priv_data *priv; ++ int id; ++ ++ ret = of_property_read_u32(node, "reg", &id); ++ if (ret || id < 0 || id >= APM_PORT_MAX) { ++ dev_err(dev, "valid dai id not found:%d\n", ret); ++ continue; ++ } ++ ++ switch (id) { ++ /* MI2S specific properties */ ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ priv = &data->priv[id]; ++ priv->mclk = of_clk_get_by_name(node, "mclk"); ++ if (IS_ERR(priv->mclk)) { ++ int err = PTR_ERR(priv->mclk); ++ ++ priv->mclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s mclk\n"); ++ } ++ } ++ ++ priv->bclk = of_clk_get_by_name(node, "bclk"); ++ if (IS_ERR(priv->bclk)) { ++ int err = PTR_ERR(priv->bclk); ++ ++ priv->bclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s bclk\n"); ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return 0; ++} ++ ++static void q6apm_lpass_dai_clocks_action(void *data) ++{ ++ q6apm_lpass_dai_put_clocks(data); ++} ++ + static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + { + struct q6dsp_audio_port_dai_driver_config cfg; +@@ -304,17 +519,26 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + struct snd_soc_dai_driver *dais; + struct device *dev = &pdev->dev; + int num_dais; ++ int ret; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + return -ENOMEM; + + dev_set_drvdata(dev, dai_data); ++ ret = of_q6apm_parse_dai_data(dev, dai_data); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, q6apm_lpass_dai_clocks_action, dai_data); ++ if (ret) ++ return ret; + + memset(&cfg, 0, sizeof(cfg)); + cfg.q6i2s_ops = &q6i2s_ops; + cfg.q6dma_ops = &q6dma_ops; + cfg.q6hdmi_ops = &q6hdmi_ops; ++ cfg.q6tdm_ops = &q6tdm_ops; + dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); + + return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +@@ -12,7 +12,7 @@ + #include + #include "q6dsp-lpass-clocks.h" + +-#define Q6DSP_MAX_CLK_ID 104 ++#define Q6DSP_MAX_CLK_ID 105 + #define Q6DSP_LPASS_CLK_ROOT_DEFAULT 0 + + +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +@@ -553,11 +553,15 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { + Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2), + Q6AFE_MI2S_RX_DAI("LPI RX3", LPI_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("LPI RX4", LPI_MI2S_RX_4), ++ Q6AFE_MI2S_RX_DAI("LPI RX5", LPI_MI2S_RX_5), ++ Q6AFE_MI2S_RX_DAI("LPI RX6", LPI_MI2S_RX_6), + Q6AFE_MI2S_TX_DAI("LPI TX0", LPI_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("LPI TX1", LPI_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("LPI TX2", LPI_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("LPI TX3", LPI_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), ++ Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5), ++ Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6), + Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), +@@ -712,6 +716,7 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -42,6 +42,11 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_OSR), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_1), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_2), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_3), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_4), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_5), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_MCLK), +@@ -59,10 +64,40 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, + "LPASS_HW_DCODEC"), ++ Q6DSP_VOTE_CLK(LPASS_HW_LPR_VOTE, Q6PRM_HW_LPR_VOTE, ++ "LPASS_HW_LPR_VOTE"), + }; + + static const struct q6dsp_clk_desc q6dsp_clk_q6prm __maybe_unused = { +diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c +--- a/sound/soc/qcom/qdsp6/q6prm.c ++++ b/sound/soc/qcom/qdsp6/q6prm.c +@@ -31,10 +31,16 @@ struct q6prm { + #define PARAM_ID_RSC_HW_CORE 0x08001032 + #define PARAM_ID_RSC_LPASS_CORE 0x0800102B + #define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C ++#define PARAM_ID_RSC_CPU_LPR 0x08001A6E ++ ++#define LPR_CPU_SS_SLEEP_DISABLE 0x1 + + struct prm_cmd_request_hw_core { + struct apm_module_param_data param_data; +- uint32_t hw_clk_id; ++ union { ++ u32 hw_clk_id; ++ u32 lpr_state; ++ }; + } __packed; + + struct prm_cmd_request_rsc { +@@ -62,6 +68,7 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + struct prm_cmd_request_hw_core *req; + gpr_device_t *gdev = prm->gdev; + uint32_t opcode, rsp_opcode; ++ bool lpr_req = (hw_block_id == Q6PRM_HW_LPR_VOTE); + + if (enable) { + opcode = PRM_CMD_REQUEST_HW_RSC; +@@ -82,10 +89,13 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + + param_data->module_instance_id = GPR_PRM_MODULE_IID; + param_data->error_code = 0; +- param_data->param_id = PARAM_ID_RSC_HW_CORE; ++ param_data->param_id = lpr_req ? PARAM_ID_RSC_CPU_LPR : PARAM_ID_RSC_HW_CORE; + param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; + +- req->hw_clk_id = hw_block_id; ++ if (lpr_req) ++ req->lpr_state = LPR_CPU_SS_SLEEP_DISABLE; ++ else ++ req->hw_clk_id = hw_block_id; + + return q6prm_send_cmd_sync(prm, pkt, rsp_opcode); + } +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -3,6 +3,9 @@ + #ifndef __Q6PRM_H__ + #define __Q6PRM_H__ + ++#define LPAIF_MI2S_MCLK 1 ++#define LPAIF_MI2S_BCLK 2 ++ + /* Clock ID for Primary I2S IBIT */ + #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 + /* Clock ID for Primary I2S EBIT */ +@@ -52,6 +55,17 @@ + /* Clock ID for QUINARY MI2S OSR CLK */ + #define Q6PRM_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 + ++/* Clock ID for MCLK1 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_1 0x300 ++/* Clock ID for MCLK2 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_2 0x301 ++/* Clock ID for MCLK3 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_3 0x302 ++/* Clock ID for MCLK4 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_4 0x303 ++/* Clock ID for MCLK5 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_5 0x304 ++ + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_MCLK 0x305 + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x306 + +@@ -83,10 +97,40 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++ + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 + #define Q6PRM_HW_CORE_ID_LPASS 1 + #define Q6PRM_HW_CORE_ID_DCODEC 2 ++#define Q6PRM_HW_LPR_VOTE 3 + + int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, + int clk_root, unsigned int freq); +diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c +--- a/sound/soc/qcom/qdsp6/q6routing.c ++++ b/sound/soc/qcom/qdsp6/q6routing.c +@@ -127,7 +127,14 @@ + { mix_name, "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2"}, \ + { mix_name, "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3"}, \ + { mix_name, "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4"}, \ +- { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"} ++ { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"}, \ ++ { mix_name, "LPI_MI2S_TX_0", "LPI_MI2S_TX_0" }, \ ++ { mix_name, "LPI_MI2S_TX_1", "LPI_MI2S_TX_1" }, \ ++ { mix_name, "LPI_MI2S_TX_2", "LPI_MI2S_TX_2" }, \ ++ { mix_name, "LPI_MI2S_TX_3", "LPI_MI2S_TX_3" }, \ ++ { mix_name, "LPI_MI2S_TX_4", "LPI_MI2S_TX_4" }, \ ++ { mix_name, "LPI_MI2S_TX_5", "LPI_MI2S_TX_5" }, \ ++ { mix_name, "LPI_MI2S_TX_6", "LPI_MI2S_TX_6" } + + #define Q6ROUTING_TX_MIXERS(id) \ + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \ +@@ -320,6 +327,27 @@ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_5", TX_CODEC_DMA_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_0", LPI_MI2S_TX_0, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_1", LPI_MI2S_TX_1, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_2", LPI_MI2S_TX_2, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_3", LPI_MI2S_TX_3, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_4", LPI_MI2S_TX_4, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_5", LPI_MI2S_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_6", LPI_MI2S_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), + +@@ -709,6 +737,26 @@ static const struct snd_kcontrol_new rxcodec_dma_rx_6_mixer_controls[] = { + static const struct snd_kcontrol_new rx_codec_dma_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_7) }; + ++static const struct snd_kcontrol_new lpi_mi2s_rx_0_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_0) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_1_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_1) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_2_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_2) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_3_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_3) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_4_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_4) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_5_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_5) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_6_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_6) }; + + static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) }; +@@ -938,6 +986,27 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + SND_SOC_DAPM_MIXER("USB_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + usb_rx_mixer_controls, + ARRAY_SIZE(usb_rx_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_0_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_0_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_1_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_1_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_2_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_2_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_3_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_3_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_4_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_4_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_5_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_5_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_6_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, +@@ -1031,6 +1100,13 @@ static const struct snd_soc_dapm_route intercon[] = { + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("USB_RX Audio Mixer", "USB_RX"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_0 Audio Mixer", "LPI_MI2S_RX_0"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_1 Audio Mixer", "LPI_MI2S_RX_1"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_2 Audio Mixer", "LPI_MI2S_RX_2"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_3 Audio Mixer", "LPI_MI2S_RX_3"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_4 Audio Mixer", "LPI_MI2S_RX_4"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_5 Audio Mixer", "LPI_MI2S_RX_5"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_6 Audio Mixer", "LPI_MI2S_RX_6"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), +diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c +--- a/sound/soc/qcom/qdsp6/topology.c ++++ b/sound/soc/qcom/qdsp6/topology.c +@@ -753,6 +753,103 @@ static int audioreach_widget_i2s_module_load(struct audioreach_module *mod, + return 0; + } + ++static int audioreach_widget_audio_if_module_load(struct audioreach_module *mod, ++ const struct snd_soc_tplg_vendor_array *mod_array) ++{ ++ const struct snd_soc_tplg_vendor_value_elem *mod_elem; ++ int tkn_count = 0; ++ u32 val; ++ ++ mod_elem = mod_array->value; ++ ++ while (tkn_count < le32_to_cpu(mod_array->num_elems)) { ++ val = le32_to_cpu(mod_elem->value); ++ switch (le32_to_cpu(mod_elem->token)) { ++ case AR_TKN_U32_MODULE_HW_IF_IDX: ++ mod->hw_interface_idx = val; ++ break; ++ case AR_TKN_U32_MODULE_FMT_DATA: ++ mod->data_format = val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_SRC: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_src = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_data_out_enable = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_SLOT_MASK: ++ mod->slot_mask = val; ++ break; ++ case AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->nslots_per_frame = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SLOT_WIDTH: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->slot_width = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_INTF_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->intf_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_invert_sync_pulse = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_sync_data_delay = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_QAIF_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->qaif_type = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: ++ mod->active_lane_mask = val; ++ break; ++ case AR_TKN_U32_MODULE_FRAME_SYNC_RATE: ++ mod->frame_sync_rate = val; ++ break; ++ case AR_TKN_U16_MODULE_BIT_CLK_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->bit_clk_type = (u16)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_INT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_int_bit_clk = (u8)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_ext_bit_clk = (u8)val; ++ break; ++ default: ++ break; ++ } ++ tkn_count++; ++ mod_elem++; ++ } ++ ++ return 0; ++} ++ + static int audioreach_widget_dp_module_load(struct audioreach_module *mod, + const struct snd_soc_tplg_vendor_array *mod_array) + { +@@ -806,6 +903,12 @@ static int audioreach_widget_load_buffer(struct snd_soc_component *component, + case MODULE_ID_I2S_SOURCE: + audioreach_widget_i2s_module_load(mod, mod_array); + break; ++ case MODULE_ID_AUDIO_IF_SINK: ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ ret = audioreach_widget_audio_if_module_load(mod, mod_array); ++ if (ret) ++ return ret; ++ break; + case MODULE_ID_DISPLAY_PORT_SINK: + audioreach_widget_dp_module_load(mod, mod_array); + break; +@@ -1313,7 +1416,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + { + struct snd_soc_card *card = component->card; + struct device *dev = component->dev; +- const struct firmware *fw; + int ret; + + /* Inline with Qualcomm UCM configs and linux-firmware path */ +@@ -1323,6 +1425,7 @@ int audioreach_tplg_init(struct snd_soc_component *component) + if (!tplg_fw_name) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_fw_name, dev); + if (ret < 0) { + dev_err(dev, "tplg firmware loading %s failed %d\n", tplg_fw_name, ret); +@@ -1335,8 +1438,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + dev_err(dev, "tplg component load failed: %d\n", ret); + } + +- release_firmware(fw); +- + return ret; + } + EXPORT_SYMBOL_GPL(audioreach_tplg_init); +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -12,17 +12,157 @@ + #include + #include + #include "qdsp6/q6afe.h" ++#include "qdsp6/q6apm.h" ++#include "qdsp6/q6prm.h" ++#include "qdsp6/q6dsp-common.h" + #include "common.h" + #include "sdw.h" + ++#define I2S_MCLKFS 256 ++ ++#define I2S_MCLK_RATE(rate) \ ++ ((rate) * (I2S_MCLKFS)) ++#define I2S_BIT_RATE(rate, channels, format) \ ++ ((rate) * (channels) * (format)) ++ ++static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_SPK("DP0 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP1 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP2 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP3 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP4 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP5 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP6 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP7 Jack", NULL), ++}; ++ ++static const struct snd_kcontrol_new max98090_controls[] = { ++ SOC_DAPM_PIN_SWITCH("Headset Mic12"), ++ SOC_DAPM_PIN_SWITCH("Headphone"), ++ SOC_DAPM_PIN_SWITCH("Headset Mic56"), ++ SOC_DAPM_PIN_SWITCH("Speaker"), ++ SOC_DAPM_PIN_SWITCH("Receiver"), ++ SOC_DAPM_PIN_SWITCH("Int Mic"), ++}; ++ ++static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_HP("Headphone", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic12", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic56", NULL), ++ SND_SOC_DAPM_MIC("Int Mic", NULL), ++ SND_SOC_DAPM_SPK("Receiver", NULL), ++ SND_SOC_DAPM_SPK("Speaker", NULL), ++}; ++ ++struct qcom_snd_soc_common { ++ const char *driver_name; ++ const struct snd_soc_dapm_widget *dapm_widgets; ++ int num_dapm_widgets; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ const struct snd_kcontrol_new *controls; ++ int num_controls; ++ unsigned int codec_dai_fmt; ++ bool codec_sysclk_set; ++ bool mi2s_mclk_enable; ++ bool mi2s_bclk_enable; ++ bool wcd_jack; ++ int (*snd_prepare)(struct snd_pcm_substream *substream); ++}; ++ + struct sc8280xp_snd_data { + bool stream_prepared[AFE_PORT_MAX]; + struct snd_soc_card *card; + struct snd_soc_jack jack; + struct snd_soc_jack dp_jack[8]; ++ const struct qcom_snd_soc_common *priv; + bool jack_setup; + }; + ++static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) ++{ ++ int rate = params_rate(params); ++ ++ switch (rate) { ++ case 11025: ++ case 44100: ++ case 88200: ++ return I2S_MCLK_RATE(44100); ++ default: ++ break; ++ } ++ ++ return I2S_MCLK_RATE(rate); ++} ++ ++static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params) ++{ ++ return I2S_BIT_RATE(params_rate(params), ++ params_channels(params), ++ snd_pcm_format_width(params_format(params))); ++} ++ ++static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ unsigned int bclk_freq; ++ int ret; ++ int i; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ if (!cpu_cfg.slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret) ++ return ret; ++ ++ ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret; ++ ++ bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); ++ if (!bclk_freq) ++ return -EINVAL; ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", ++ __func__, ret); ++ return ret; ++ } ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", ++ __func__, codec_dai->name, ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ + static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + { + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); +@@ -32,10 +172,6 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + int dp_pcm_id = 0; + + switch (cpu_dai->id) { +- case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX: +- case QUINARY_MI2S_RX...QUINARY_MI2S_TX: +- snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- break; + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + /* +@@ -64,7 +200,10 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + if (dp_jack) + return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); + +- return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ if (data->priv->wcd_jack) ++ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ ++ return 0; + } + + static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, +@@ -96,12 +235,118 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + return 0; + } + ++static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ int mclk_freq = sc8280xp_get_mclk_freq(params); ++ int bclk_freq = sc8280xp_get_bclk_freq(params); ++ int ret; ++ ++ switch (cpu_dai->id) { ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ ++ if (data->priv->codec_dai_fmt) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_mclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_MCLK, mclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, ++ 0, mclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ break; ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ return sc8280xp_tdm_hw_params(substream, params); ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++/* ++ * WSA and WSA2 are handled as a single interface with the ++ * following channels mask: ++ * __________________________________________________ ++ * | Bits | 3 | 2 | 1 | 0 | ++ * --------------------------------------------------- ++ * | Line | WSA2 Ch2 | WSA2 Ch1 | WSA Ch2 | WSA Ch1 | ++ * --------------------------------------------------- ++ * ++ * The Ayaneo Pocket S2 speakers are connected only to ++ * the WSA2 interface and the WSA interface is not enabled. ++ * ++ * Set the channel mapping on the WSA2 channels only. ++ */ ++static const unsigned int ayaneo_ps2_channels_mapping[] = { ++ 0, /* WSA Ch1 */ ++ 0, /* WSA Ch2 */ ++ PCM_CHANNEL_FL, /* WSA2 Ch1 */ ++ PCM_CHANNEL_FR /* WSA2 Ch2 */ ++}; ++ ++static int ayaneo_ps2_snd_prepare(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ unsigned int channels = substream->runtime->channels; ++ ++ if (cpu_dai->id != WSA_CODEC_DMA_RX_0) ++ return 0; ++ ++ if (channels != 2) ++ return -EINVAL; ++ ++ return snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, ++ ARRAY_SIZE(ayaneo_ps2_channels_mapping), ++ ayaneo_ps2_channels_mapping); ++} ++ + static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + ++ if (data->priv->snd_prepare) { ++ int ret; ++ ++ ret = data->priv->snd_prepare(substream); ++ if (ret) ++ return ret; ++ } ++ + return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]); + } + +@@ -117,6 +362,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) + static const struct snd_soc_ops sc8280xp_be_ops = { + .startup = qcom_snd_sdw_startup, + .shutdown = qcom_snd_sdw_shutdown, ++ .hw_params = sc8280xp_snd_hw_params, + .hw_free = sc8280xp_snd_hw_free, + .prepare = sc8280xp_snd_prepare, + }; +@@ -145,38 +391,182 @@ static int sc8280xp_platform_probe(struct platform_device *pdev) + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; +- card->owner = THIS_MODULE; ++ + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + ++ data->priv = of_device_get_match_data(dev); ++ if (!data->priv) ++ return -ENODEV; ++ ++ card->owner = THIS_MODULE; + card->dev = dev; + dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, data); ++ card->dapm_widgets = data->priv->dapm_widgets; ++ card->num_dapm_widgets = data->priv->num_dapm_widgets; ++ card->dapm_routes = data->priv->dapm_routes; ++ card->num_dapm_routes = data->priv->num_dapm_routes; ++ card->controls = data->priv->controls; ++ card->num_controls = data->priv->num_controls; ++ + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + +- card->driver_name = of_device_get_match_data(dev); ++ card->driver_name = data->priv->driver_name; + sc8280xp_add_be_ops(card); + return devm_snd_soc_register_card(dev, card); + } + ++static struct qcom_snd_soc_common ayaneo_ps2_priv_data = { ++ .driver_name = "ayaneo-ps2", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .snd_prepare = ayaneo_ps2_snd_prepare, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common eliza_priv_data = { ++ .driver_name = "eliza", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common hawi_priv_data = { ++ .driver_name = "hawi", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++ .mi2s_bclk_enable = true, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common kaanapali_priv_data = { ++ .driver_name = "kaanapali", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs9100_priv_data = { ++ .driver_name = "sa8775p", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++}; ++ ++static const struct qcom_snd_soc_common qcs615_priv_data = { ++ .driver_name = "qcs615", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++}; ++ ++static const struct qcom_snd_soc_common qcm6490_priv_data = { ++ .driver_name = "qcm6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs6490_priv_data = { ++ .driver_name = "qcs6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs8275_priv_data = { ++ .driver_name = "qcs8300", ++ .dapm_widgets = max98090_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(max98090_dapm_widgets), ++ .controls = max98090_controls, ++ .num_controls = ARRAY_SIZE(max98090_controls), ++ .codec_sysclk_set = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_BC_FC, ++}; ++ ++static const struct qcom_snd_soc_common sc8280xp_priv_data = { ++ .driver_name = "sc8280xp", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8450_priv_data = { ++ .driver_name = "sm8450", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8475_priv_data = { ++ .driver_name = "sm8475", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8550_priv_data = { ++ .driver_name = "sm8550", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8650_priv_data = { ++ .driver_name = "sm8650", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8750_priv_data = { ++ .driver_name = "sm8750", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ + static const struct of_device_id snd_sc8280xp_dt_match[] = { +- {.compatible = "qcom,eliza-sndcard", "eliza"}, +- {.compatible = "qcom,kaanapali-sndcard", "kaanapali"}, +- {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, +- {.compatible = "qcom,qcs615-sndcard", "qcs615"}, +- {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, +- {.compatible = "qcom,qcs8275-sndcard", "qcs8300"}, +- {.compatible = "qcom,qcs9075-sndcard", "sa8775p"}, +- {.compatible = "qcom,qcs9100-sndcard", "sa8775p"}, +- {.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"}, +- {.compatible = "qcom,sm8450-sndcard", "sm8450"}, +- {.compatible = "qcom,sm8550-sndcard", "sm8550"}, +- {.compatible = "qcom,sm8650-sndcard", "sm8650"}, +- {.compatible = "qcom,sm8750-sndcard", "sm8750"}, ++ { .compatible = "ayaneo,pocket-s2-sndcard", .data = &ayaneo_ps2_priv_data }, ++ { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, ++ { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, ++ { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, ++ { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, ++ { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, ++ { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data }, ++ { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data }, ++ { .compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data }, ++ { .compatible = "qcom,sm8475-sndcard", .data = &sm8475_priv_data }, ++ { .compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data }, ++ { .compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data }, ++ { .compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data }, + {} + }; + +diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c +--- a/sound/soc/qcom/sdm845.c ++++ b/sound/soc/qcom/sdm845.c +@@ -36,7 +36,6 @@ struct sdm845_snd_data { + bool jack_setup; + bool slim_port_setup; + bool stream_prepared[AFE_PORT_MAX]; +- struct snd_soc_card *card; + uint32_t pri_mi2s_clk_count; + uint32_t sec_mi2s_clk_count; + uint32_t quat_tdm_clk_count; +@@ -564,7 +563,6 @@ static int sdm845_snd_platform_probe(struct platform_device *pdev) + if (ret) + return ret; + +- data->card = card; + snd_soc_card_set_drvdata(card, data); + + sdm845_add_ops(card); +diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c +--- a/sound/soc/qcom/sm8250.c ++++ b/sound/soc/qcom/sm8250.c +@@ -112,6 +112,22 @@ static int sm8250_snd_startup(struct snd_pcm_substream *substream) + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; ++ case LPI_MI2S_RX_0: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; ++ case LPI_MI2S_TX_3: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; + default: + break; + } +@@ -194,6 +210,7 @@ static const struct of_device_id snd_sm8250_dt_match[] = { + { .compatible = "qcom,qrb2210-sndcard", .data = "qcm2290" }, + { .compatible = "qcom,qrb4210-rb2-sndcard", .data = "sm4250" }, + { .compatible = "qcom,qrb5165-rb5-sndcard", .data = "sm8250" }, ++ { .compatible = "qcom,sdm660-sndcard", .data = "sdm660" }, + { .compatible = "qcom,sm8250-sndcard", .data = "sm8250" }, + {} + }; +diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c +--- a/sound/soc/qcom/storm.c ++++ b/sound/soc/qcom/storm.c +@@ -64,21 +64,21 @@ static struct snd_soc_dai_link storm_dai_link = { + SND_SOC_DAILINK_REG(hifi), + }; + +-static int storm_parse_of(struct snd_soc_card *card) ++static int storm_parse_of(struct device *dev) + { +- struct snd_soc_dai_link *dai_link = card->dai_link; +- struct device_node *np = card->dev->of_node; ++ struct snd_soc_dai_link *dai_link = &storm_dai_link; ++ struct device_node *np = dev->of_node; + + dai_link->cpus->of_node = of_parse_phandle(np, "cpu", 0); + if (!dai_link->cpus->of_node) { +- dev_err(card->dev, "error getting cpu phandle\n"); ++ dev_err(dev, "error getting cpu phandle\n"); + return -EINVAL; + } + dai_link->platforms->of_node = dai_link->cpus->of_node; + + dai_link->codecs->of_node = of_parse_phandle(np, "codec", 0); + if (!dai_link->codecs->of_node) { +- dev_err(card->dev, "error getting codec phandle\n"); ++ dev_err(dev, "error getting codec phandle\n"); + return -EINVAL; + } + +@@ -106,7 +106,7 @@ static int storm_platform_probe(struct platform_device *pdev) + card->dai_link = &storm_dai_link; + card->num_links = 1; + +- ret = storm_parse_of(card); ++ ret = storm_parse_of(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "error resolving dai links: %d\n", ret); + return ret; +diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c +--- a/sound/soc/renesas/fsi.c ++++ b/sound/soc/renesas/fsi.c +@@ -1979,8 +1979,8 @@ static const struct of_device_id fsi_of_match[] = { + MODULE_DEVICE_TABLE(of, fsi_of_match); + + static const struct platform_device_id fsi_id_table[] = { +- { "sh_fsi", (kernel_ulong_t)&fsi1_core }, +- {}, ++ /* an array with no valid entry prevents matching on driver name */ ++ { } + }; + MODULE_DEVICE_TABLE(platform, fsi_id_table); + +@@ -1992,7 +1992,7 @@ static int fsi_probe(struct platform_device *pdev) + const struct fsi_core *core; + struct fsi_priv *fsi; + struct resource *res; +- unsigned int irq; ++ int irq; + int ret; + + memset(&info, 0, sizeof(info)); +@@ -2007,12 +2007,15 @@ static int fsi_probe(struct platform_device *pdev) + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- irq = platform_get_irq(pdev, 0); +- if (!res || (int)irq <= 0) { ++ if (!res) { + dev_err(&pdev->dev, "Not enough FSI platform resources.\n"); + return -ENODEV; + } + ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; +diff --git a/sound/soc/renesas/rcar/adg.c b/sound/soc/renesas/rcar/adg.c +--- a/sound/soc/renesas/rcar/adg.c ++++ b/sound/soc/renesas/rcar/adg.c +@@ -565,7 +565,7 @@ static struct clk *rsnd_adg_create_null_clk(struct rsnd_priv *priv, + struct clk *clk; + + clk = clk_register_fixed_rate(dev, name, parent, 0, 0); +- if (IS_ERR_OR_NULL(clk)) { ++ if (IS_ERR(clk)) { + dev_err(dev, "create null clk error\n"); + return ERR_CAST(clk); + } +@@ -618,7 +618,7 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + * No "adg" is not error + */ + clk = devm_clk_get(dev, "adg"); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); + adg->adg = clk; + +@@ -626,9 +626,9 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + for (i = 0; i < clkin_size; i++) { + clk = devm_clk_get(dev, clkin_name[i]); + +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + goto err; + + adg->clkin[i] = clk; +diff --git a/sound/soc/renesas/siu_dai.c b/sound/soc/renesas/siu_dai.c +--- a/sound/soc/renesas/siu_dai.c ++++ b/sound/soc/renesas/siu_dai.c +@@ -715,7 +715,6 @@ static struct snd_soc_dai_driver siu_i2s_dai = { + + static int siu_probe(struct platform_device *pdev) + { +- const struct firmware *fw_entry; + struct resource *res, *region; + struct siu_info *info; + int ret; +@@ -726,6 +725,7 @@ static int siu_probe(struct platform_device *pdev) + siu_i2s_data = info; + info->dev = &pdev->dev; + ++ const struct firmware *fw_entry __free(firmware) = NULL; + ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); + if (ret) + return ret; +@@ -736,8 +736,6 @@ static int siu_probe(struct platform_device *pdev) + */ + memcpy(&info->fw, fw_entry->data, fw_entry->size); + +- release_firmware(fw_entry); +- + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; +diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c +--- a/sound/soc/rockchip/rk3288_hdmi_analog.c ++++ b/sound/soc/rockchip/rk3288_hdmi_analog.c +@@ -185,10 +185,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + gpiod_set_consumer_name(machine->gpio_hp_en, "hp_en"); + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(card->dev, "SoC parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + rk_dailink.codecs[0].of_node = of_parse_phandle(np, + "rockchip,audio-codec", +@@ -207,10 +205,9 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + } + + ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); +- if (ret) { +- dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Unable to get codec_dai_name\n"); + + rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller", + 0); +@@ -223,11 +220,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); +- if (ret) { +- dev_err(&pdev->dev, +- "Unable to parse 'rockchip,routing' property\n"); ++ if (ret) + return ret; +- } + + snd_soc_card_set_drvdata(card, machine); + +diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c +--- a/sound/soc/rockchip/rockchip_i2s.c ++++ b/sound/soc/rockchip/rockchip_i2s.c +@@ -767,16 +767,14 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + /* try to prepare related clocks */ + i2s->hclk = devm_clk_get_enabled(&pdev->dev, "i2s_hclk"); +- if (IS_ERR(i2s->hclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); +- return PTR_ERR(i2s->hclk); +- } ++ if (IS_ERR(i2s->hclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->hclk), ++ "Can't retrieve i2s bus clock\n"); + + i2s->mclk = devm_clk_get(&pdev->dev, "i2s_clk"); +- if (IS_ERR(i2s->mclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s master clock\n"); +- return PTR_ERR(i2s->mclk); +- } ++ if (IS_ERR(i2s->mclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mclk), ++ "Can't retrieve i2s master clock\n"); + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) +@@ -792,7 +790,11 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + i2s->bclk_ratio = 64; + i2s->pinctrl = devm_pinctrl_get(&pdev->dev); +- if (!IS_ERR(i2s->pinctrl)) { ++ if (IS_ERR(i2s->pinctrl)) { ++ if (PTR_ERR(i2s->pinctrl) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); ++ } else { + i2s->bclk_on = pinctrl_lookup_state(i2s->pinctrl, "bclk_on"); + if (!IS_ERR_OR_NULL(i2s->bclk_on)) { + i2s->bclk_off = pinctrl_lookup_state(i2s->pinctrl, "bclk_off"); +@@ -801,8 +803,6 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + } +- } else { +- dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); + } + + i2s_pinctrl_select_bclk_off(i2s); +@@ -831,16 +831,12 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + &rockchip_i2s_component, + dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c +--- a/sound/soc/rockchip/rockchip_i2s_tdm.c ++++ b/sound/soc/rockchip/rockchip_i2s_tdm.c +@@ -1263,16 +1263,14 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + i2s_tdm->tx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "tx-m"); + if (IS_ERR(i2s_tdm->tx_reset)) { +- ret = PTR_ERR(i2s_tdm->tx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->tx_reset), + "Error in tx-m reset control\n"); + } + + i2s_tdm->rx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "rx-m"); + if (IS_ERR(i2s_tdm->rx_reset)) { +- ret = PTR_ERR(i2s_tdm->rx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->rx_reset), + "Error in rx-m reset control\n"); + } + +@@ -1363,17 +1361,12 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_tdm_component, + i2s_tdm->dai, 1); +- +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + goto err_suspend; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c +--- a/sound/soc/rockchip/rockchip_max98090.c ++++ b/sound/soc/rockchip/rockchip_max98090.c +@@ -428,11 +428,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + + /* Parse card name. */ + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + /* register the soc card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); +diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c +--- a/sound/soc/rockchip/rockchip_pdm.c ++++ b/sound/soc/rockchip/rockchip_pdm.c +@@ -630,10 +630,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + &rockchip_pdm_component, + &rockchip_pdm_dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "could not register dai: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + rockchip_pdm_rxctrl(pdm, 0); + +@@ -642,10 +640,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register pcm: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_rt5645.c b/sound/soc/rockchip/rockchip_rt5645.c +--- a/sound/soc/rockchip/rockchip_rt5645.c ++++ b/sound/soc/rockchip/rockchip_rt5645.c +@@ -191,11 +191,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + goto put_cpu_of_node; +- } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c +--- a/sound/soc/rockchip/rockchip_sai.c ++++ b/sound/soc/rockchip/rockchip_sai.c +@@ -1428,12 +1428,13 @@ static int rockchip_sai_probe(struct platform_device *pdev) + "Failed to initialize regmap\n"); + + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, rockchip_sai_isr, + IRQF_SHARED, node->name, sai); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "Failed to request irq %d\n", irq); ++ return ret; + } else { + dev_dbg(&pdev->dev, "Asked for an IRQ but got %d\n", irq); + } +@@ -1456,7 +1457,7 @@ static int rockchip_sai_probe(struct platform_device *pdev) + + ret = rockchip_sai_parse_paths(sai, node); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Failed to parse paths\n"); ++ return ret; + + /* + * From here on, all register accesses need to be wrapped in +@@ -1471,18 +1472,14 @@ static int rockchip_sai_probe(struct platform_device *pdev) + return dev_err_probe(&pdev->dev, ret, "Failed to resume device\n"); + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register PCM: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_sai_component, + dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register component: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_put(&pdev->dev); +diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c +--- a/sound/soc/rockchip/rockchip_spdif.c ++++ b/sound/soc/rockchip/rockchip_spdif.c +@@ -396,13 +396,13 @@ static int rk_spdif_probe(struct platform_device *pdev) + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register PCM\n"); ++ return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &rk_spdif_component, + &rk_spdif_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register DAI\n"); ++ return ret; + + return 0; + } +diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c +--- a/sound/soc/samsung/i2s.c ++++ b/sound/soc/samsung/i2s.c +@@ -1116,15 +1116,31 @@ static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = { + + static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = { + { "Playback Mixer", NULL, "Primary Playback" }, +- { "Playback Mixer", NULL, "Secondary Playback" }, +- + { "Mixer DAI TX", NULL, "Playback Mixer" }, + { "Primary Capture", NULL, "Mixer DAI RX" }, + }; + ++static const struct snd_soc_dapm_route samsung_i2s_dapm_routes_sec_play[] = { ++ { "Playback Mixer", NULL, "Secondary Playback" }, ++}; ++ ++static int samsung_i2s_component_probe(struct snd_soc_component *component) ++{ ++ struct samsung_i2s_priv *priv = snd_soc_component_get_drvdata(component); ++ ++ if (priv->quirks & QUIRK_SEC_DAI) ++ snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component), ++ samsung_i2s_dapm_routes_sec_play, ++ ARRAY_SIZE(samsung_i2s_dapm_routes_sec_play)); ++ ++ return 0; ++} ++ + static const struct snd_soc_component_driver samsung_i2s_component = { + .name = "samsung-i2s", + ++ .probe = samsung_i2s_component_probe, ++ + .dapm_widgets = samsung_i2s_widgets, + .num_dapm_widgets = ARRAY_SIZE(samsung_i2s_widgets), + +@@ -1441,10 +1457,10 @@ static int samsung_i2s_probe(struct platform_device *pdev) + regs_base = res->start; + + priv->clk = devm_clk_get(&pdev->dev, "iis"); +- if (IS_ERR(priv->clk)) { +- dev_err(&pdev->dev, "Failed to get iis clock\n"); +- return PTR_ERR(priv->clk); +- } ++ if (IS_ERR(priv->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk), ++ "Failed to get iis clock\n"); ++ + + ret = clk_prepare_enable(priv->clk); + if (ret != 0) { +@@ -1650,8 +1666,7 @@ static const struct samsung_i2s_dai_data i2sv6_dai_type __maybe_unused = { + }; + + static const struct samsung_i2s_dai_data i2sv7_dai_type __maybe_unused = { +- .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | +- QUIRK_SUPPORTS_TDM, ++ .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM, + .pcm_rates = SNDRV_PCM_RATE_8000_192000, + .i2s_variant_regs = &i2sv7_regs, + }; +@@ -1675,7 +1690,7 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = { + .name = "samsung-i2s", + .driver_data = (kernel_ulong_t)&i2sv3_dai_type, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids); + +diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c +--- a/sound/soc/samsung/smdk_spdif.c ++++ b/sound/soc/samsung/smdk_spdif.c +@@ -130,12 +130,8 @@ static int smdk_hw_params(struct snd_pcm_substream *substream, + return ret; + + /* Set S/PDIF uses internal source clock */ +- ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, +- rclk_rate, SND_SOC_CLOCK_IN); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, ++ rclk_rate, SND_SOC_CLOCK_IN); + } + + static const struct snd_soc_ops smdk_spdif_ops = { +diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c +--- a/sound/soc/samsung/spdif.c ++++ b/sound/soc/samsung/spdif.c +@@ -380,8 +380,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->pclk = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(spdif->pclk)) { +- dev_err(&pdev->dev, "failed to get peri-clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->pclk), ++ "failed to get peri-clock\n"); + goto err0; + } + ret = clk_prepare_enable(spdif->pclk); +@@ -390,8 +390,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif"); + if (IS_ERR(spdif->sclk)) { +- dev_err(&pdev->dev, "failed to get internal source clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->sclk), ++ "failed to get internal source clock\n"); + goto err1; + } + ret = clk_prepare_enable(spdif->sclk); +diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c +--- a/sound/soc/sdca/sdca_asoc.c ++++ b/sound/soc/sdca/sdca_asoc.c +@@ -364,7 +364,6 @@ static int entity_parse_ot(struct device *dev, + + /** + * sdca_asoc_pde_poll_actual_ps - Verify PDE power state reached target state +- * @dev: Pointer to the device for error logging. + * @regmap: Register map for reading ACTUAL_PS register. + * @function_id: SDCA function identifier. + * @entity_id: SDCA entity identifier for the power domain. +@@ -389,11 +388,11 @@ static int entity_parse_ot(struct device *dev, + * polling times out before reaching the target state, or a negative error code if + * a register read fails. + */ +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays) ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays) + { + static const int polls = 100; + static const int default_poll_us = 1000; +@@ -451,14 +450,14 @@ static int entity_pde_event(struct snd_soc_dapm_widget *widget, + return 0; + } + +- ret = sdca_asoc_pde_poll_actual_ps(component->dev, component->regmap, ++ ret = sdca_asoc_pde_poll_actual_ps(component->regmap, + SDW_SDCA_CTL_FUNC(widget->reg), + SDW_SDCA_CTL_ENT(widget->reg), + from, to, + entity->pde.max_delay, + entity->pde.num_max_delay); + if (ret) +- dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ dev_err(component->dev, "%s: pde transition %x -> %x failed: %d\n", + entity->label, from, to, ret); + + return ret; +@@ -1231,7 +1230,7 @@ static u64 width_find_mask(unsigned int bits) + } + } + +-static int populate_rate_format(struct device *dev, ++int sdca_asoc_populate_rate_format(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_pcm_stream *stream) +@@ -1292,6 +1291,7 @@ static int populate_rate_format(struct device *dev, + + return 0; + } ++EXPORT_SYMBOL_NS(sdca_asoc_populate_rate_format, "SND_SOC_SDCA"); + + /** + * sdca_asoc_populate_dais - fill in an array of DAI drivers for a Function +@@ -1344,7 +1344,7 @@ int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *funct + stream->channels_min = 1; + stream->channels_max = SDCA_MAX_CHANNEL_COUNT; + +- ret = populate_rate_format(dev, function, entity, stream); ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, stream); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -329,7 +329,7 @@ static int class_function_probe(struct auxiliary_device *auxdev, + drv->core = core; + drv->function = &sdev->function; + +- ret = sdca_parse_function(dev, core->sdw, drv->function); ++ ret = sdca_parse_function(dev, drv->function); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c +--- a/sound/soc/sdca/sdca_fdl.c ++++ b/sound/soc/sdca/sdca_fdl.c +@@ -195,7 +195,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + { + struct device *dev = interrupt->dev; + struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; +- const struct firmware *firmware = NULL; + struct acpi_sw_file *swf = NULL, *tmp; + struct sdca_fdl_file *fdl_file; + char *disk_filename; +@@ -230,6 +229,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); + ++ const struct firmware *firmware __free(firmware) = NULL; + ret = firmware_request_nowarn(&firmware, disk_filename, dev); + kfree(disk_filename); + if (ret) { +@@ -258,8 +258,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + if (!swf) { + dev_err(dev, "failed to locate SWF\n"); +- ret = -ENOENT; +- goto error; ++ return -ENOENT; + } + + dev_info(dev, "loading SWF: %x-%x-%x\n", +@@ -271,9 +270,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, + SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, + swf->file_length - offsetof(struct acpi_sw_file, data)); +- +-error: +- release_firmware(firmware); + return ret; + } + +diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c +--- a/sound/soc/sdca/sdca_function_device.c ++++ b/sound/soc/sdca/sdca_function_device.c +@@ -32,7 +32,8 @@ static void sdca_dev_release(struct device *dev) + + /* alloc, init and add link devices */ + static struct sdca_dev *sdca_dev_register(struct device *parent, +- struct sdca_function_desc *function_desc) ++ struct sdca_function_desc *function_desc, ++ struct acpi_table_swft *swft) + { + struct sdca_dev *sdev; + struct auxiliary_device *auxdev; +@@ -50,6 +51,7 @@ static struct sdca_dev *sdca_dev_register(struct device *parent, + auxdev->dev.release = sdca_dev_release; + + sdev->function.desc = function_desc; ++ sdev->function.fdl_data.swft = swft; + + rc = ida_alloc(&sdca_function_ida, GFP_KERNEL); + if (rc < 0) { +@@ -99,7 +101,8 @@ int sdca_dev_register_functions(struct sdw_slave *slave) + struct sdca_dev *func_dev; + + func_dev = sdca_dev_register(&slave->dev, +- &sdca_data->function[i]); ++ &sdca_data->function[i], ++ sdca_data->swft); + if (IS_ERR(func_dev)) { + ret = PTR_ERR(func_dev); + /* +diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c +--- a/sound/soc/sdca/sdca_functions.c ++++ b/sound/soc/sdca/sdca_functions.c +@@ -7,6 +7,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -18,7 +19,6 @@ + #include + #include + #include +-#include + + /* + * Should be long enough to encompass all the MIPI DisCo properties. +@@ -1365,90 +1365,76 @@ static int find_sdca_entity_ge(struct device *dev, + return -EINVAL; + } + +-static int +-find_sdca_entity_hide(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, +- struct fwnode_handle *entity_node, struct sdca_entity *entity) ++static int find_sdca_entity_hide(struct device *dev, ++ struct fwnode_handle *entity_node, ++ struct sdca_entity *entity) + { + struct sdca_entity_hide *hide = &entity->hide; +- unsigned int delay, *af_list = hide->af_number_list; +- int nval, ret; +- unsigned char *report_desc = NULL; ++ int num_reports, ret; ++ unsigned int delay; + + ret = fwnode_property_read_u32(entity_node, +- "mipi-sdca-RxUMP-ownership-transition-max-delay", &delay); ++ "mipi-sdca-RxUMP-ownership-transition-max-delay", ++ &delay); + if (!ret) + hide->max_delay = delay; + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDTx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidtx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid tx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidtx_ids = num_reports; + hide->hidtx_ids = devm_kcalloc(dev, hide->num_hidtx_ids, + sizeof(*hide->hidtx_ids), GFP_KERNEL); + if (!hide->hidtx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDTx-supported-report-ids", +- hide->hidtx_ids, +- hide->num_hidtx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids", ++ hide->hidtx_ids, hide->num_hidtx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDRx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidrx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid rx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidrx_ids = num_reports; + hide->hidrx_ids = devm_kcalloc(dev, hide->num_hidrx_ids, + sizeof(*hide->hidrx_ids), GFP_KERNEL); + if (!hide->hidrx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDRx-supported-report-ids", +- hide->hidrx_ids, +- hide->num_hidrx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids", ++ hide->hidrx_ids, hide->num_hidrx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-hide-related-audio-function-list"); +- if (nval <= 0) { ++ /* ++ * FIXME: This should probably link to the actual sdca_function_data pointer, ++ * but updating to do so should probably wait until we have a user. ++ */ ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-hide-related-audio-function-list"); ++ if (num_reports <= 0) { + dev_err(dev, "%pfwP: audio function numbers list missing: %d\n", +- entity_node, nval); ++ entity_node, num_reports); + return -EINVAL; +- } else if (nval > SDCA_MAX_FUNCTION_COUNT) { +- dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", entity_node); ++ } else if (num_reports > ARRAY_SIZE(hide->af_number_list)) { ++ dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", ++ entity_node); + return -EINVAL; + } + +- hide->hide_reside_function_num = nval; ++ hide->hide_reside_function_num = num_reports; + fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-hide-related-audio-function-list", af_list, nval); +- +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); +- if (nval) +- fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", +- (u8 *)&hide->hid_desc, nval); +- +- if (hide->hid_desc.bNumDescriptors) { +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); +- if (nval) { +- report_desc = devm_kzalloc(dev, nval, GFP_KERNEL); +- if (!report_desc) +- return -ENOMEM; +- hide->hid_report_desc = report_desc; +- fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", +- report_desc, nval); +- +- /* add HID device */ +- ret = sdca_add_hid_device(dev, sdw, entity); +- if (ret) { +- dev_err(dev, "%pfwP: failed to add HID device: %d\n", entity_node, ret); +- return ret; +- } +- } +- } ++ "mipi-sdca-hide-related-audio-function-list", ++ hide->af_number_list, num_reports); + + return 0; + } +@@ -1475,8 +1461,7 @@ static int find_sdca_entity_xu(struct device *dev, + return 0; + } + +-static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function, ++static int find_sdca_entity(struct device *dev, struct sdca_function_data *function, + struct fwnode_handle *function_node, + struct fwnode_handle *entity_node, + struct sdca_entity *entity) +@@ -1528,8 +1513,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + ret = find_sdca_entity_ge(dev, entity_node, entity); + break; + case SDCA_ENTITY_TYPE_HIDE: +- ret = find_sdca_entity_hide(dev, sdw, function_node, +- entity_node, entity); ++ ret = find_sdca_entity_hide(dev, entity_node, entity); + break; + default: + break; +@@ -1544,8 +1528,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_entities(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + struct sdca_entity *entities; +@@ -1596,7 +1579,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return -EINVAL; + } + +- ret = find_sdca_entity(dev, sdw, function, function_node, ++ ret = find_sdca_entity(dev, function, function_node, + entity_node, &entities[i]); + fwnode_handle_put(entity_node); + if (ret) +@@ -1619,7 +1602,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data *function, ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, + const char *entity_label) + { + struct sdca_entity *entity = NULL; +@@ -1648,6 +1631,7 @@ static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data * + + return NULL; + } ++EXPORT_SYMBOL_NS(sdca_find_entity_by_label, "SND_SOC_SDCA"); + + static struct sdca_entity *find_sdca_entity_by_id(struct sdca_function_data *function, + const int id) +@@ -1688,7 +1672,7 @@ static int find_sdca_entity_connection_iot(struct device *dev, + return ret; + } + +- clock_entity = find_sdca_entity_by_label(function, clock_label); ++ clock_entity = sdca_find_entity_by_label(function, clock_label); + if (!clock_entity) { + dev_err(dev, "%s: failed to find clock with label %s\n", + entity->label, clock_label); +@@ -1873,7 +1857,7 @@ static int find_sdca_entity_connection(struct device *dev, + return ret; + } + +- connected_entity = find_sdca_entity_by_label(function, connected_label); ++ connected_entity = sdca_find_entity_by_label(function, connected_label); + if (!connected_entity) { + dev_err(dev, "%s: failed to find entity with label %s\n", + entity->label, connected_label); +@@ -2088,8 +2072,7 @@ static int find_sdca_clusters(struct device *dev, + return 0; + } + +-static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_filesets(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + static const int mult_fileset = 3; +@@ -2171,23 +2154,65 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, + set->files = files; + } + +- function->fdl_data.swft = sdw->sdca_data.swft; + function->fdl_data.num_sets = num_sets; + function->fdl_data.sets = sets; + + return 0; + } + ++static int find_sdca_hid(struct device *dev, struct fwnode_handle *function_node, ++ struct sdca_function_data *function) ++{ ++ int num_desc; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); ++ if (!num_desc) { ++ return 0; ++ } else if (num_desc < 0) { ++ dev_err(dev, "%pfwP: failed to read hid descriptor: %d\n", ++ function_node, num_desc); ++ return num_desc; ++ } else if (num_desc > sizeof(function->hid.desc)) { ++ dev_err(dev, "%pfwP: hid descriptor too large: %d\n", ++ function_node, num_desc); ++ return -EINVAL; ++ } ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", ++ (u8 *)&function->hid.desc, num_desc); ++ ++ if (!function->hid.desc.bNumDescriptors) ++ return 0; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); ++ if (num_desc <= 0) { ++ dev_err(dev, "%pfwP: failed to read report descriptor: %d\n", ++ function_node, num_desc); ++ ++ if (!num_desc) ++ return -EINVAL; ++ ++ return num_desc; ++ } ++ ++ function->hid.report_desc = devm_kzalloc(dev, num_desc, GFP_KERNEL); ++ if (!function->hid.report_desc) ++ return -ENOMEM; ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", ++ function->hid.report_desc, num_desc); ++ ++ return 0; ++} ++ + /** + * sdca_parse_function - parse ACPI DisCo for a Function + * @dev: Pointer to device against which function data will be allocated. +- * @sdw: SoundWire slave device to be processed. + * @function: Pointer to the Function information, to be populated. + * + * Return: Returns 0 for success. + */ +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function) ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function) + { + struct fwnode_handle *node = function->desc->node; + u32 tmp; +@@ -2213,7 +2238,7 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret) + return ret; + +- ret = find_sdca_entities(dev, sdw, node, function); ++ ret = find_sdca_entities(dev, node, function); + if (ret) + return ret; + +@@ -2225,10 +2250,20 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret < 0) + return ret; + +- ret = find_sdca_filesets(dev, sdw, node, function); ++ ret = find_sdca_filesets(dev, node, function); + if (ret) + return ret; + ++ switch (function->desc->type) { ++ case SDCA_FUNCTION_TYPE_HID: ++ ret = find_sdca_hid(dev, node, function); ++ if (ret) ++ return ret; ++ break; ++ default: ++ break; ++ } ++ + return 0; + } + EXPORT_SYMBOL_NS(sdca_parse_function, "SND_SOC_SDCA"); +diff --git a/sound/soc/sdca/sdca_hid.c b/sound/soc/sdca/sdca_hid.c +--- a/sound/soc/sdca/sdca_hid.c ++++ b/sound/soc/sdca/sdca_hid.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -23,18 +24,18 @@ + + static int sdwhid_parse(struct hid_device *hid) + { +- struct sdca_entity *entity = hid->driver_data; ++ struct sdca_function_data *function = hid->driver_data; + unsigned int rsize; + int ret; + +- rsize = le16_to_cpu(entity->hide.hid_desc.rpt_desc.wDescriptorLength); ++ rsize = le16_to_cpu(function->hid.desc.rpt_desc.wDescriptorLength); + + if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { + dev_err(&hid->dev, "invalid size of report descriptor (%u)\n", rsize); + return -EINVAL; + } + +- ret = hid_parse_report(hid, entity->hide.hid_report_desc, rsize); ++ ret = hid_parse_report(hid, function->hid.report_desc, rsize); + + if (!ret) + return 0; +@@ -85,10 +86,16 @@ static const struct hid_ll_driver sdw_hid_driver = { + .raw_request = sdwhid_raw_request, + }; + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++/** ++ * sdca_add_hid_device - create a new SDCA HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ * ++ * Return: Zero on success, and a negative error code on failure. ++ */ ++int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { +- struct sdw_bus *bus = sdw->bus; ++ struct device *dev = interrupt->dev; ++ struct sdca_function_data *function = interrupt->function; + struct hid_device *hid; + int ret; + +@@ -100,16 +107,13 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + + hid->dev.parent = dev; + hid->bus = BUS_SDW; +- hid->version = le16_to_cpu(entity->hide.hid_desc.bcdHID); ++ hid->version = le16_to_cpu(function->hid.desc.bcdHID); + +- snprintf(hid->name, sizeof(hid->name), +- "HID sdw:%01x:%01x:%04x:%04x:%02x", +- bus->controller_id, bus->link_id, sdw->id.mfg_id, +- sdw->id.part_id, sdw->id.class_id); ++ strscpy(hid->phys, dev_name(dev)); ++ snprintf(hid->name, sizeof(hid->name), "SDCA %s:%02x", ++ function->desc->name, function->desc->adr); + +- snprintf(hid->phys, sizeof(hid->phys), "%s", dev->bus->name); +- +- hid->driver_data = entity; ++ hid->driver_data = function; + + ret = hid_add_device(hid); + if (ret && ret != -ENODEV) { +@@ -118,12 +122,24 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + return ret; + } + +- entity->hide.hid = hid; ++ interrupt->priv = hid; + + return 0; + } + EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + ++/** ++ * sdca_destroy_hid_device - destroy the HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ */ ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct hid_device *hid = interrupt->priv; ++ ++ hid_destroy_device(hid); ++} ++EXPORT_SYMBOL_NS(sdca_destroy_hid_device, "SND_SOC_SDCA"); ++ + /** + * sdca_hid_process_report - read a HID event from the device and report + * @interrupt: Pointer to the SDCA interrupt information structure. +@@ -133,7 +149,7 @@ EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + struct device *dev = interrupt->dev; +- struct hid_device *hid = interrupt->entity->hide.hid; ++ struct hid_device *hid = interrupt->priv; + void *val __free(kfree) = NULL; + int len, ret; + +diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c +--- a/sound/soc/sdca/sdca_interrupts.c ++++ b/sound/soc/sdca/sdca_interrupts.c +@@ -487,6 +487,12 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, + } + break; + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ interrupt->free_priv = sdca_destroy_hid_device; ++ ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ + interrupt->handler = hid_handler; + break; + default: +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -834,6 +834,35 @@ struct asoc_sdw_codec_info codec_info_list[] = { + }, + .dai_num = 2, + }, ++ { ++ .vendor_id = 0x01fa, ++ .part_id = 0x3562, ++ .name_prefix = "AMP", ++ .is_amp = true, ++ .dais = { ++ { ++ .direction = {true, false}, ++ .dai_name = "cs35l56-sdw1", ++ .component_name = "cs35l56", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, ++ .init = asoc_sdw_cs_amp_init, ++ .rtd_init = asoc_sdw_cs_spk_rtd_init, ++ .controls = generic_spk_controls, ++ .num_controls = ARRAY_SIZE(generic_spk_controls), ++ .widgets = generic_spk_widgets, ++ .num_widgets = ARRAY_SIZE(generic_spk_widgets), ++ }, ++ { ++ .direction = {false, true}, ++ .dai_name = "cs35l56-sdw1c", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, ++ .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, ++ }, ++ }, ++ .dai_num = 2, ++ }, + { + .vendor_id = 0x01fa, + .part_id = 0x3563, +@@ -1789,9 +1818,7 @@ static int is_sdca_aux_dev_present(struct device *dev, + int adr_index) + { + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret = 0; + int i; + + if (!aux_codec_name) +@@ -1801,7 +1828,8 @@ static int is_sdca_aux_dev_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1811,24 +1839,19 @@ static int is_sdca_aux_dev_present(struct device *dev, + + if (!slave->sdca_data.interface_revision) { + dev_warn(dev, "No SDCA properties, assuming aux '%s' present\n", aux_codec_name); +- ret = 1; +- goto put_dev; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { + const char *fname = slave->sdca_data.function[i].name; + +- if (fname && strstr(aux_codec_name, fname)) { +- ret = 1; +- goto put_dev; +- } ++ if (fname && strstr(aux_codec_name, fname)) ++ return 1; + } + + dev_dbg(dev, "SDCA function for aux '%s' NOT FOUND on slave, skipping\n", aux_codec_name); + +-put_dev: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + + int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, +@@ -1928,9 +1951,8 @@ static int is_sdca_endpoint_present(struct device *dev, + const struct snd_soc_acpi_endpoint *adr_end; + const struct asoc_sdw_dai_info *dai_info; + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret, i; ++ int i; + + adr_end = &adr_dev->endpoints[end_index]; + dai_info = &codec_info->dais[adr_end->num]; +@@ -1939,7 +1961,8 @@ static int is_sdca_endpoint_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1950,8 +1973,7 @@ static int is_sdca_endpoint_present(struct device *dev, + /* Make sure BIOS provides SDCA properties */ + if (!slave->sdca_data.interface_revision) { + dev_warn(&slave->dev, "SDCA properties not found in the BIOS\n"); +- ret = 1; +- goto put_device; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { +@@ -1960,8 +1982,7 @@ static int is_sdca_endpoint_present(struct device *dev, + if (dai_type == dai_info->dai_type) { + dev_dbg(&slave->dev, "DAI type %d sdca function %s found\n", + dai_type, slave->sdca_data.function[i].name); +- ret = 1; +- goto put_device; ++ return 1; + } + } + +@@ -1969,11 +1990,7 @@ static int is_sdca_endpoint_present(struct device *dev, + "SDCA device function for DAI type %d not supported, skip endpoint\n", + dai_info->dai_type); + +- ret = 0; +- +-put_device: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + + int asoc_sdw_parse_sdw_endpoints(struct device *dev, +diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c +--- a/sound/soc/soc-component.c ++++ b/sound/soc/soc-component.c +@@ -29,17 +29,42 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, + func, component->name, reg); + } + +-static inline int soc_component_field_shift(struct snd_soc_component *component, +- unsigned int mask) ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev) + { +- if (!mask) { +- dev_err(component->dev, "ASoC: error field mask is zero for %s\n", +- component->name); +- return 0; +- } ++ struct snd_soc_component *component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + +- return (ffs(mask) - 1); ++ if (!component) ++ return NULL; ++ ++ component->dev = dev; ++ ++ return component; + } ++EXPORT_SYMBOL_GPL(snd_soc_component_alloc); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name) ++{ ++ component->name = name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_name); ++ ++const char *snd_soc_component_name(struct snd_soc_component *component) ++{ ++ return component->name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_name); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv) ++{ ++ component->priv = priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_priv); ++ ++void *snd_soc_component_to_priv(struct snd_soc_component *component) ++{ ++ return component->priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_to_priv); + + /* + * We might want to check substream by using list. +@@ -820,6 +845,18 @@ int snd_soc_component_update_bits_async(struct snd_soc_component *component, + } + EXPORT_SYMBOL_GPL(snd_soc_component_update_bits_async); + ++static inline int soc_component_field_shift(struct snd_soc_component *component, ++ unsigned int mask) ++{ ++ if (!mask) { ++ dev_err(component->dev, "ASoC: error field mask is zero for %s\n", ++ component->name); ++ return 0; ++ } ++ ++ return (ffs(mask) - 1); ++} ++ + /** + * snd_soc_component_read_field() - Read register field value + * @component: Component to read from +diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c +--- a/sound/soc/soc-core.c ++++ b/sound/soc/soc-core.c +@@ -2707,10 +2707,11 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) + list_del(&component->list); + } + +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev) ++static int soc_component_initialize(struct snd_soc_component *component, ++ const struct snd_soc_component_driver *driver) + { ++ struct device *dev = component->dev; ++ + component->dapm = snd_soc_dapm_alloc(dev); + if (!component->dapm) + return -ENOMEM; +@@ -2730,16 +2731,14 @@ int snd_soc_component_initialize(struct snd_soc_component *component, + } + } + +- component->dev = dev; + component->driver = driver; + + return 0; + } +-EXPORT_SYMBOL_GPL(snd_soc_component_initialize); + +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai) ++static int soc_component_add(struct snd_soc_component *component, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) + { + struct snd_soc_card *card, *c; + int ret; +@@ -2778,27 +2777,36 @@ int snd_soc_add_component(struct snd_soc_component *component, + + return ret; + } +-EXPORT_SYMBOL_GPL(snd_soc_add_component); + +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) + { +- struct snd_soc_component *component; + int ret; + +- component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); +- if (!component) +- return -ENOMEM; +- +- ret = snd_soc_component_initialize(component, component_driver, dev); ++ ret = soc_component_initialize(component, component_driver); + if (ret < 0) + return ret; + +- return snd_soc_add_component(component, dai_drv, num_dai); ++ return soc_component_add(component, dai_drv, num_dai); + } +-EXPORT_SYMBOL_GPL(snd_soc_register_component); ++EXPORT_SYMBOL_GPL(snd_soc_register_component_c); ++ ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) ++{ ++ struct snd_soc_component *component; ++ ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ ++ return snd_soc_register_component_c(component, component_driver, dai_drv, num_dai); ++} ++EXPORT_SYMBOL_GPL(snd_soc_register_component_d); + + /** + * snd_soc_unregister_component_by_driver - Unregister component using a given driver +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -1203,6 +1203,8 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + wname_in_long_name = true; + kcname_in_long_name = true; + break; +@@ -2785,6 +2787,8 @@ static ssize_t dapm_widget_show_component(struct snd_soc_component *component, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: +@@ -3367,6 +3371,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + dapm_new_pga(w); + break; + case snd_soc_dapm_dai_link: +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -3,6 +3,7 @@ + // Copyright (C) 2013, Analog Devices Inc. + // Author: Lars-Peter Clausen + ++#include + #include + #include + #include +@@ -77,7 +78,7 @@ static int dmaengine_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct dma_slave_config slave_config; + int ret; +@@ -99,7 +100,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct device *dma_dev = dmaengine_dma_dev(pcm, substream); + struct dma_chan *chan = pcm->chan[substream->stream]; + struct snd_dmaengine_dai_dma_data *dma_data; +@@ -148,7 +149,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + static int dmaengine_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = pcm->chan[substream->stream]; + int ret; + +@@ -176,7 +177,7 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel( + struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct snd_dmaengine_dai_dma_data *dma_data; + + if (rtd->dai_link->num_cpus > 1) { +@@ -219,7 +220,7 @@ static bool dmaengine_pcm_can_report_residue(struct device *dev, + static int dmaengine_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + const struct snd_dmaengine_pcm_config *config = pcm->config; + struct device *dev = component->dev; + size_t prealloc_buffer_size; +@@ -279,7 +280,7 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( + struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) + return snd_dmaengine_pcm_pointer_no_residue(substream); +@@ -293,7 +294,7 @@ static int dmaengine_copy(struct snd_soc_component *component, + struct iov_iter *iter, unsigned long bytes) + { + struct snd_pcm_runtime *runtime = substream->runtime; +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + int (*process)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + unsigned long bytes) = pcm->config->process; +@@ -395,6 +396,27 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + */ + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; ++ ++ bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); ++ bool name_exists_in_fw = false; ++ ++ if (has_fw_node) ++ name_exists_in_fw = device_property_match_string(dev, ++ "dma-names", ++ name) >= 0; ++ ++ if (has_fw_node && name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (has_fw_node && !name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (!has_fw_node) ++ dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ + pcm->chan[i] = NULL; + } else { + pcm->chan[i] = chan; +@@ -406,6 +428,12 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + pcm->chan[1] = pcm->chan[0]; + ++ if (!pcm->chan[0] && ++ !pcm->chan[1]) { ++ dev_err(dev, "no DMA channel found for either playback or capture\n"); ++ return -ENODEV; ++ } ++ + return 0; + } + +@@ -435,10 +463,15 @@ static const struct snd_dmaengine_pcm_config snd_dmaengine_pcm_default_config = + int snd_dmaengine_pcm_register(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags) + { ++ struct snd_soc_component *component; + const struct snd_soc_component_driver *driver; + struct dmaengine_pcm *pcm; + int ret; + ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ + pcm = kzalloc_obj(*pcm); + if (!pcm) + return -ENOMEM; +@@ -449,7 +482,8 @@ int snd_dmaengine_pcm_register(struct device *dev, + pcm->flags = flags; + + if (config->name) +- pcm->component.name = config->name; ++ snd_soc_component_set_name(component, config->name); ++ snd_soc_component_set_priv(component, pcm); + + ret = dmaengine_pcm_request_chan_of(pcm, dev, config); + if (ret) +@@ -460,11 +494,7 @@ int snd_dmaengine_pcm_register(struct device *dev, + else + driver = &dmaengine_pcm_component; + +- ret = snd_soc_component_initialize(&pcm->component, driver, dev); +- if (ret) +- goto err_free_dma; +- +- ret = snd_soc_add_component(&pcm->component, NULL, 0); ++ ret = snd_soc_register_component(component, driver, NULL, 0); + if (ret) + goto err_free_dma; + +@@ -493,7 +523,7 @@ void snd_dmaengine_pcm_unregister(struct device *dev) + if (!component) + return; + +- pcm = soc_component_to_pcm(component); ++ pcm = snd_soc_component_to_priv(component); + + snd_soc_unregister_component_by_driver(dev, component->driver); + dmaengine_pcm_release_chan(pcm); +diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c +--- a/sound/soc/soc-topology-test.c ++++ b/sound/soc/soc-topology-test.c +@@ -45,15 +45,13 @@ static void snd_soc_tplg_test_exit(struct kunit *test) + struct kunit_soc_component { + struct kunit *kunit; + int expect; /* what result we expect when loading topology */ +- struct snd_soc_component comp; + struct snd_soc_card card; + struct firmware fw; + }; + + static int d_probe(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_load(component, NULL, &kunit_comp->fw); +@@ -65,8 +63,7 @@ static int d_probe(struct snd_soc_component *component) + + static void d_remove(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_remove(component); +@@ -214,8 +211,7 @@ static struct tplg_tmpl_002 tplg_tmpl_with_pcm = { + */ + static int d_probe_null_comp(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing component pointer as first argument, pass NULL here */ +@@ -234,6 +230,7 @@ static const struct snd_soc_component_driver test_component_null_comp = { + static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -249,15 +246,17 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_comp, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_comp, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -276,6 +275,7 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -291,15 +291,17 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -318,8 +320,7 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + */ + static int d_probe_null_fw(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing fw pointer as third argument, pass NULL here */ +@@ -338,6 +339,7 @@ static const struct snd_soc_component_driver test_component_null_fw = { + static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -353,15 +355,17 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_fw, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_fw, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -375,6 +379,7 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -401,15 +406,17 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -425,6 +432,7 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -456,15 +464,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -480,6 +490,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -511,15 +522,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -535,6 +548,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -566,15 +580,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -590,6 +606,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -622,15 +639,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -644,6 +663,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -670,15 +690,17 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -693,6 +715,7 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -720,16 +743,19 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + + for (i = 0; i < 100; i++) { +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -745,6 +771,7 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -772,11 +799,13 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + +- /* run test */ +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ snd_soc_component_set_priv(component, kunit_comp); ++ ++ /* run test */ ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + for (i = 0; i < 100; i++) { +diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig +--- a/sound/soc/sof/amd/Kconfig ++++ b/sound/soc/sof/amd/Kconfig +@@ -104,4 +104,15 @@ config SND_SOC_SOF_AMD_ACP70 + AMD ACP7.0/ACP7.1 version based platforms. + Say Y if you want to enable SOF on ACP7.0/ACP7.1 based platforms. + ++config SND_SOC_SOF_AMD_ACP7X ++ tristate "SOF support for ACP7.B/7.F platforms" ++ depends on SND_SOC_SOF_PCI ++ depends on AMD_NODE ++ select SND_SOC_SOF_AMD_COMMON ++ help ++ Select this option for SOF support on AMD ACP7.B and ACP7.F PCI ++ revision based platforms. ++ Say Y if you want to enable SOF on ACP7.B/7.F based platforms. ++ If unsure select "N". ++ + endif +diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile +--- a/sound/soc/sof/amd/Makefile ++++ b/sound/soc/sof/amd/Makefile +@@ -11,6 +11,7 @@ snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o + snd-sof-amd-vangogh-y := pci-vangogh.o vangogh.o + snd-sof-amd-acp63-y := pci-acp63.o acp63.o + snd-sof-amd-acp70-y := pci-acp70.o acp70.o ++snd-sof-amd-acp7x-y := pci-acp7x.o acp7x.o + + obj-$(CONFIG_SND_SOC_SOF_AMD_COMMON) += snd-sof-amd-acp.o + obj-$(CONFIG_SND_SOC_SOF_AMD_RENOIR) += snd-sof-amd-renoir.o +@@ -18,3 +19,4 @@ obj-$(CONFIG_SND_SOC_SOF_AMD_REMBRANDT) += snd-sof-amd-rembrandt.o + obj-$(CONFIG_SND_SOC_SOF_AMD_VANGOGH) += snd-sof-amd-vangogh.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP63) += snd-sof-amd-acp63.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP70) += snd-sof-amd-acp70.o ++obj-$(CONFIG_SND_SOC_SOF_AMD_ACP7X) += snd-sof-amd-acp7x.o +diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h +--- a/sound/soc/sof/amd/acp-dsp-offset.h ++++ b/sound/soc/sof/amd/acp-dsp-offset.h +@@ -68,12 +68,14 @@ + #define ACP5X_PGFSM_BASE 0x1424 + #define ACP6X_PGFSM_BASE 0x1024 + #define ACP70_PGFSM_BASE ACP6X_PGFSM_BASE ++#define ACP7X_PGFSM_BASE ACP6X_PGFSM_BASE + #define PGFSM_CONTROL_OFFSET 0x0 + #define PGFSM_STATUS_OFFSET 0x4 + #define ACP3X_CLKMUX_SEL 0x1424 + #define ACP5X_CLKMUX_SEL 0x142C + #define ACP6X_CLKMUX_SEL 0x102C + #define ACP70_CLKMUX_SEL ACP6X_CLKMUX_SEL ++#define ACP7X_CLKMUX_SEL ACP6X_CLKMUX_SEL + + /* Registers from ACP_INTR block */ + #define ACP3X_EXT_INTR_STAT 0x1808 +@@ -86,21 +88,31 @@ + #define ACP70_EXTERNAL_INTR_CNTL ACP6X_EXTERNAL_INTR_CNTL + #define ACP70_EXT_INTR_STAT ACP6X_EXT_INTR_STAT + #define ACP70_EXT_INTR_STAT1 ACP6X_EXT_INTR_STAT1 ++#define ACP7X_EXTERNAL_INTR_ENB ACP6X_EXTERNAL_INTR_ENB ++#define ACP7X_EXTERNAL_INTR_CNTL 0x1A04 ++#define ACP7X_EXT_INTR_STAT 0x1A1C ++#define ACP7X_EXTERNAL_INTR_CNTL1 0x1A08 ++#define ACP7X_EXT_INTR_STAT1 0x1A20 + + #define ACP3X_DSP_SW_INTR_BASE 0x1814 + #define ACP5X_DSP_SW_INTR_BASE 0x1814 + #define ACP6X_DSP_SW_INTR_BASE 0x1808 + #define ACP70_DSP_SW_INTR_BASE ACP6X_DSP_SW_INTR_BASE ++#define ACP7X_DSP_SW_INTR_BASE 0x1860 + #define DSP_SW_INTR_CNTL_OFFSET 0x0 + #define DSP_SW_INTR_STAT_OFFSET 0x4 ++#define ACP7X_DSP_SW_INTR_STAT (ACP7X_DSP_SW_INTR_BASE + DSP_SW_INTR_STAT_OFFSET) + #define DSP_SW_INTR_TRIG_OFFSET 0x8 ++#define ACP7X_DSP_SW_INTR_TRIG_OFFSET 0x30 + #define ACP3X_ERROR_STATUS 0x18C4 + #define ACP6X_ERROR_STATUS 0x1A4C + #define ACP70_ERROR_STATUS ACP6X_ERROR_STATUS ++#define ACP7X_ERROR_STATUS 0x1A88 + #define ACP3X_AXI2DAGB_SEM_0 0x1880 + #define ACP5X_AXI2DAGB_SEM_0 0x1884 + #define ACP6X_AXI2DAGB_SEM_0 0x1874 + #define ACP70_AXI2DAGB_SEM_0 ACP6X_AXI2DAGB_SEM_0 ++#define ACP7X_AXI2DAGB_SEM_0 0x18F4 + + /* ACP common registers to report errors related to I2S & SoundWire interfaces */ + #define ACP3X_SW_I2S_ERROR_REASON 0x18C8 +@@ -123,6 +135,7 @@ + #define ACP_SCRATCH_REG_0 0x10000 + #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 + #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL ++#define ACP7X_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL + + /* Cache window registers */ + #define ACP_DSP0_CACHE_OFFSET0 0x0420 +@@ -139,5 +152,9 @@ + #define ACP70_SDW1_HOST_WAKE_STAT BIT(25) + #define ACP70_SDW0_PME_STAT BIT(26) + #define ACP70_SDW1_PME_STAT BIT(27) ++#define ACP7X_DSP0_IDMA_ERROR_MASK 0x4B0 ++#define ACP7X_IDMA_ERROR_MASK 0x1FF9FF ++#define ACP7X_ZSC_DSP_CTRL 0x001014 ++#define ACP7X_PME_EN ACP70_PME_EN + + #endif +diff --git a/sound/soc/sof/amd/acp-ipc.c b/sound/soc/sof/amd/acp-ipc.c +--- a/sound/soc/sof/amd/acp-ipc.c ++++ b/sound/soc/sof/amd/acp-ipc.c +@@ -32,11 +32,20 @@ static void acpbus_trigger_host_to_dsp_swintr(struct acp_dev_data *adata) + struct snd_sof_dev *sdev = adata->dev; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 swintr_trigger; ++ unsigned int swintr_trigger_reg_offset; + ++ switch (adata->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ swintr_trigger_reg_offset = ACP7X_DSP_SW_INTR_TRIG_OFFSET; ++ break; ++ default: ++ swintr_trigger_reg_offset = DSP_SW_INTR_TRIG_OFFSET; ++ } + swintr_trigger = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->dsp_intr_base + +- DSP_SW_INTR_TRIG_OFFSET); ++ swintr_trigger_reg_offset); + swintr_trigger |= 0x01; +- snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_TRIG_OFFSET, ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + swintr_trigger_reg_offset, + swintr_trigger); + } + +diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c +--- a/sound/soc/sof/amd/acp-loader.c ++++ b/sound/soc/sof/amd/acp-loader.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "../ops.h" + #include "acp-dsp-offset.h" +@@ -173,10 +174,31 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) + + adata = sdev->pdata->hw_pdata; + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if (adata->pci_rev >= ACP7B_PCI_ID) { ++ if (adata->acp_sof_signed_firmware_image) { ++ if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, "Invalid signed firmware size %u\n", ++ adata->fw_bin_size); ++ return -EINVAL; ++ } ++ size_fw = get_unaligned_le32(adata->bin_buf + ++ ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); ++ if (!size_fw || ++ size_fw > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, ++ "Invalid signed firmware payload size %u (max %u)\n", ++ size_fw, adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE); ++ return -EINVAL; ++ } ++ size_fw += ACP_IMAGE_HEADER_SIZE; ++ } else { ++ size_fw = adata->fw_bin_size; ++ } ++ } else if (adata->quirks && adata->quirks->signed_fw_image) { + size_fw = adata->fw_bin_size - ACP_FIRMWARE_SIGNATURE; +- else ++ } else { + size_fw = adata->fw_bin_size; ++ } + + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; +@@ -312,9 +334,14 @@ int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev) + } + kfree(fw_filename); + +- ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, +- (void *)adata->fw_dbin->data, +- adata->fw_dbin->size); ++ if (adata->pci_rev >= ACP7B_PCI_ID) ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); ++ else ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); + return ret; + } + EXPORT_SYMBOL_NS(acp_sof_load_signed_firmware, "SND_SOC_SOF_AMD_COMMON"); +diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c +--- a/sound/soc/sof/amd/acp.c ++++ b/sound/soc/sof/amd/acp.c +@@ -12,6 +12,7 @@ + * Hardware interface for generic AMD ACP processor + */ + ++#include + #include + #include + #include +@@ -60,6 +61,8 @@ static void init_dma_descriptor(struct acp_dev_data *adata) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_desc_base_addr = ACP70_DMA_DESC_BASE_ADDR; + acp_dma_desc_max_num_dscr = ACP70_DMA_DESC_MAX_NUM_DSCR; + break; +@@ -101,6 +104,8 @@ static int config_dma_channel(struct acp_dev_data *adata, unsigned int ch, + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_cntl_0 = ACP70_DMA_CNTL_0; + acp_dma_ch_rst_sts = ACP70_DMA_CH_RST_STS; + acp_dma_dscr_err_sts_0 = ACP70_DMA_ERR_STS_0; +@@ -283,7 +288,8 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, + } + } + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if ((adata->quirks && adata->quirks->signed_fw_image) || ++ adata->acp_sof_signed_firmware_image) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); +@@ -342,6 +348,8 @@ int acp_dma_status(struct acp_dev_data *adata, unsigned char ch) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_ch_sts = ACP70_DMA_CH_STS; + break; + default: +@@ -534,8 +542,17 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_sw0_i2s_err_reason, 0); + /* ACP_SW1_I2S_ERROR_REASON is newly added register from rmb platform onwards */ +- if (adata->pci_rev >= ACP_RMB_PCI_ID) ++ switch (adata->pci_rev) { ++ case ACP_RMB_PCI_ID: ++ case ACP63_PCI_ID: ++ case ACP70_PCI_ID: ++ case ACP71_PCI_ID: ++ case ACP72_PCI_ID: + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SW1_I2S_ERROR_REASON, 0); ++ break; ++ default: ++ break; ++ } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + irq_flag = 1; + } +@@ -564,6 +581,35 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + return IRQ_NONE; + } + ++static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) ++{ ++ struct snd_sof_dev *sdev = dev_id; ++ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); ++ unsigned int base = desc->dsp_intr_base; ++ unsigned int val; ++ unsigned int ext_intr_stat; ++ int irq_flag = 0; ++ ++ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); ++ if (val & ACP_DSP_TO_HOST_IRQ) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ++ ACP_DSP_TO_HOST_IRQ); ++ return IRQ_WAKE_THREAD; ++ } ++ ++ ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); ++ if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); ++ irq_flag = 1; ++ } ++ ++ if (irq_flag) ++ return IRQ_HANDLED; ++ ++ return IRQ_NONE; ++} ++ + static int acp_power_on(struct snd_sof_dev *sdev) + { + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); +@@ -571,6 +617,7 @@ static int acp_power_on(struct snd_sof_dev *sdev) + unsigned int base = desc->pgfsm_base; + unsigned int val; + unsigned int acp_pgfsm_status_mask, acp_pgfsm_cntl_mask; ++ bool use_masked_status = false; + int ret; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET); +@@ -595,6 +642,12 @@ static int acp_power_on(struct snd_sof_dev *sdev) + acp_pgfsm_status_mask = ACP70_PGFSM_STATUS_MASK; + acp_pgfsm_cntl_mask = ACP70_PGFSM_CNTL_POWER_ON_MASK; + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ acp_pgfsm_status_mask = ACP7X_PGFSM_STATUS_MASK; ++ acp_pgfsm_cntl_mask = ACP7X_PGFSM_CNTL_POWER_ON_MASK; ++ use_masked_status = true; ++ break; + default: + return -EINVAL; + } +@@ -603,8 +656,17 @@ static int acp_power_on(struct snd_sof_dev *sdev) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + PGFSM_CONTROL_OFFSET, + acp_pgfsm_cntl_mask); + +- ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET, val, +- !val, ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US); ++ if (use_masked_status) ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !(val & acp_pgfsm_status_mask), ++ ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); ++ else ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !val, ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "timeout in ACP_PGFSM_STATUS read\n"); + +@@ -703,6 +765,13 @@ static int acp_init(struct snd_sof_dev *sdev) + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP70_PME_EN, 1); + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ++ ACP7X_IDMA_ERROR_MASK); ++ break; + } + return 0; + } +@@ -749,6 +818,8 @@ int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state) + case ACP72_PCI_ID: + enable = true; + break; ++ default: ++ break; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + +@@ -990,6 +1061,194 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) + } + EXPORT_SYMBOL_NS(amd_sof_acp_remove, "SND_SOC_SOF_AMD_COMMON"); + ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) ++{ ++ struct pci_dev *pci = to_pci_dev(sdev->dev); ++ struct acp_dev_data *adata; ++ const struct sof_amd_acp_desc *chip; ++ const union acpi_object *obj; ++ struct acpi_device *adev; ++ unsigned int addr; ++ unsigned int irqflags; ++ int ret; ++ ++ chip = get_chip_info(sdev->pdata); ++ if (!chip) { ++ dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device); ++ return -EIO; ++ } ++ adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data), GFP_KERNEL); ++ if (!adata) ++ return -ENOMEM; ++ ++ adata->dev = sdev; ++ adata->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec", ++ PLATFORM_DEVID_NONE, NULL, 0); ++ if (IS_ERR(adata->dmic_dev)) { ++ dev_err(sdev->dev, "failed to register platform for dmic codec\n"); ++ return PTR_ERR(adata->dmic_dev); ++ } ++ ++ addr = pci_resource_start(pci, ACP_DSP_BAR); ++ sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR)); ++ if (!sdev->bar[ACP_DSP_BAR]) { ++ dev_err(sdev->dev, "ioremap error\n"); ++ ret = -ENXIO; ++ goto unregister_dev; ++ } ++ ++ pci_set_master(pci); ++ adata->addr = addr; ++ adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; ++ adata->pci_rev = pci->revision; ++ mutex_init(&adata->acp_lock); ++ sdev->pdata->hw_pdata = adata; ++ ++ ret = acp_init(sdev); ++ if (ret < 0) ++ goto unregister_dev; ++ ++ adev = ACPI_COMPANION(&pci->dev); ++ ++ sdev->ipc_irq = pci->irq; ++ irqflags = IRQF_SHARED; ++ ++ ret = request_threaded_irq(pci->irq, acp7x_irq_handler, acp_irq_thread, ++ irqflags, "AudioDSP", sdev); ++ if (ret < 0) { ++ dev_err(sdev->dev, "failed to register IRQ %d\n", sdev->ipc_irq); ++ goto unregister_dev; ++ } ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ adata->acp_sof_signed_firmware_image = obj->integer.value; ++ } ++ ++ sdev->dsp_box.offset = 0; ++ sdev->dsp_box.size = BOX_SIZE_512; ++ ++ sdev->host_box.offset = sdev->dsp_box.offset + sdev->dsp_box.size; ++ sdev->host_box.size = BOX_SIZE_512; ++ ++ sdev->debug_box.offset = sdev->host_box.offset + sdev->host_box.size; ++ sdev->debug_box.size = BOX_SIZE_1024; ++ ++ if (adata->acp_sof_signed_firmware_image) { ++ adata->fw_code_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-code.bin", chip->name); ++ if (!adata->fw_code_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ adata->fw_data_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-data.bin", chip->name); ++ if (!adata->fw_data_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ } ++ ++ adata->enable_fw_debug = enable_fw_debug; ++ acp_memory_init(sdev); ++ acp_dsp_stream_init(sdev); ++ ++ return 0; ++ ++free_ipc_irq: ++ free_irq(sdev->ipc_irq, sdev); ++unregister_dev: ++ platform_device_unregister(adata->dmic_dev); ++ return ret; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_probe, "SND_SOC_SOF_AMD_COMMON"); ++ ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *adata = sdev->pdata->hw_pdata; ++ ++ if (sdev->ipc_irq) ++ free_irq(sdev->ipc_irq, sdev); ++ ++ if (adata->dmic_dev) ++ platform_device_unregister(adata->dmic_dev); ++ ++ acp_reset(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ bool enable = false; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_reset(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Reset failed\n"); ++ return ret; ++ } ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ enable = true; ++ break; ++ default: ++ break; ++ } ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Init failed\n"); ++ return ret; ++ } ++ ret = acp_memory_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Memory init failed\n"); ++ return ret; ++ } ++ ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_suspend(sdev, 0); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_resume(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ + MODULE_LICENSE("Dual BSD/GPL"); + MODULE_DESCRIPTION("AMD ACP sof driver"); + MODULE_IMPORT_NS("SOUNDWIRE_AMD_INIT"); +diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h +--- a/sound/soc/sof/amd/acp.h ++++ b/sound/soc/sof/amd/acp.h +@@ -76,6 +76,12 @@ + #define ACP70_PCI_ID 0x70 + #define ACP71_PCI_ID 0x71 + #define ACP72_PCI_ID 0x72 ++#define ACP7B_PCI_ID 0x7B ++#define ACP7F_PCI_ID 0x7F ++ ++#define ACP7X_PGFSM_CNTL_POWER_ON_MASK 0x7F ++#define ACP7X_PGFSM_STATUS_MASK 0xFFF ++#define ACP7X_SRAM_PTE_OFFSET ACP6X_SRAM_PTE_OFFSET + + #define HOST_BRIDGE_CZN 0x1630 + #define HOST_BRIDGE_VGH 0x1645 +@@ -107,6 +113,9 @@ + #define PROBE_STATUS_BIT BIT(31) + + #define ACP_FIRMWARE_SIGNATURE 0x100 ++#define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE ++#define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 ++ + #define ACP_ERROR_IRQ_MASK BIT(29) + #define ACP_SDW0_IRQ_MASK BIT(21) + #define ACP_SDW1_IRQ_MASK BIT(2) +@@ -271,6 +280,7 @@ struct acp_dev_data { + /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ + bool acp70_sdw1_wake_event; + unsigned int pci_rev; ++ int acp_sof_signed_firmware_image; + }; + + void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); +@@ -344,6 +354,16 @@ int sof_acp63_ops_init(struct snd_sof_dev *sdev); + extern struct snd_sof_dsp_ops sof_acp70_ops; + int sof_acp70_ops_init(struct snd_sof_dev *sdev); + ++extern struct snd_sof_dsp_ops sof_acp7x_ops; ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev); ++ ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev); ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state); ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev); ++ + struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev); + /* Machine configuration */ + int snd_amd_acp_find_config(struct pci_dev *pci); +diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/acp7x.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * Hardware interface for Audio DSP on ACP7.B/7.F platforms ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-audio.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define I2S_TDM0_INSTANCE 0 ++#define I2S_TDM1_INSTANCE 1 ++#define I2S_TDM2_INSTANCE 2 ++#define PDM0_DMIC_INSTANCE 3 ++#define PDM1_DMIC_INSTANCE 4 ++ ++static struct snd_soc_dai_driver acp7x_sof_dai[] = { ++ [I2S_TDM0_INSTANCE] = { ++ .id = I2S_TDM0_INSTANCE, ++ .name = "acp-sof-i2s0", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S HS controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM1_INSTANCE] = { ++ .id = I2S_TDM1_INSTANCE, ++ .name = "acp-sof-i2s1", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S BT controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM2_INSTANCE] = { ++ .id = I2S_TDM2_INSTANCE, ++ .name = "acp-sof-i2s2", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S SP controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM0_DMIC_INSTANCE] = { ++ .id = PDM0_DMIC_INSTANCE, ++ .name = "acp-sof-dmic0", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM1_DMIC_INSTANCE] = { ++ .id = PDM1_DMIC_INSTANCE, ++ .name = "acp-sof-dmic1", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ }, ++}; ++ ++static int sof_acp7x_post_fw_run_delay(struct snd_sof_dev *sdev) ++{ ++ /* ++ * Resuming from suspend in some cases may cause the DSP firmware ++ * to enter an unrecoverable faulty state. Delaying a bit any host ++ * to DSP transmission right after firmware boot completion seems ++ * to resolve the issue. ++ */ ++ if (!sdev->first_boot) ++ usleep_range(100, 150); ++ ++ return 0; ++} ++ ++struct snd_sof_dsp_ops sof_acp7x_ops; ++EXPORT_SYMBOL_NS(sof_acp7x_ops, "SND_SOC_SOF_AMD_COMMON"); ++ ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&to_pci_dev(sdev->dev)->dev); ++ const union acpi_object *obj; ++ int acp_sof_signed_firmware_image = 0; ++ int acp_sof_post_fw_run_delay = 0; ++ ++ /* common defaults */ ++ memcpy(&sof_acp7x_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops)); ++ ++ sof_acp7x_ops.drv = acp7x_sof_dai; ++ sof_acp7x_ops.num_drv = ARRAY_SIZE(acp7x_sof_dai); ++ sof_acp7x_ops.probe = amd_sof_acp7x_probe; ++ sof_acp7x_ops.remove = amd_sof_acp7x_remove; ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_signed_firmware_image = obj->integer.value; ++ ++ if (!acpi_dev_get_property(adev, "acp-sof-post_fw_run_delay", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_post_fw_run_delay = obj->integer.value; ++ } ++ ++ if (acp_sof_signed_firmware_image) ++ sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; ++ ++ if (acp_sof_post_fw_run_delay) ++ sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; ++ ++ sof_acp7x_ops.suspend = amd_sof_acp7x_suspend; ++ sof_acp7x_ops.resume = amd_sof_acp7x_resume; ++ sof_acp7x_ops.runtime_suspend = amd_sof_acp7x_suspend_runtime; ++ sof_acp7x_ops.runtime_resume = amd_sof_acp7x_resume_runtime; ++ ++ return 0; ++} +diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/pci-acp7x.c +@@ -0,0 +1,116 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. All rights reserved. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * PCI interface for ACP7.B/7.F devices ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-pci-dev.h" ++#include "../../amd/mach-config.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define ACP7X_FUTURE_REG_ACLK_0 0x18e0 ++#define ACP7X_REG_START 0x1240000 ++#define ACP7X_REG_END 0x125C000 ++ ++static const struct sof_amd_acp_desc acp7x_chip_info = { ++ .name = "acp7x", ++ .pgfsm_base = ACP7X_PGFSM_BASE, ++ .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, ++ .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, ++ .ext_intr_stat = ACP7X_EXT_INTR_STAT, ++ .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, ++ .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, ++ .acp_error_stat = ACP7X_ERROR_STATUS, ++ .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, ++ .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, ++ .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, ++ .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, ++ .reg_start_addr = ACP7X_REG_START, ++ .reg_end_addr = ACP7X_REG_END, ++}; ++ ++static const struct sof_dev_desc acp7x_desc = { ++ .machines = snd_soc_acpi_amd_acp7x_sof_machines, ++ .resindex_lpe_base = 0, ++ .resindex_pcicfg_base = -1, ++ .resindex_imr_base = -1, ++ .irqindex_host_ipc = -1, ++ .chip_info = &acp7x_chip_info, ++ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), ++ .ipc_default = SOF_IPC_TYPE_3, ++ .default_fw_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof", ++ }, ++ .default_tplg_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof-tplg", ++ }, ++ .default_fw_filename = { ++ [SOF_IPC_TYPE_3] = "sof-acp7x.ri", ++ }, ++ .nocodec_tplg_filename = "sof-acp.tplg", ++ .ops = &sof_acp7x_ops, ++ .ops_init = sof_acp7x_ops_init, ++}; ++ ++static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) ++{ ++ unsigned int flag; ++ ++ switch (pci->revision) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ flag = snd_amd_acp_find_config(pci); ++ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) ++ return -ENODEV; ++ ++ return sof_pci_probe(pci, pci_id); ++} ++ ++static void acp7x_pci_remove(struct pci_dev *pci) ++{ ++ sof_pci_remove(pci); ++} ++ ++/* PCI IDs */ ++static const struct pci_device_id acp7x_pci_ids[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), ++ .driver_data = (unsigned long)&acp7x_desc}, ++ { 0, } ++}; ++MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); ++ ++/* pci_driver definition */ ++static struct pci_driver snd_sof_pci_amd_acp7x_driver = { ++ .name = KBUILD_MODNAME, ++ .id_table = acp7x_pci_ids, ++ .probe = acp7x_pci_probe, ++ .remove = acp7x_pci_remove, ++ .driver = { ++ .pm = pm_ptr(&sof_pci_pm), ++ }, ++}; ++module_pci_driver(snd_sof_pci_amd_acp7x_driver); ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_DESCRIPTION("ACP7X SOF Driver"); ++MODULE_IMPORT_NS("SND_SOC_SOF_AMD_COMMON"); ++MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV"); +diff --git a/sound/soc/sof/fw-file-profile.c b/sound/soc/sof/fw-file-profile.c +--- a/sound/soc/sof/fw-file-profile.c ++++ b/sound/soc/sof/fw-file-profile.c +@@ -16,20 +16,19 @@ static int sof_test_firmware_file(struct device *dev, + enum sof_ipc_type *ipc_type_to_adjust) + { + enum sof_ipc_type fw_ipc_type; +- const struct firmware *fw; +- const char *fw_filename; + const u32 *magic; + int ret; + +- fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, +- profile->fw_name); ++ const char *fw_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, ++ profile->fw_name); + if (!fw_filename) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = firmware_request_nowarn(&fw, fw_filename, dev); + if (ret < 0) { + dev_dbg(dev, "Failed to open firmware file: %s\n", fw_filename); +- kfree(fw_filename); + return ret; + } + +@@ -44,8 +43,7 @@ static int sof_test_firmware_file(struct device *dev, + break; + default: + dev_err(dev, "Invalid firmware magic: %#x\n", *magic); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + if (ipc_type_to_adjust) { +@@ -54,13 +52,10 @@ static int sof_test_firmware_file(struct device *dev, + dev_err(dev, + "ipc type mismatch between %s and expected: %d vs %d\n", + fw_filename, fw_ipc_type, profile->ipc_type); +- ret = -EINVAL; ++ return -EINVAL; + } +-out: +- release_firmware(fw); +- kfree(fw_filename); + +- return ret; ++ return 0; + } + + static int sof_test_topology_file(struct device *dev, +diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c +--- a/sound/soc/sof/intel/hda-ctrl.c ++++ b/sound/soc/sof/intel/hda-ctrl.c +@@ -186,6 +186,7 @@ EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON" + int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + { + struct hdac_bus *bus = sof_to_bus(sdev); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct hdac_stream *stream; + int sd_offset, ret = 0; + u32 gctl; +@@ -193,6 +194,16 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + if (bus->chip_init) + return 0; + ++ /* ++ * The controller reset clears the ACE2+ link DMA stream allocation ++ * constraints; reset the masks to reflect this. ++ */ ++ memset(sof_hda->link_dma_active_sdw_mask, 0, ++ sizeof(sof_hda->link_dma_active_sdw_mask)); ++ memset(sof_hda->link_dma_active_multi_mask, 0, ++ sizeof(sof_hda->link_dma_active_multi_mask)); ++ sof_hda->link_dma_out_hda_used_mask = 0; ++ + hda_codec_set_codec_wakeup(sdev, true); + + hda_dsp_ctrl_misc_clock_gating(sdev, false); +@@ -223,6 +234,14 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + /* Accept unsolicited responses */ + snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); + ++ /* Perform a one-time enumeration of the Multi-Link capability */ ++ ret = hda_bus_ml_init(bus); ++ if (ret < 0) { ++ dev_err(sdev->dev, "%s: failed to enumerate multi-links\n", ++ __func__); ++ goto err; ++ } ++ + if (detect_codec) + hda_codec_detect_mask(sdev); + +diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c +--- a/sound/soc/sof/intel/hda-dai-ops.c ++++ b/sound/soc/sof/intel/hda-dai-ops.c +@@ -20,7 +20,7 @@ + /* These ops are only applicable for the HDA DAI's in their current form */ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) + /* +- * This function checks if the host dma channel corresponding ++ * This function checks if the host DMA stream corresponding + * to the link DMA stream_tag argument is assigned to one + * of the FEs connected to the BE DAI. + */ +@@ -42,23 +42,53 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, + } + + static struct hdac_ext_stream * +-hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream) ++hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream, ++ enum hda_bus_ml_link_type link_type) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct sof_intel_hda_stream *hda_stream; + const struct sof_intel_dsp_desc *chip; + struct snd_sof_dev *sdev; + struct hdac_ext_stream *res = NULL; + struct hdac_stream *hstream = NULL; +- + int stream_dir = substream->stream; ++ bool is_multi = link_type == HDA_BUS_ML_LINK_HDA || link_type == HDA_BUS_ML_LINK_UAOL; ++ bool is_play = stream_dir == SNDRV_PCM_STREAM_PLAYBACK; ++ bool is_sdw = link_type == HDA_BUS_ML_LINK_SDW; ++ bool is_hda = link_type == HDA_BUS_ML_LINK_HDA; ++ u32 concur_block_mask = 0; ++ u32 seq_block_mask = 0; ++ unsigned int stream_idx; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + ++ /* ++ * On ACE2+ the link DMA stream allocator must avoid two HW errata, ++ * see the comment on struct sof_intel_hda_dev. ++ * ++ * - Concurrent cross-direction: SoundWire conflicts with HDA, iDisp ++ * and UAOL on the same physical stream index; SSP and DMIC are safe. ++ * - Sequential playback: a stream index previously used by an HDA/iDisp ++ * link cannot drive any non-HDA/iDisp link in the same direction ++ * until the next controller reset. ++ * ++ * The masks are protected by bus->reg_lock; sample them inside the ++ * lock together with the stream walk to keep the decision atomic ++ * with concurrent allocations and releases. ++ */ + guard(spinlock_irq)(&bus->reg_lock); ++ ++ if (is_sdw) ++ concur_block_mask = sof_hda->link_dma_active_multi_mask[!stream_dir]; ++ else if (is_multi) ++ concur_block_mask = sof_hda->link_dma_active_sdw_mask[!stream_dir]; ++ if (is_play && !is_hda) ++ seq_block_mask = sof_hda->link_dma_out_hda_used_mask; ++ + list_for_each_entry(hstream, &bus->stream_list, list) { + struct hdac_ext_stream *hext_stream = + stream_to_hdac_ext_stream(hstream); +@@ -69,6 +99,12 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + sdev = hda_stream->sdev; + chip = get_chip_info(sdev->pdata); + ++ stream_idx = hstream->stream_tag - 1; ++ ++ /* skip streams blocked by the ACE2+ allocator constraints */ ++ if ((concur_block_mask | seq_block_mask) & BIT(stream_idx)) ++ continue; ++ + /* check if link is available */ + if (!hext_stream->link_locked) { + /* +@@ -95,7 +131,7 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + /* + * This must be a hostless stream. +- * So reserve the host DMA channel. ++ * So reserve the host DMA stream. + */ + hda_stream->host_reserved = 1; + break; +@@ -109,6 +145,16 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + res->link_locked = 1; + res->link_substream = substream; ++ ++ stream_idx = res->hstream.stream_tag - 1; ++ if (is_sdw) ++ sof_hda->link_dma_active_sdw_mask[stream_dir] |= BIT(stream_idx); ++ else if (is_multi) ++ sof_hda->link_dma_active_multi_mask[stream_dir] |= BIT(stream_idx); ++ ++ /* persistent OUT HDA/iDisp shadow, cleared only on CRST# */ ++ if (is_hda && is_play) ++ sof_hda->link_dma_out_hda_used_mask |= BIT(stream_idx); + } + + return res; +@@ -143,11 +189,13 @@ static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev + + static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream) ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink) + { + struct hdac_ext_stream *hext_stream; ++ enum hda_bus_ml_link_type link_type = hda_bus_ml_link_get_type(hlink); + +- hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream); ++ hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream, link_type); + if (!hext_stream) + return NULL; + +@@ -160,6 +208,22 @@ static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai + struct snd_pcm_substream *substream) + { + struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream); ++ struct sof_intel_hda_dev *sof_hda = sdev->pdata->hw_pdata; ++ struct hdac_bus *bus = sof_to_bus(sdev); ++ int dir = substream->stream; ++ unsigned int stream_idx = hext_stream->hstream.stream_tag - 1; ++ ++ /* ++ * Drop the stream index from the per-direction active concurrency masks. ++ * The two masks are mutually exclusive for a given stream/direction ++ * (and a stream of the SSP/DMIC kind appears in neither), so a blind ++ * clear of both is safe and lets us avoid having to remember the ++ * link type at allocation time. ++ */ ++ scoped_guard(spinlock_irq, &bus->reg_lock) { ++ sof_hda->link_dma_active_sdw_mask[dir] &= ~BIT(stream_idx); ++ sof_hda->link_dma_active_multi_mask[dir] &= ~BIT(stream_idx); ++ } + + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); +diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c +--- a/sound/soc/sof/intel/hda-dai.c ++++ b/sound/soc/sof/intel/hda-dai.c +@@ -188,7 +188,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, + + if (!hext_stream) { + if (ops->assign_hext_stream) +- hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream); ++ hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream, hlink); + } + + if (!hext_stream) +diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c +--- a/sound/soc/sof/intel/hda-dsp.c ++++ b/sound/soc/sof/intel/hda-dsp.c +@@ -1114,11 +1114,7 @@ static int hda_dsp_s5_quirk(struct snd_sof_dev *sdev) + usleep_range(500, 1000); + + /* Restore state for shutdown, back to reset */ +- ret = hda_dsp_ctrl_link_reset(sdev, true); +- if (ret < 0) +- return ret; +- +- return ret; ++ return hda_dsp_ctrl_link_reset(sdev, true); + } + + int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev) +diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c +--- a/sound/soc/sof/intel/hda-loader.c ++++ b/sound/soc/sof/intel/hda-loader.c +@@ -216,9 +216,10 @@ int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int + EXPORT_SYMBOL_NS(hda_cl_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax) + { +- return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, false); ++ return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, ++ is_iccmax, false); + } + EXPORT_SYMBOL_NS(hda_cl_cleanup, "SND_SOC_SOF_INTEL_HDA_COMMON"); + +@@ -302,7 +303,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->iccmax_dmab, +- persistent_cl_buffer, iccmax_stream); ++ persistent_cl_buffer, iccmax_stream, true); + if (ret1 < 0) { + dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n"); + +@@ -458,7 +459,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, +- persistent_cl_buffer, hext_stream); ++ persistent_cl_buffer, hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); + +@@ -587,7 +588,7 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, + cleanup: + /* clean up even in case of error and return the first error */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, persistent_cl_buffer, +- hext_stream); ++ hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "%s: Code loader DSP cleanup failed\n", __func__); + +diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c +--- a/sound/soc/sof/intel/hda-mlink.c ++++ b/sound/soc/sof/intel/hda-mlink.c +@@ -432,6 +432,10 @@ int hda_bus_ml_init(struct hdac_bus *bus) + if (!bus->mlcap) + return 0; + ++ /* Enumeration is a one time operation, skip if already done */ ++ if (!list_empty(&bus->hlink_list)) ++ return 0; ++ + link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1; + + dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count); +@@ -880,19 +884,6 @@ int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK"); + +-void hda_bus_ml_put_all(struct hdac_bus *bus) +-{ +- struct hdac_ext_link *hlink; +- +- list_for_each_entry(hlink, &bus->hlink_list, list) { +- struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); +- +- if (!h2link->alt) +- snd_hdac_ext_bus_link_put(bus, hlink); +- } +-} +-EXPORT_SYMBOL_NS(hda_bus_ml_put_all, "SND_SOC_SOF_HDA_MLINK"); +- + void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +@@ -903,6 +894,24 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + } + EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK"); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) ++{ ++ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); ++ ++ if (!h2link->alt) ++ return HDA_BUS_ML_LINK_HDA; ++ ++ switch (h2link->elid) { ++ case AZX_REG_ML_LEPTR_ID_SDW: ++ return HDA_BUS_ML_LINK_SDW; ++ case AZX_REG_ML_LEPTR_ID_INTEL_UAOL: ++ return HDA_BUS_ML_LINK_UAOL; ++ default: ++ return HDA_BUS_ML_LINK_OTHER; ++ } ++} ++EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK"); ++ + int hda_bus_ml_resume(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +diff --git a/sound/soc/sof/intel/hda-sdw-bpt.c b/sound/soc/sof/intel/hda-sdw-bpt.c +--- a/sound/soc/sof/intel/hda-sdw-bpt.c ++++ b/sound/soc/sof/intel/hda-sdw-bpt.c +@@ -163,7 +163,7 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream + u32 mask; + int ret; + +- ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, true); ++ ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, false, true); + if (ret < 0) { + dev_err(sdev->dev, "%s: SDW BPT DMA cleanup failed\n", + __func__); +diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c +--- a/sound/soc/sof/intel/hda-stream.c ++++ b/sound/soc/sof/intel/hda-stream.c +@@ -198,13 +198,18 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + + mask = (1 << hstream->index); + ++ /* Reset the spib_addr before disabling SPIB */ ++ if (!enable) ++ sof_io_write(sdev, hstream->spib_addr, 0); ++ + /* enable/disable SPIB for the stream */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_SPIB_BAR, + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, mask, + enable << hstream->index); + + /* set the SPIB value */ +- sof_io_write(sdev, hstream->spib_addr, size); ++ if (enable) ++ sof_io_write(sdev, hstream->spib_addr, size); + + return 0; + } +@@ -608,6 +613,9 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + return ret; + } + ++ /* Host DMA is not running */ ++ hstream->running = false; ++ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK, +@@ -1323,16 +1331,18 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + EXPORT_SYMBOL_NS(hda_data_stream_prepare, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair) + { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct hdac_stream *hstream = hdac_stream(hext_stream); + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret = 0; + +- if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) ++ if (!is_iccmax) + ret = hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); +- else ++ ++ if (hstream->direction == SNDRV_PCM_STREAM_CAPTURE) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_SD_CTL_DMA_START, 0); + +diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c +--- a/sound/soc/sof/intel/hda.c ++++ b/sound/soc/sof/intel/hda.c +@@ -421,6 +421,20 @@ static inline void hda_dsp_sdw_process_mic_privacy(struct snd_sof_dev *sdev) { } + /* pre fw run operations */ + int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { ++ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; ++ const struct sof_intel_dsp_desc *chip = hda->desc; ++ int ret; ++ ++ /* Power down DSP if left enabled to ensure a clean boot state. */ ++ if (hda_dsp_core_is_enabled(sdev, chip->host_managed_cores_mask)) { ++ dev_dbg(sdev->dev, "DSP core enabled, power down DSP first\n"); ++ ++ ret = chip->power_down_dsp(sdev); ++ if (ret < 0) ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ } ++ + /* disable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, false); + } +@@ -625,8 +639,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + return ret; + } + +- hda_bus_ml_init(bus); +- + /* Skip SoundWire if it is not supported */ + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + goto skip_soundwire; +@@ -670,8 +682,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + if (!HDA_IDISP_CODEC(bus->codec_mask)) + hda_codec_i915_display_power(sdev, false); + +- hda_bus_ml_put_all(bus); +- + return 0; + } + +diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h +--- a/sound/soc/sof/intel/hda.h ++++ b/sound/soc/sof/intel/hda.h +@@ -523,6 +523,29 @@ struct sof_intel_hda_dev { + /* the maximum number of streams (playback + capture) supported */ + u32 stream_max; + ++ /* ++ * ACE2+ link DMA stream allocation constraints (stream index = ++ * stream_tag - 1, shared between input and output directions). All ++ * masks are cleared by hda_dsp_ctrl_init_chip() on controller reset ++ * (CRST#). ++ * ++ * - Concurrent (cross-direction) constraint: a SoundWire stream and ++ * a HDA/iDisp/UAOL stream cannot share a physical stream index ++ * across directions, the resulting LLP/timestamp values are wrong. ++ * link_dma_active_sdw_mask and link_dma_active_multi_mask ++ * (indexed by SNDRV_PCM_STREAM_*) track currently allocated ++ * streams per direction in each of the conflicting groups; SSP ++ * and DMIC do not participate. Bits are cleared on stream release. ++ * ++ * - Sequential (playback only) constraint: once a HDA/iDisp link ++ * has used a playback stream index, that index cannot drive a ++ * non-HDA/iDisp link in the same direction until the next CRST#. ++ * link_dma_out_hda_used_mask records this. ++ */ ++ u32 link_dma_active_sdw_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_active_multi_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_out_hda_used_mask; ++ + /* PM related */ + bool l1_disabled;/* is DMI link L1 disabled? */ + +@@ -739,7 +762,7 @@ struct hdac_ext_stream *hda_cl_prepare(struct device *dev, unsigned int format, + int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int cmd); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax); + int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot); + #define HDA_CL_STREAM_FORMAT 0x40 + +@@ -911,7 +934,8 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + bool is_iccmax, bool pair); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair); + + /* common dai driver */ + extern struct snd_soc_dai_driver skl_dai[]; +@@ -1030,7 +1054,8 @@ struct hda_dai_widget_dma_ops { + struct snd_pcm_substream *substream); + struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream); ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink); + void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream); + void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream, +diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c +--- a/sound/soc/sof/intel/icl.c ++++ b/sound/soc/sof/intel/icl.c +@@ -9,10 +9,14 @@ + * Hardware interface for audio DSP on IceLake. + */ + +-#include +-#include +-#include ++#include + #include ++#include ++#include ++#include ++#include ++#include ++ + #include "../ipc4-priv.h" + #include "../ops.h" + #include "hda.h" +diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c +--- a/sound/soc/sof/intel/mtl.c ++++ b/sound/soc/sof/intel/mtl.c +@@ -236,6 +236,17 @@ int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) + } + EXPORT_SYMBOL_NS(mtl_enable_interrupts, "SND_SOC_SOF_INTEL_MTL"); + ++static bool mtl_dsp_is_enabled(struct snd_sof_dev *sdev) ++{ ++ int val; ++ ++ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS); ++ if (val & MTL_HFDSSCS_CPA_MASK) ++ return true; ++ ++ return false; ++} ++ + /* pre fw run operations */ + static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { +@@ -249,6 +260,18 @@ static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + u32 dsppwrsts; + const struct sof_intel_dsp_desc *chip; + ++ /* Power down the DSP if it is left enabled to ensure clean boot state */ ++ if (mtl_dsp_is_enabled(sdev)) { ++ dev_dbg(sdev->dev, "powering down DSP first\n"); ++ ++ ret = mtl_power_down_dsp(sdev); ++ if (ret < 0) { ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ /* Continue anyway to attempt recovery */ ++ } ++ } ++ + chip = get_chip_info(sdev->pdata); + if (chip->hw_ip_version > SOF_INTEL_ACE_2_0) { + dsppwrctl = PTL_HFPWRCTL2; +diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c +--- a/sound/soc/sof/ipc3-pcm.c ++++ b/sound/soc/sof/ipc3-pcm.c +@@ -421,6 +421,12 @@ static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + dev_dbg(component->dev, "AMD_SDW channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; ++ case SOF_DAI_AMD_I2S: ++ rate->min = private->dai_config->acp_i2s.fsync_rate; ++ rate->max = private->dai_config->acp_i2s.fsync_rate; ++ channels->min = private->dai_config->acp_i2s.tdm_slots; ++ channels->max = private->dai_config->acp_i2s.tdm_slots; ++ break; + default: + dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type); + break; +diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c +--- a/sound/soc/sof/ipc3-topology.c ++++ b/sound/soc/sof/ipc3-topology.c +@@ -281,7 +281,10 @@ static const struct sof_topology_token acpdmic_tokens[] = { + offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)}, + }; + +-/* ACPI2S */ ++/* ++ * ACPI2S tokens fill struct sof_ipc_dai_acp_params; SOF_DAI_AMD_I2S (ACPTDM ++ * on ACP7.B/7.F) reuses this tuple group rather than defining a parallel set. ++ */ + static const struct sof_topology_token acpi2s_tokens[] = { + {SOF_TKN_AMD_ACPI2S_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, fsync_rate)}, +@@ -289,6 +292,8 @@ static const struct sof_topology_token acpi2s_tokens[] = { + offsetof(struct sof_ipc_dai_acp_params, tdm_slots)}, + {SOF_TKN_AMD_ACPI2S_TDM_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, tdm_mode)}, ++ {SOF_TKN_AMD_ACPI2S_FORMAT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, ++ offsetof(struct sof_ipc_dai_acp_params, format)}, + }; + + /* MICFIL PDM */ +@@ -1363,6 +1368,35 @@ static int sof_link_acp_sdw_load(struct snd_soc_component *scomp, struct snd_sof + return 0; + } + ++static int sof_link_acp_i2s_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, ++ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) ++{ ++ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; ++ struct sof_dai_private_data *private = dai->private; ++ u32 size = sizeof(*config); ++ int ret; ++ ++ /* handle master/slave and inverted clocks */ ++ sof_dai_set_format(hw_config, config); ++ ++ /* init IPC */ ++ memset(&config->acp_i2s, 0, sizeof(config->acp_i2s)); ++ config->hdr.size = size; ++ ++ ret = sof_update_ipc_object(scomp, &config->acp_i2s, SOF_ACPI2S_TOKENS, slink->tuples, ++ slink->num_tuples, size, slink->num_hw_configs); ++ if (ret < 0) ++ return ret; ++ ++ dai->number_configs = 1; ++ dai->current_config = 0; ++ private->dai_config = kmemdup(config, size, GFP_KERNEL); ++ if (!private->dai_config) ++ return -ENOMEM; ++ ++ return 0; ++} ++ + static int sof_link_afe_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) + { +@@ -1692,6 +1726,9 @@ static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget) + case SOF_DAI_AMD_SDW: + ret = sof_link_acp_sdw_load(scomp, slink, config, dai); + break; ++ case SOF_DAI_AMD_I2S: ++ ret = sof_link_acp_i2s_load(scomp, slink, config, dai); ++ break; + default: + break; + } +diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c +--- a/sound/soc/sof/ipc4-topology.c ++++ b/sound/soc/sof/ipc4-topology.c +@@ -163,11 +163,11 @@ static const struct sof_topology_token comp_ext_tokens[] = { + {SOF_TKN_COMP_SCHED_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_domain, + offsetof(struct snd_sof_widget, comp_domain)}, + {SOF_TKN_COMP_DOMAIN_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_domain_id)}, ++ offsetof(struct snd_sof_widget, domain_id)}, + {SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_heap_bytes)}, ++ offsetof(struct snd_sof_widget, heap_bytes)}, + {SOF_TKN_COMP_STACK_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_stack_bytes)}, ++ offsetof(struct snd_sof_widget, stack_bytes)}, + }; + + static const struct sof_topology_token gain_tokens[] = { +@@ -406,6 +406,39 @@ sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index) + return NULL; + } + ++static void ++sof_ipc4_evaluate_params_change(struct sof_ipc4_available_audio_format *available_fmt) ++{ ++ struct sof_ipc4_audio_format *fmt; ++ u32 in_rate, in_channels, in_valid_bits; ++ u32 out_rate, out_channels, out_valid_bits; ++ u32 changed_params = 0; ++ int i, j; ++ ++ for (i = 0; i < available_fmt->num_input_formats; i++) { ++ fmt = &available_fmt->input_pin_fmts[i].audio_fmt; ++ in_rate = fmt->sampling_frequency; ++ in_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ in_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ for (j = 0; j < available_fmt->num_output_formats; j++) { ++ fmt = &available_fmt->output_pin_fmts[j].audio_fmt; ++ out_rate = fmt->sampling_frequency; ++ out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ if (in_rate != out_rate) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_RATE); ++ if (in_channels != out_channels) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_CHANNELS); ++ if (in_valid_bits != out_valid_bits) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ } ++ } ++ ++ available_fmt->changed_params = changed_params; ++} ++ + /** + * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples + * @scomp: pointer to pointer to SOC component +@@ -497,6 +530,8 @@ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, + available_fmt->num_output_formats); + } + ++ sof_ipc4_evaluate_params_change(available_fmt); ++ + return 0; + + err_out: +@@ -661,6 +696,9 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + /* + * This callback is used by host copier and module-to-module copier, + * and only host copier needs to set gtw_cfg. +@@ -789,6 +827,9 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + ret = sof_update_ipc_object(scomp, &node_type, + SOF_COPIER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(node_type), 1); +@@ -1371,6 +1412,22 @@ sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget * + pipeline = pipe_widget->private; + pipeline->mem_usage += total; + ++ /* ++ * If this is not a Data Processing module instance, add the ++ * required heap sizes to the sum of all module instances belonging ++ * to the same pipeline, and find the maximum stack requirement ++ * among all module instances belonging to the same pipeline. ++ */ ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { ++ pipe_widget->heap_bytes += swidget->heap_bytes; ++ if (pipe_widget->stack_bytes < swidget->stack_bytes) ++ pipe_widget->stack_bytes = swidget->stack_bytes; ++ ++ dev_dbg(sdev->dev, "%s mem reqs to %s heap %u stack %u", ++ swidget->widget->name, pipe_widget->widget->name, ++ pipe_widget->heap_bytes, pipe_widget->stack_bytes); ++ } ++ + /* Update base_config->cpc from the module manifest */ + sof_ipc4_update_cpc_from_manifest(sdev, fw_module, base_config); + +@@ -1688,6 +1745,8 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + pipeline->mem_usage = 0; ++ pipe_widget->heap_bytes = 0; ++ pipe_widget->stack_bytes = 0; + + if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) { + if (pipeline->use_chain_dma) { +@@ -2065,10 +2124,57 @@ static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget + } + + static int +-sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, +- struct snd_pcm_hw_params *fe_params, +- struct snd_sof_platform_stream_params *platform_params, +- struct snd_pcm_hw_params *pipeline_params, int dir) ++sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *pipeline_params) ++{ ++ struct snd_soc_component *scomp = swidget->scomp; ++ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); ++ struct sof_ipc4_copier_data *copier_data; ++ struct sof_ipc4_copier *ipc4_copier; ++ ++ switch (swidget->id) { ++ case snd_soc_dapm_aif_in: ++ case snd_soc_dapm_aif_out: ++ case snd_soc_dapm_buffer: ++ ipc4_copier = swidget->private; ++ copier_data = &ipc4_copier->data; ++ break; ++ case snd_soc_dapm_dai_in: ++ case snd_soc_dapm_dai_out: ++ { ++ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; ++ struct sof_ipc4_pipeline *pipeline = pipe_widget->private; ++ struct snd_sof_dai *dai; ++ ++ if (pipeline->use_chain_dma) ++ return 0; ++ ++ dai = swidget->private; ++ ++ ipc4_copier = (struct sof_ipc4_copier *)dai->private; ++ copier_data = &ipc4_copier->data; ++ ++ break; ++ } ++ default: ++ dev_err(sdev->dev, "unsupported type %d for copier %s", ++ swidget->id, swidget->widget->name); ++ return -EINVAL; ++ } ++ ++ /* modify the input params for the next widget */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &copier_data->out_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++} ++ ++static int ++_sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) + { + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_soc_component *scomp = swidget->scomp; +@@ -2540,6 +2646,21 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, + return 0; + } + ++static int ++sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) ++{ ++ if (swidget->prepared) ++ return sof_ipc4_copier_module_update_params(swidget, ++ pipeline_params); ++ ++ return _sof_ipc4_prepare_copier_module(swidget, fe_params, ++ platform_params, pipeline_params, ++ dir); ++} ++ + static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, +@@ -2553,6 +2674,10 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &gain->data.base_config, + pipeline_params, +@@ -2598,6 +2723,10 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* Already prepared, nothing to do */ ++ if (swidget->prepared) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &mixer->base_config, + pipeline_params, +@@ -2644,6 +2773,10 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int output_fmt_index, input_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &src->data.base_config, + pipeline_params, +@@ -2810,6 +2943,18 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + int ret; + + if (available_fmt->num_input_formats) { ++ if (swidget->prepared) { ++ if (!available_fmt->num_output_formats) ++ return 0; ++ ++ /* modify the pipeline params with the output format */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &process->output_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ } ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &process->base_config, + pipeline_params, +@@ -2822,39 +2967,69 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + if (available_fmt->num_output_formats) { + struct sof_ipc4_audio_format *in_fmt; + struct sof_ipc4_pin_format *pin_fmt; +- u32 out_ref_rate, out_ref_channels; +- int out_ref_valid_bits, out_ref_type; ++ u32 ref_rate, ref_channels; ++ int ref_valid_bits, ref_type; + + if (available_fmt->num_input_formats) { ++ /* ++ * The process module can change parameters and their operation ++ * depends on the direction: ++ * Playback: typically they have single output format. This is ++ * to 'force' the conversion from input to output. ++ * Use the input format as reference since the single ++ * format is going to be picked. ++ * Capture: typically they have multiple output formats to ++ * convert from dai (input) to FE (output) parameters. ++ * Use the input format as base and replace the param ++ * which is changed by the module with the FE parameter ++ * Reason: we can have module which changes the ++ * parameters in path, we cannot use the full ++ * FE param set for the module output lookup. ++ */ + in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; + +- out_ref_rate = in_fmt->sampling_frequency; +- out_ref_channels = ++ ref_rate = in_fmt->sampling_frequency; ++ ref_channels = + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); +- out_ref_valid_bits = ++ ref_valid_bits = + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); +- out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); ++ ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + } else { + /* for modules without input formats, use FE params as reference */ +- out_ref_rate = params_rate(fe_params); +- out_ref_channels = params_channels(fe_params); ++ ref_rate = params_rate(fe_params); ++ ref_channels = params_channels(fe_params); + ret = sof_ipc4_get_sample_type(sdev, fe_params); + if (ret < 0) + return ret; +- out_ref_type = (u32)ret; ++ ref_type = (u32)ret; + +- out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); +- if (out_ref_valid_bits < 0) +- return out_ref_valid_bits; ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; + } + ++ if (dir == SNDRV_PCM_STREAM_CAPTURE) { ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) ++ ref_rate = params_rate(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) ++ ref_channels = params_channels(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; ++ ++ ref_type = sof_ipc4_get_sample_type(sdev, fe_params); ++ if (ref_type < 0) ++ return ref_type; ++ } ++ } + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, + &process->base_config, + available_fmt, +- out_ref_rate, +- out_ref_channels, +- out_ref_valid_bits, +- out_ref_type); ++ ref_rate, ++ ref_channels, ++ ref_valid_bits, ++ ref_type); + if (output_fmt_index < 0) + return output_fmt_index; + +@@ -2868,9 +3043,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + /* modify the pipeline params with the output format */ + ret = sof_ipc4_update_hw_params(sdev, pipeline_params, + &process->output_format, +- BIT(SNDRV_PCM_HW_PARAM_FORMAT) | +- BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | +- BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ available_fmt->changed_params); + if (ret) + return ret; + } +@@ -3085,27 +3258,47 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr + return 0; + } + +-static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, +- struct snd_sof_widget *swidget, +- struct sof_ipc4_msg *msg, +- void *ipc_data, u32 ipc_size, +- void **new_data) ++static void sof_ipc4_add_init_ext_dp_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_module_init_ext_object **hdr) ++{ ++ /* Add memory_data if comp_domain indicates DP */ ++ if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { ++ struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; ++ ++ *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | ++ SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; ++ dp_mem_data->domain_id = swidget->domain_id; ++ dp_mem_data->stack_bytes = swidget->stack_bytes; ++ dp_mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ } ++} ++ ++static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void *ipc_data, u32 ipc_size, ++ void **new_data) + { +- struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; + struct sof_ipc4_module_init_ext_init *ext_init; +- struct sof_ipc4_module_init_ext_object *hdr; ++ struct sof_ipc4_module_init_ext_object *hdr = NULL; + int new_size; + u32 *payload; + u32 ext_pos; + +- /* For the moment the only reason for adding init_ext_init payload is DP +- * memory data. If both stack and heap size are 0 (= use default), then +- * there is no need for init_ext_init payload. ++ /* ++ * Only DP widgets currently add init-ext objects here. Avoid allocating ++ * a max-sized payload buffer for widgets that will immediately return 0. + */ +- if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { +- msg->extension &= ~SOF_IPC4_MOD_EXT_EXTENDED_INIT_MASK; ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) + return 0; +- } + + payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); + if (!payload) +@@ -3113,27 +3306,26 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + + /* Add ext_init first and set objects array flag to 1 */ + ext_init = (struct sof_ipc4_module_init_ext_init *)payload; +- ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; + ext_pos = DIV_ROUND_UP(sizeof(*ext_init), sizeof(u32)); + + /* Add object array objects after ext_init */ + +- /* Add dp_memory_data if comp_domain indicates DP */ +- if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { +- hdr = (struct sof_ipc4_module_init_ext_object *)&payload[ext_pos]; +- hdr->header = SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK | +- SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | +- SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), +- sizeof(u32))); +- ext_pos += DIV_ROUND_UP(sizeof(*hdr), sizeof(u32)); +- dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[ext_pos]; +- dp_mem_data->domain_id = swidget->dp_domain_id; +- dp_mem_data->stack_bytes = swidget->dp_stack_bytes; +- dp_mem_data->heap_bytes = swidget->dp_heap_bytes; +- ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ ++ /* Add following object array items here */ ++ ++ if (!hdr) { ++ /* ++ * NOTE: Remove this early bail out, when struct ++ * sof_ipc4_module_init_ext_init alone has some ++ * function. ++ */ ++ kfree(payload); ++ return 0; + } + +- /* If another array object is added, remember clear previous OBJ_LAST bit */ ++ ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; ++ hdr->header |= SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK; + + /* Calculate final size and check that it fits to max payload size */ + new_size = ext_pos * sizeof(u32) + ipc_size; +@@ -3156,6 +3348,69 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + return new_size; + } + ++static void sof_ipc4_widget_pipe_ext_obj_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_glb_pipe_ext_object **hdr) ++{ ++ struct sof_ipc4_glb_pipe_ext_obj_memory_data *mem_data; ++ ++ *hdr = (struct sof_ipc4_glb_pipe_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA) | ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ mem_data = (struct sof_ipc4_glb_pipe_ext_obj_memory_data *)&payload[*ext_pos]; ++ mem_data->domain_id = swidget->domain_id; ++ mem_data->stack_bytes = swidget->stack_bytes; ++ mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*mem_data), sizeof(u32)); ++ ++ dev_dbg(sdev->dev, ++ "%s; domain_id %u stack %u heap %u bytes", ++ swidget->widget->name, mem_data->domain_id, mem_data->stack_bytes, ++ mem_data->heap_bytes); ++} ++ ++static int sof_ipc4_widget_pipe_create_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void **new_data) ++{ ++ struct sof_ipc4_glb_pipe_payload *payload_hdr; ++ struct sof_ipc4_glb_pipe_ext_object *hdr = NULL; ++ u32 *payload; ++ u32 ext_pos; ++ ++ payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); ++ if (!payload) ++ return -ENOMEM; ++ ++ /* Add sof_ipc4_glb_pipe_payload and set array bit to 1 */ ++ payload_hdr = (struct sof_ipc4_glb_pipe_payload *)payload; ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK; ++ ext_pos = DIV_ROUND_UP(sizeof(*payload_hdr), sizeof(u32)); ++ ++ sof_ipc4_widget_pipe_ext_obj_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ /* Add following array objects here */ ++ ++ /* Mark end of object array */ ++ hdr->header |= SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK; ++ ++ /* Put total payload size in words to the payload header */ ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(ext_pos); ++ *new_data = payload; ++ ++ /* Update msg extension bits according to the payload changes */ ++ msg->extension |= SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ++ dev_dbg(sdev->dev, "%s: payload word0 %#x", swidget->widget->name, ++ payload_hdr->word0); ++ ++ return ext_pos * sizeof(int32_t); ++} ++ + static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) + { + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; +@@ -3342,8 +3597,8 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name, swidget->pipeline_id, module_id, + swidget->instance_id, swidget->core); + +- ret = sof_ipc4_widget_setup_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, +- &ext_data); ++ ret = sof_ipc4_widget_mod_init_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, ++ &ext_data); + if (ret < 0) + goto fail; + +@@ -3355,6 +3610,17 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + dev_dbg(sdev->dev, "Create pipeline %s (pipe %d) - instance %d, core %d\n", + swidget->widget->name, swidget->pipeline_id, + swidget->instance_id, swidget->core); ++ ++ msg->extension &= ~SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ret = sof_ipc4_widget_pipe_create_msg_payload(sdev, swidget, msg, ++ &ext_data); ++ if (ret < 0) ++ goto fail; ++ ++ if (ret > 0) { ++ ipc_size = ret; ++ ipc_data = ext_data; ++ } + } + + msg->data_size = ipc_size; +@@ -3412,6 +3678,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name); + + pipeline->mem_usage = 0; ++ swidget->heap_bytes = 0; ++ swidget->stack_bytes = 0; + pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; + ida_free(&pipeline_ida, swidget->instance_id); + swidget->instance_id = -EINVAL; +diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h +--- a/sound/soc/sof/ipc4-topology.h ++++ b/sound/soc/sof/ipc4-topology.h +@@ -167,7 +167,7 @@ struct sof_ipc4_pipeline { + }; + + /** +- * struct sof_ipc4_multi_pipeline_data - multi pipeline trigger IPC data ++ * struct ipc4_pipeline_set_state_data - multi pipeline trigger IPC data + * @count: Number of pipelines to be triggered + * @pipeline_instance_ids: Flexible array of IDs of the pipelines to be triggered + */ +@@ -197,12 +197,15 @@ struct sof_ipc4_pin_format { + * @input_pin_fmts: Available input pin formats + * @num_input_formats: Number of input pin formats + * @num_output_formats: Number of output pin formats ++ * @changed_params: Mask of changed params by the module instance between it's ++ * input and output formts (rate, channels, depth) + */ + struct sof_ipc4_available_audio_format { + struct sof_ipc4_pin_format *output_pin_fmts; + struct sof_ipc4_pin_format *input_pin_fmts; + u32 num_input_formats; + u32 num_output_formats; ++ u32 changed_params; + }; + + /** +@@ -210,7 +213,7 @@ struct sof_ipc4_available_audio_format { + * @node_id: ID of Gateway Node + * @dma_buffer_size: Preferred Gateway DMA buffer size (in bytes) + * @config_length: Length of gateway node configuration blob specified in #config_data +- * config_data: Gateway node configuration blob ++ * @config_data: Gateway node configuration blob + */ + struct sof_copier_gateway_cfg { + uint32_t node_id; +@@ -285,7 +288,9 @@ struct sof_ipc4_dma_stream_ch_map { + struct sof_ipc4_dma_config { + uint8_t dma_method; + uint8_t pre_allocated_by_host; ++ /* private: */ + uint16_t rsvd; ++ /* public: */ + uint32_t dma_channel_id; + uint32_t stream_id; + struct sof_ipc4_dma_stream_ch_map dma_stream_channel_map; +@@ -296,7 +301,7 @@ struct sof_ipc4_dma_config { + #define SOF_IPC4_GTW_DMA_CONFIG_ID 0x1000 + + /** +- * struct sof_ipc4_dma_config: DMA configuration ++ * struct sof_ipc4_dma_config_tlv - DMA configuration + * @type: set to SOF_IPC4_GTW_DMA_CONFIG_ID + * @length: sizeof(struct sof_ipc4_dma_config) + dma_config.dma_priv_config_size + * @dma_config: actual DMA configuration +@@ -321,6 +326,7 @@ struct sof_ipc4_alh_configuration_blob { + * @data: IPC copier data + * @copier_config: Copier + blob + * @ipc_config_size: Size of copier_config ++ * @ipc_config_data: Copier module config data + * @available_fmt: Available audio format + * @frame_fmt: frame format + * @msg: message structure for copier +diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c +--- a/sound/soc/sof/ipc4.c ++++ b/sound/soc/sof/ipc4.c +@@ -289,6 +289,89 @@ static void sof_ipc4_dump_payload(struct snd_sof_dev *sdev, + 16, 4, ipc_data, size, false); + } + ++static const char *sof_ipc4_resource_type_str(u32 type) ++{ ++ switch (type) { ++ case SOF_IPC4_MODULE_INSTANCE: ++ return "resource: MODULE_INSTANCE"; ++ case SOF_IPC4_PIPELINE: ++ return "resource: PIPELINE"; ++ case SOF_IPC4_GATEWAY: ++ return "resource: GATEWAY"; ++ case SOF_IPC4_EDF_TASK: ++ return "resource: EDF_TASK"; ++ case SOF_IPC4_INVALID_RESOURCE_TYPE: ++ return "Resource is invalid"; ++ default: ++ return "Unknown resource type"; ++ } ++} ++ ++static const char *sof_ipc4_resource_event_type_str(u32 event_type) ++{ ++ switch (event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ return "event: MIXER_UNDERRUN_DETECTED"; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ return "event: PROCESS_DATA_ERROR"; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ return "event: GATEWAY_UNDERRUN_DETECTED"; ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ return "event: GATEWAY_OVERRUN_DETECTED"; ++ default: ++ return "Unknown event type"; ++ } ++} ++ ++static void sof_ipc4_resource_event_handler(struct snd_sof_dev *sdev, ++ struct sof_ipc4_msg *ipc4_msg) ++{ ++ struct sof_ipc4_notify_resource_data *data = ipc4_msg->data_ptr; ++ ++ /* Print event details */ ++ switch (data->event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u): eos %u, mixed %u, expected %u\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.mixer_underrun.eos_flag, ++ data->data.mixer_underrun.data_mixed, ++ data->data.mixer_underrun.expected_data_mixed); ++ break; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ dev_dbg(sdev->dev, "%s (%u): error_code %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.process_data_error.error_code); ++ break; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u)\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type); ++ break; ++ default: ++ dev_dbg(sdev->dev, "%s (%u): raw dws %#x %#x %#x %#x %#x %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, ++ data->data.dws[0], data->data.dws[1], data->data.dws[2], ++ data->data.dws[3], data->data.dws[4], data->data.dws[5]); ++ break; ++ } ++ ++ /* Print resource details */ ++ if (data->resource_type == SOF_IPC4_MODULE_INSTANCE) { ++ u32 module_id = SOF_IPC4_MOD_ID_GET(data->resource_id); ++ u32 instance_id = SOF_IPC4_MOD_INSTANCE_GET(data->resource_id); ++ ++ dev_dbg(sdev->dev, "%s (%u), module_id %u, instance_id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, module_id, instance_id); ++ } else if (data->resource_type != SOF_IPC4_INVALID_RESOURCE_TYPE) { ++ dev_dbg(sdev->dev, "%s (%u), id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, data->resource_id); ++ } ++} ++ + static int sof_ipc4_get_reply(struct snd_sof_dev *sdev) + { + struct snd_sof_ipc_msg *msg = sdev->msg; +@@ -734,6 +817,7 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev) + break; + case SOF_IPC4_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct sof_ipc4_notify_resource_data); ++ handler_func = sof_ipc4_resource_event_handler; + break; + case SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS: + sof_ipc4_mtrace_update_pos(sdev, SOF_IPC4_LOG_CORE_GET(ipc4_msg->primary)); +diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c +--- a/sound/soc/sof/nocodec.c ++++ b/sound/soc/sof/nocodec.c +@@ -21,12 +21,13 @@ static struct snd_soc_card sof_nocodec_card = { + static int sof_nocodec_bes_setup(struct device *dev, + struct snd_soc_dai_driver *drv, + struct snd_soc_dai_link *links, +- int link_num, struct snd_soc_card *card) ++ int link_num) + { ++ struct snd_soc_card *card = &sof_nocodec_card; + struct snd_soc_dai_link_component *dlc; + int i; + +- if (!drv || !links || !card) ++ if (!drv || !links) + return -EINVAL; + + /* set up BE dai_links */ +@@ -67,10 +68,10 @@ static int sof_nocodec_bes_setup(struct device *dev, + return 0; + } + +-static int sof_nocodec_setup(struct device *dev, +- u32 num_dai_drivers, +- struct snd_soc_dai_driver *dai_drivers) ++static int sof_nocodec_setup(struct device *dev, struct snd_soc_acpi_mach *mach) + { ++ u32 num_dai_drivers = mach->mach_params.num_dai_drivers; ++ struct snd_soc_dai_driver *dai_drivers = mach->mach_params.dai_drivers; + struct snd_soc_dai_link *links; + + /* create dummy BE dai_links */ +@@ -78,7 +79,7 @@ static int sof_nocodec_setup(struct device *dev, + if (!links) + return -ENOMEM; + +- return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers, &sof_nocodec_card); ++ return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers); + } + + static int sof_nocodec_probe(struct platform_device *pdev) +@@ -92,8 +93,7 @@ static int sof_nocodec_probe(struct platform_device *pdev) + + snd_soc_card_set_topology_name(card, "sof"); + +- ret = sof_nocodec_setup(card->dev, mach->mach_params.num_dai_drivers, +- mach->mach_params.dai_drivers); ++ ret = sof_nocodec_setup(card->dev, mach); + if (ret < 0) + return ret; + +diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c +--- a/sound/soc/sof/pcm.c ++++ b/sound/soc/sof/pcm.c +@@ -62,7 +62,7 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) + * To avoid sending IPC before the previous IPC is handled, we + * schedule delayed work here to call the snd_pcm_period_elapsed(). + */ +- schedule_work(&spcm->stream[substream->stream].period_elapsed_work); ++ queue_work(system_highpri_wq, &spcm->stream[substream->stream].period_elapsed_work); + } + EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); + +diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c +--- a/sound/soc/sof/sof-audio.c ++++ b/sound/soc/sof/sof-audio.c +@@ -501,9 +501,8 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget + !sof_widget_in_same_direction(swidget, dir)) + return 0; + +- /* skip widgets already prepared or aggregated DAI widgets*/ +- if (!widget_ops[widget->id].ipc_prepare || swidget->prepared || +- is_aggregated_dai(swidget)) ++ /* skip widgets aggregated DAI widgets */ ++ if (!widget_ops[widget->id].ipc_prepare || is_aggregated_dai(swidget)) + goto sink_prepare; + + /* prepare the source widget */ +diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h +--- a/sound/soc/sof/sof-audio.h ++++ b/sound/soc/sof/sof-audio.h +@@ -459,10 +459,10 @@ struct snd_sof_widget { + /* Scheduling domain (enum sof_comp_domain), unset, Low Latency, or Data Processing */ + u32 comp_domain; + +- /* The values below are added to mod_init pay load if comp_domain indicates DP component */ +- u32 dp_domain_id; /* DP process userspace domain ID */ +- u32 dp_stack_bytes; /* DP process stack size requirement in bytes */ +- u32 dp_heap_bytes; /* DP process heap size requirement in bytes */ ++ /* Module instance's memory configuration. */ ++ u32 domain_id; /* Module instance's userspace domain ID */ ++ u32 stack_bytes; /* Module instance's stack size requirement */ ++ u32 heap_bytes; /* Module instance's heap size requirement */ + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ +diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c +--- a/sound/soc/sof/topology.c ++++ b/sound/soc/sof/topology.c +@@ -309,7 +309,7 @@ static const struct sof_dai_types sof_dais[] = { + {"ACPHS_VIRTUAL", SOF_DAI_AMD_HS_VIRTUAL}, + {"MICFIL", SOF_DAI_IMX_MICFIL}, + {"ACP_SDW", SOF_DAI_AMD_SDW}, +- ++ {"ACPTDM", SOF_DAI_AMD_I2S}, + }; + + static enum sof_ipc_dai_type find_dai(const char *name) +@@ -1108,7 +1108,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, + + full = NULL; + partial = NULL; +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (rtd->dai_link->stream_name) { + if (!strcmp(rtd->dai_link->stream_name, w->sname)) { +@@ -1173,7 +1173,7 @@ static void sof_disconnect_dai_widget(struct snd_soc_component *scomp, + else + return; + +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (!rtd->dai_link->stream_name || + !strstr(rtd->dai_link->stream_name, sname)) +@@ -1949,8 +1949,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + private->array, le32_to_cpu(private->size)); + if (ret < 0) { + dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n"); +- kfree(slink); +- return ret; ++ goto free_slink; + } + + token_list = tplg_ops ? tplg_ops->token_list : NULL; +@@ -2001,6 +2000,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_HS_VIRTUAL: ++ case SOF_DAI_AMD_I2S: + token_id = SOF_ACPI2S_TOKENS; + num_tuples += token_list[SOF_ACPI2S_TOKENS].count; + break; +@@ -2019,8 +2019,8 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + /* allocate memory for tuples array */ + slink->tuples = kzalloc_objs(*slink->tuples, num_tuples); + if (!slink->tuples) { +- kfree(slink); +- return -ENOMEM; ++ ret = -ENOMEM; ++ goto free_slink; + } + + if (token_list[SOF_DAI_LINK_TOKENS].tokens) { +@@ -2076,6 +2076,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + + err: + kfree(slink->tuples); ++free_slink: + kfree(slink); + + return ret; +@@ -2511,13 +2512,12 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const char *tplg_filename_prefix = sof_pdata->tplg_filename_prefix; +- const struct firmware *fw; +- const char **tplg_files; + int tplg_cnt = 0; + int ret; + int i; + +- tplg_files = kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); ++ const char **tplg_files __free(kfree) = ++ kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + if (!tplg_files) + return -ENOMEM; + +@@ -2543,10 +2543,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + tplg_filename_prefix, + &tplg_files, + no_fallback); +- if (tplg_cnt < 0) { +- kfree(tplg_files); ++ if (tplg_cnt < 0) + return tplg_cnt; +- } + } + + /* +@@ -2557,8 +2555,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (strstr(file, "dummy")) { + dev_err(scomp->dev, + "Function topology is required, please upgrade sof-firmware\n"); +- +- kfree(tplg_files); + return -EINVAL; + } + tplg_files[0] = file; +@@ -2573,6 +2569,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (tplg_files[0] != file) + dev_info(scomp->dev, "loading topology %d: %s\n", i, tplg_files[i]); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_files[i], scomp->dev); + if (ret < 0) { + /* +@@ -2591,8 +2588,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "tplg %s component load failed %d\n", + tplg_files[i], ret); +@@ -2611,6 +2606,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + goto out; + } + dev_info(scomp->dev, "loading feature topology %d: %s\n", i, feature_topology); ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, feature_topology, scomp->dev); + if (ret < 0) { + /* +@@ -2635,8 +2632,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "feature tplg %s component load failed %d\n", + feature_topologies[i], ret); +@@ -2655,8 +2650,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (ret >= 0 && sdev->led_present) + ret = snd_ctl_led_request(); + +- kfree(tplg_files); +- + return ret; + } + EXPORT_SYMBOL(snd_sof_load_topology); +diff --git a/sound/soc/sophgo/cv1800b-tdm.c b/sound/soc/sophgo/cv1800b-tdm.c +--- a/sound/soc/sophgo/cv1800b-tdm.c ++++ b/sound/soc/sophgo/cv1800b-tdm.c +@@ -558,7 +558,7 @@ static const struct snd_soc_dai_ops cv1800b_i2s_dai_ops = { + .set_sysclk = cv1800b_i2s_dai_set_sysclk, + }; + +-static const struct snd_soc_dai_driver cv1800b_i2s_dai_template = { ++static struct snd_soc_dai_driver cv1800b_i2s_dai_template = { + .name = "cv1800b-i2s", + .playback = { + .stream_name = "Playback", +@@ -636,7 +636,6 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + struct cv1800b_i2s *i2s; + struct resource *res; + void __iomem *regs; +- struct snd_soc_dai_driver *dai; + int ret; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); +@@ -666,21 +665,14 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, i2s); + cv1800b_i2s_setup_tdm(i2s); + +- dai = devm_kmemdup(dev, &cv1800b_i2s_dai_template, sizeof(*dai), +- GFP_KERNEL); +- if (!dai) +- return -ENOMEM; +- +- ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, dai, +- 1); ++ ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, ++ &cv1800b_i2s_dai_template, 1); + if (ret) + return ret; + + ret = devm_snd_dmaengine_pcm_register(dev, &cv1800b_i2s_pcm_config, 0); +- if (ret) { +- dev_err(dev, "dmaengine_pcm_register failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c +--- a/sound/soc/spacemit/k1_i2s.c ++++ b/sound/soc/spacemit/k1_i2s.c +@@ -335,6 +335,11 @@ static int spacemit_i2s_dai_remove(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 spacemit_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .probe = spacemit_i2s_dai_probe, + .remove = spacemit_i2s_dai_remove, +@@ -343,6 +348,8 @@ static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .set_sysclk = spacemit_i2s_set_sysclk, + .set_fmt = spacemit_i2s_set_fmt, + .trigger = spacemit_i2s_trigger, ++ .auto_selectable_formats = &spacemit_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver spacemit_i2s_dai = { +@@ -459,7 +466,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + + i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->base)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->base), "failed to map registers\n"); ++ return PTR_ERR(i2s->base); + + i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s->reset)) +@@ -476,7 +483,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + &spacemit_i2s_component, + dai, 1); + if (ret) +- return dev_err_probe(i2s->dev, ret, "failed to register component"); ++ return ret; + + return devm_snd_dmaengine_pcm_register(&pdev->dev, &spacemit_dmaengine_pcm_config, 0); + } +diff --git a/sound/soc/spear/spdif_in.c b/sound/soc/spear/spdif_in.c +--- a/sound/soc/spear/spdif_in.c ++++ b/sound/soc/spear/spdif_in.c +@@ -55,7 +55,7 @@ static int spdif_in_dai_probe(struct snd_soc_dai *dai) + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + + host->dma_params_rx.filter_data = &host->dma_params; +- dai->capture_dma_data = &host->dma_params_rx; ++ snd_soc_dai_dma_data_set_capture(dai, &host->dma_params_rx); + + return 0; + } +@@ -150,12 +150,12 @@ static int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + + static const struct snd_soc_dai_ops spdif_in_dai_ops = { + .shutdown = spdif_in_shutdown, ++ .probe = spdif_in_dai_probe, + .trigger = spdif_in_trigger, + .hw_params = spdif_in_hw_params, + }; + + static struct snd_soc_dai_driver spdif_in_dai = { +- .probe = spdif_in_dai_probe, + .capture = { + .channels_min = 2, + .channels_max = 2, +@@ -218,10 +218,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + host->io_base = io_base; + host->irq = platform_get_irq(pdev, 0); +- if (host->irq < 0) { +- dev_warn(&pdev->dev, "failed to get IRQ: %d\n", host->irq); ++ if (host->irq < 0) + return host->irq; +- } + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) +@@ -243,10 +241,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0, + "spdif-in", host); +- if (ret) { +- dev_warn(&pdev->dev, "request_irq failed\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component, + &spdif_in_dai, 1); +diff --git a/sound/soc/spear/spdif_out.c b/sound/soc/spear/spdif_out.c +--- a/sound/soc/spear/spdif_out.c ++++ b/sound/soc/spear/spdif_out.c +@@ -249,6 +249,7 @@ static int spdif_soc_dai_probe(struct snd_soc_dai *dai) + } + + static const struct snd_soc_dai_ops spdif_out_dai_ops = { ++ .probe = spdif_soc_dai_probe, + .mute_stream = spdif_mute, + .startup = spdif_out_startup, + .shutdown = spdif_out_shutdown, +@@ -266,7 +267,6 @@ static struct snd_soc_dai_driver spdif_out_dai = { + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +- .probe = spdif_soc_dai_probe, + .ops = &spdif_out_dai_ops, + }; + +diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c +--- a/sound/soc/sprd/sprd-mcdt.c ++++ b/sound/soc/sprd/sprd-mcdt.c +@@ -931,10 +931,8 @@ static int sprd_mcdt_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler, + 0, "sprd-mcdt", mcdt); +- if (ret) { +- dev_err(&pdev->dev, "Failed to request MCDT IRQ\n"); ++ if (ret) + return ret; +- } + + sprd_mcdt_init_chans(mcdt, res); + +diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c +--- a/sound/soc/sprd/sprd-pcm-dma.c ++++ b/sound/soc/sprd/sprd-pcm-dma.c +@@ -469,8 +469,6 @@ static int sprd_soc_platform_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &sprd_soc_component, + NULL, 0); +- if (ret) +- dev_err(&pdev->dev, "could not register platform:%d\n", ret); + + return ret; + } +diff --git a/sound/soc/starfive/jh7110_pwmdac.c b/sound/soc/starfive/jh7110_pwmdac.c +--- a/sound/soc/starfive/jh7110_pwmdac.c ++++ b/sound/soc/starfive/jh7110_pwmdac.c +@@ -477,25 +477,22 @@ static int jh7110_pwmdac_probe(struct platform_device *pdev) + &jh7110_pwmdac_component, + &jh7110_pwmdac_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register dai\n"); ++ return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n"); ++ return ret; + + pm_runtime_enable(dev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_pwmdac_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_pwmdac_remove(struct platform_device *pdev) +diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c +--- a/sound/soc/starfive/jh7110_tdm.c ++++ b/sound/soc/starfive/jh7110_tdm.c +@@ -559,10 +559,8 @@ static int jh7110_tdm_clk_reset_get(struct platform_device *pdev, + tdm->clks[5].id = "tdm"; + + ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(tdm->clks), tdm->clks); +- if (ret) { +- dev_err(&pdev->dev, "Failed to get tdm clocks\n"); ++ if (ret) + return ret; +- } + + tdm->resets = devm_reset_control_array_get_exclusive(&pdev->dev); + if (IS_ERR(tdm->resets)) { +@@ -589,42 +587,33 @@ static int jh7110_tdm_probe(struct platform_device *pdev) + tdm->dev = &pdev->dev; + + ret = jh7110_tdm_clk_reset_get(pdev, tdm); +- if (ret) { +- dev_err(&pdev->dev, "Failed to enable audio-tdm clock\n"); ++ if (ret) + return ret; +- } + + jh7110_tdm_init_params(tdm); + + dev_set_drvdata(&pdev->dev, tdm); + ret = devm_snd_soc_register_component(&pdev->dev, &jh7110_tdm_component, + &jh7110_tdm_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register dai\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &jh7110_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +- if (ret) { +- dev_err(&pdev->dev, "Could not register pcm: %d\n", ret); ++ if (ret) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_tdm_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_tdm_dev_remove(struct platform_device *pdev) +diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c +--- a/sound/soc/stm/stm32_adfsdm.c ++++ b/sound/soc/stm/stm32_adfsdm.c +@@ -360,11 +360,8 @@ static int stm32_adfsdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_soc_platform, + NULL, 0); +- if (ret < 0) { +- dev_err(&pdev->dev, "%s: Failed to register PCM platform\n", +- __func__); ++ if (ret < 0) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + +diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c +--- a/sound/soc/stm/stm32_i2s.c ++++ b/sound/soc/stm/stm32_i2s.c +@@ -1238,10 +1238,8 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, + dev_name(&pdev->dev), i2s); +- if (ret) { +- dev_err(&pdev->dev, "irq request returned %d\n", ret); ++ if (ret) + return ret; +- } + + /* Reset */ + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); +@@ -1295,7 +1293,7 @@ static int stm32_i2s_probe(struct platform_device *pdev) + + ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, &stm32_i2s_component, + i2s->dai_drv, 1); +diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c +--- a/sound/soc/stm/stm32_sai_sub.c ++++ b/sound/soc/stm/stm32_sai_sub.c +@@ -1696,19 +1696,15 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, + IRQF_SHARED, dev_name(&pdev->dev), sai); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + goto err_unprepare_pclk; +- } + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + conf = &stm32_sai_pcm_config_spdif; + + ret = snd_dmaengine_pcm_register(&pdev->dev, conf, 0); +- if (ret) { +- ret = dev_err_probe(&pdev->dev, ret, "Could not register pcm dma\n"); ++ if (ret) + goto err_unprepare_pclk; +- } + + ret = snd_soc_register_component(&pdev->dev, &stm32_component, + &sai->cpu_dai_drv, 1); +diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c +--- a/sound/soc/stm/stm32_spdifrx.c ++++ b/sound/soc/stm/stm32_spdifrx.c +@@ -970,10 +970,8 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, spdifrx->irq, stm32_spdifrx_isr, 0, + dev_name(&pdev->dev), spdifrx); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + return ret; +- } + + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (IS_ERR(rst)) +@@ -987,7 +985,7 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + pcm_config = &stm32_spdifrx_pcm_config; + ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, + &stm32_spdifrx_component, +diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c +--- a/sound/soc/sunxi/sun4i-codec.c ++++ b/sound/soc/sunxi/sun4i-codec.c +@@ -22,13 +22,13 @@ + #include + + #include ++#include ++#include + #include + #include + #include + #include + #include +-#include +-#include + + /* Codec DAC digital controls and FIFO registers */ + #define SUN4I_CODEC_DAC_DPC (0x00) +@@ -2317,67 +2317,54 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + quirks->regmap_config); +- if (IS_ERR(scodec->regmap)) { +- dev_err(&pdev->dev, "Failed to create our regmap\n"); +- return PTR_ERR(scodec->regmap); +- } ++ if (IS_ERR(scodec->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->regmap), ++ "Failed to create our regmap\n"); + + /* Get the clocks from the DT */ + scodec->clk_apb = devm_clk_get_enabled(&pdev->dev, "apb"); +- if (IS_ERR(scodec->clk_apb)) { +- dev_err(&pdev->dev, "Failed to get the APB clock\n"); +- return PTR_ERR(scodec->clk_apb); +- } ++ if (IS_ERR(scodec->clk_apb)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_apb), ++ "Failed to get the APB clock\n"); + + scodec->clk_module = devm_clk_get(&pdev->dev, "codec"); +- if (IS_ERR(scodec->clk_module)) { +- dev_err(&pdev->dev, "Failed to get the module clock\n"); +- return PTR_ERR(scodec->clk_module); +- } ++ if (IS_ERR(scodec->clk_module)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_module), ++ "Failed to get the module clock\n"); + + if (quirks->has_reset) { + scodec->rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); +- if (IS_ERR(scodec->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(scodec->rst); +- } ++ if (IS_ERR(scodec->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->rst), ++ "Failed to get reset control\n"); + } + + scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", + GPIOD_OUT_LOW); +- if (IS_ERR(scodec->gpio_pa)) { +- ret = PTR_ERR(scodec->gpio_pa); +- dev_err_probe(&pdev->dev, ret, "Failed to get pa gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_pa)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_pa), ++ "Failed to get pa gpio\n"); ++ + + scodec->gpio_hp = devm_gpiod_get_optional(&pdev->dev, "hp-det", GPIOD_IN); +- if (IS_ERR(scodec->gpio_hp)) { +- ret = PTR_ERR(scodec->gpio_hp); +- dev_err_probe(&pdev->dev, ret, "Failed to get hp-det gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_hp)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_hp), ++ "Failed to get hp-det gpio\n"); + + /* reg_field setup */ + scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_adc_fifoc); +- if (IS_ERR(scodec->reg_adc_fifoc)) { +- ret = PTR_ERR(scodec->reg_adc_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_adc_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_adc_fifoc), ++ "Failed to create regmap fields\n"); + + scodec->reg_dac_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_dac_fifoc); +- if (IS_ERR(scodec->reg_dac_fifoc)) { +- ret = PTR_ERR(scodec->reg_dac_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_dac_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_dac_fifoc), ++ "Failed to create regmap fields\n"); + + /* DMA configuration for TX FIFO */ + scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; +@@ -2394,39 +2381,29 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec, + &sun4i_codec_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our codec\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_codec_component, + &dummy_cpu_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); ++ if (ret) + return ret; +- } + + card = quirks->create_card(&pdev->dev); +- if (IS_ERR(card)) { +- ret = PTR_ERR(card); +- dev_err(&pdev->dev, "Failed to create our card\n"); +- return ret; +- } ++ if (IS_ERR(card)) ++ return PTR_ERR(card); + + snd_soc_card_set_drvdata(card, scodec); + + ret = snd_soc_register_card(card); +- if (ret) { +- dev_err_probe(&pdev->dev, ret, "Failed to register our card\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Failed to register our card\n"); + + return 0; + } +diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c +--- a/sound/soc/sunxi/sun4i-i2s.c ++++ b/sound/soc/sunxi/sun4i-i2s.c +@@ -1550,30 +1550,26 @@ static int sun4i_i2s_probe(struct platform_device *pdev) + } + + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(i2s->bus_clk)) { +- dev_err(&pdev->dev, "Can't get our bus clock\n"); +- return PTR_ERR(i2s->bus_clk); +- } ++ if (IS_ERR(i2s->bus_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->bus_clk), ++ "Can't get our bus clock\n"); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + i2s->variant->sun4i_i2s_regmap); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "Regmap initialisation failed\n"); +- return PTR_ERR(i2s->regmap); +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), ++ "Regmap initialisation failed\n"); + + i2s->mod_clk = devm_clk_get(&pdev->dev, "mod"); +- if (IS_ERR(i2s->mod_clk)) { +- dev_err(&pdev->dev, "Can't get our mod clock\n"); +- return PTR_ERR(i2s->mod_clk); +- } ++ if (IS_ERR(i2s->mod_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mod_clk), ++ "Can't get our mod clock\n"); + + if (i2s->variant->has_reset) { + i2s->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); +- if (IS_ERR(i2s->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(i2s->rst); +- } ++ if (IS_ERR(i2s->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->rst), ++ "Failed to get reset control\n"); + } + + if (!IS_ERR(i2s->rst)) { +diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c +--- a/sound/soc/sunxi/sun4i-spdif.c ++++ b/sound/soc/sunxi/sun4i-spdif.c +@@ -684,26 +684,23 @@ static int sun4i_spdif_probe(struct platform_device *pdev) + + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_spdif_regmap_config); +- if (IS_ERR(host->regmap)) { +- dev_err(&pdev->dev, "failed to initialise regmap.\n"); +- return PTR_ERR(host->regmap); +- } ++ if (IS_ERR(host->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->regmap), ++ "failed to initialise regmap.\n"); + + /* Clocks */ + host->apb_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(host->apb_clk)) { +- dev_err(&pdev->dev, "failed to get a apb clock.\n"); +- return PTR_ERR(host->apb_clk); +- } ++ if (IS_ERR(host->apb_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->apb_clk), ++ "failed to get a apb clock.\n"); + + if (quirks->tx_clk_name) + tx_clk_name = quirks->tx_clk_name; + host->spdif_clk = devm_clk_get(&pdev->dev, tx_clk_name); +- if (IS_ERR(host->spdif_clk)) { +- dev_err(&pdev->dev, "failed to get the \"%s\" clock.\n", +- tx_clk_name); +- return PTR_ERR(host->spdif_clk); +- } ++ if (IS_ERR(host->spdif_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->spdif_clk), ++ "failed to get the \"%s\" clock.\n", ++ tx_clk_name); + + host->dma_params_tx.addr = res->start + quirks->reg_dac_txdata; + host->dma_params_tx.maxburst = 8; +diff --git a/sound/soc/sunxi/sun50i-codec-analog.c b/sound/soc/sunxi/sun50i-codec-analog.c +--- a/sound/soc/sunxi/sun50i-codec-analog.c ++++ b/sound/soc/sunxi/sun50i-codec-analog.c +@@ -551,16 +551,13 @@ static int sun50i_codec_analog_probe(struct platform_device *pdev) + bool enable; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + enable = device_property_read_bool(&pdev->dev, + "allwinner,internal-bias-resistor"); +diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c +--- a/sound/soc/sunxi/sun8i-codec-analog.c ++++ b/sound/soc/sunxi/sun8i-codec-analog.c +@@ -822,16 +822,13 @@ static int sun8i_codec_analog_probe(struct platform_device *pdev) + void __iomem *base; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + return devm_snd_soc_register_component(&pdev->dev, + &sun8i_codec_analog_cmpnt_drv, +diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c +--- a/sound/soc/tegra/tegra20_das.c ++++ b/sound/soc/tegra/tegra20_das.c +@@ -167,10 +167,9 @@ static int tegra20_das_probe(struct platform_device *pdev) + + das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_das_regmap_config); +- if (IS_ERR(das->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- return PTR_ERR(das->regmap); +- } ++ if (IS_ERR(das->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(das->regmap), ++ "regmap init failed\n"); + + tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_1, + TEGRA20_DAS_DAP_SEL_DAC1); +diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c +--- a/sound/soc/tegra/tegra210_adx.c ++++ b/sound/soc/tegra/tegra210_adx.c +@@ -496,16 +496,12 @@ static struct snd_kcontrol_new tegra264_adx_controls[] = { + static int tegra210_adx_component_probe(struct snd_soc_component *component) + { + struct tegra210_adx *adx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (adx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, adx->soc_data->controls, +- adx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add ADX controls, err: %d\n", err); +- } ++ if (adx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, adx->soc_data->controls, ++ adx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_adx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c +--- a/sound/soc/tegra/tegra210_amx.c ++++ b/sound/soc/tegra/tegra210_amx.c +@@ -504,16 +504,12 @@ static struct snd_kcontrol_new tegra264_amx_controls[] = { + static int tegra210_amx_component_probe(struct snd_soc_component *component) + { + struct tegra210_amx *amx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (amx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, amx->soc_data->controls, +- amx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add AMX controls, err: %d\n", err); +- } ++ if (amx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, amx->soc_data->controls, ++ amx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_amx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c +--- a/sound/soc/tegra/tegra210_i2s.c ++++ b/sound/soc/tegra/tegra210_i2s.c +@@ -1090,13 +1090,13 @@ static int tegra210_i2s_probe(struct platform_device *pdev) + "can't retrieve I2S bit clock\n"); + + /* +- * Not an error, as this clock is needed only when some other I/O +- * requires input clock from current I2S instance, which is +- * configurable from DT. ++ * This clock is optional and is only needed when another I/O uses ++ * the current I2S instance as its input clock, as configured in DT. + */ +- i2s->clk_sync_input = devm_clk_get(dev, "sync_input"); ++ i2s->clk_sync_input = devm_clk_get_optional(dev, "sync_input"); + if (IS_ERR(i2s->clk_sync_input)) +- dev_dbg(dev, "can't retrieve I2S sync input clock\n"); ++ return dev_err_probe(dev, PTR_ERR(i2s->clk_sync_input), ++ "can't retrieve I2S sync input clock\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/ti/ams-delta.c b/sound/soc/ti/ams-delta.c +--- a/sound/soc/ti/ams-delta.c ++++ b/sound/soc/ti/ams-delta.c +@@ -562,21 +562,21 @@ static int ams_delta_probe(struct platform_device *pdev) + + card->dev = &pdev->dev; + +- handset_mute = devm_gpiod_get(card->dev, "handset_mute", ++ handset_mute = devm_gpiod_get(&pdev->dev, "handset_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handset_mute)) + return PTR_ERR(handset_mute); + +- handsfree_mute = devm_gpiod_get(card->dev, "handsfree_mute", ++ handsfree_mute = devm_gpiod_get(&pdev->dev, "handsfree_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handsfree_mute)) + return PTR_ERR(handsfree_mute); + + ret = snd_soc_register_card(card); + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + card->dev = NULL; +- return ret; ++ return dev_err_probe(&pdev->dev, ret, ++ "snd_soc_register_card() failed\n"); + } + return 0; + } +diff --git a/sound/soc/ti/davinci-evm.c b/sound/soc/ti/davinci-evm.c +--- a/sound/soc/ti/davinci-evm.c ++++ b/sound/soc/ti/davinci-evm.c +@@ -245,9 +245,8 @@ static int davinci_evm_probe(struct platform_device *pdev) + + snd_soc_card_set_drvdata(&evm_soc_card, drvdata); + ret = devm_snd_soc_register_card(&pdev->dev, &evm_soc_card); +- + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n"); + goto err_put; + } + +diff --git a/sound/soc/ti/j721e-evm.c b/sound/soc/ti/j721e-evm.c +--- a/sound/soc/ti/j721e-evm.c ++++ b/sound/soc/ti/j721e-evm.c +@@ -870,10 +870,9 @@ static int j721e_soc_probe(struct platform_device *pdev) + card->num_dapm_routes = ARRAY_SIZE(j721e_cpb_dapm_routes); + card->fully_routed = 1; + +- if (snd_soc_of_parse_card_name(card, "model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "model"); ++ if (ret) ++ return ret; + + link_cnt = 0; + conf_cnt = 0; +diff --git a/sound/soc/ti/omap-abe-twl6040.c b/sound/soc/ti/omap-abe-twl6040.c +--- a/sound/soc/ti/omap-abe-twl6040.c ++++ b/sound/soc/ti/omap-abe-twl6040.c +@@ -234,16 +234,13 @@ static int omap_abe_probe(struct platform_device *pdev) + card->dapm_routes = audio_map; + card->num_dapm_routes = ARRAY_SIZE(audio_map); + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + + ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); +- if (ret) { +- dev_err(&pdev->dev, "Error while parsing DAPM routing\n"); ++ if (ret) + return ret; +- } + + dai_node = of_parse_phandle(node, "ti,mcpdm", 0); + if (!dai_node) { +@@ -299,8 +296,8 @@ static int omap_abe_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); ++ dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return ret; + } +diff --git a/sound/soc/ti/omap-dmic.c b/sound/soc/ti/omap-dmic.c +--- a/sound/soc/ti/omap-dmic.c ++++ b/sound/soc/ti/omap-dmic.c +@@ -465,10 +465,9 @@ static int asoc_dmic_probe(struct platform_device *pdev) + mutex_init(&dmic->mutex); + + dmic->fclk = devm_clk_get(dmic->dev, "fck"); +- if (IS_ERR(dmic->fclk)) { +- dev_err(dmic->dev, "can't get fck\n"); +- return -ENODEV; +- } ++ if (IS_ERR(dmic->fclk)) ++ return dev_err_probe(dmic->dev, PTR_ERR(dmic->fclk), ++ "can't get fck\n"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (!res) { +diff --git a/sound/soc/ti/omap-hdmi.c b/sound/soc/ti/omap-hdmi.c +--- a/sound/soc/ti/omap-hdmi.c ++++ b/sound/soc/ti/omap-hdmi.c +@@ -24,8 +24,6 @@ + #define DRV_NAME "omap-hdmi-audio" + + struct hdmi_audio_data { +- struct snd_soc_card *card; +- + const struct omap_hdmi_audio_ops *ops; + struct device *dssdev; + struct snd_dmaengine_dai_dma_data dma_data; +@@ -375,12 +373,9 @@ static int omap_hdmi_audio_probe(struct platform_device *pdev) + card->dev = dev; + + ret = devm_snd_soc_register_card(dev, card); +- if (ret) { +- dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "snd_soc_register_card() failed\n"); + +- ad->card = card; + snd_soc_card_set_drvdata(card, ad); + + dev_set_drvdata(dev, ad); +diff --git a/sound/soc/ti/omap-twl4030.c b/sound/soc/ti/omap-twl4030.c +--- a/sound/soc/ti/omap-twl4030.c ++++ b/sound/soc/ti/omap-twl4030.c +@@ -17,7 +17,6 @@ + */ + + #include +-#include + #include + #include + +@@ -29,6 +28,7 @@ + #include "omap-mcbsp.h" + + struct omap_twl4030 { ++ struct snd_soc_jack_gpio hs_jack_gpio; + struct snd_soc_jack hs_jack; + }; + +@@ -124,38 +124,18 @@ static struct snd_soc_jack_pin hs_jack_pins[] = { + }, + }; + +-/* Headset jack detection gpios */ +-static struct snd_soc_jack_gpio hs_jack_gpios[] = { +- { +- .name = "ti,jack-det", +- .report = SND_JACK_HEADSET, +- .debounce_time = 200, +- }, +-}; +- +-static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm, +- int connected, char *pin) +-{ +- if (!connected) +- snd_soc_dapm_disable_pin(dapm, pin); +-} +- + static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + { + struct snd_soc_card *card = rtd->card; +- struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); +- struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev); ++ + struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); +- int ret = 0; ++ int ret; + + /* + * This is a bit of a hack, but the GPIO is optional so we + * only want to add the jack detection if the GPIO is there. + */ + if (of_property_present(card->dev->of_node, "ti,jack-det-gpio")) { +- hs_jack_gpios[0].gpiod_dev = card->dev; +- hs_jack_gpios[0].idx = 0; +- + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET, + &priv->hs_jack, hs_jack_pins, +@@ -163,36 +143,19 @@ static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + if (ret) + return ret; + +- ret = snd_soc_jack_add_gpios(&priv->hs_jack, +- ARRAY_SIZE(hs_jack_gpios), +- hs_jack_gpios); ++ priv->hs_jack_gpio.name = "ti,jack-det"; ++ priv->hs_jack_gpio.report = SND_JACK_HEADSET; ++ priv->hs_jack_gpio.debounce_time = 200; ++ priv->hs_jack_gpio.gpiod_dev = card->dev; ++ priv->hs_jack_gpio.idx = 0; ++ ++ ret = snd_soc_jack_add_gpios(&priv->hs_jack, 1, ++ &priv->hs_jack_gpio); + if (ret) + return ret; + } + +- /* +- * NULL pdata means we booted with DT. In this case the routing is +- * provided and the card is fully routed, no need to mark pins. +- */ +- if (!pdata || !pdata->custom_routing) +- return ret; +- +- /* Disable not connected paths if not used */ +- twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); +- twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk"); +- +- twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In"); +- +- return ret; ++ return 0; + } + + /* Digital audio interface glue - connects codec <--> CPU */ +@@ -237,11 +200,15 @@ static struct snd_soc_card omap_twl4030_card = { + + static int omap_twl4030_probe(struct platform_device *pdev) + { +- struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); +- struct device_node *node = pdev->dev.of_node; + struct snd_soc_card *card = &omap_twl4030_card; ++ struct device_node *node, *dai_node; + struct omap_twl4030 *priv; +- int ret = 0; ++ struct property *prop; ++ int ret; ++ ++ node = pdev->dev.of_node; ++ if (!node) ++ return -ENODEV; + + card->dev = &pdev->dev; + +@@ -249,69 +216,52 @@ static int omap_twl4030_probe(struct platform_device *pdev) + if (priv == NULL) + return -ENOMEM; + +- if (node) { +- struct device_node *dai_node; +- struct property *prop; ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- dai_node = of_parse_phandle(node, "ti,mcbsp", 0); +- if (!dai_node) { +- dev_err(&pdev->dev, "McBSP node is not provided\n"); +- return -EINVAL; +- } +- omap_twl4030_dai_links[0].cpus->dai_name = NULL; +- omap_twl4030_dai_links[0].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[0].platforms->name = NULL; +- omap_twl4030_dai_links[0].platforms->of_node = dai_node; +- +- dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); +- if (!dai_node) { +- card->num_links = 1; +- } else { +- omap_twl4030_dai_links[1].cpus->dai_name = NULL; +- omap_twl4030_dai_links[1].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[1].platforms->name = NULL; +- omap_twl4030_dai_links[1].platforms->of_node = dai_node; +- } +- +- /* Optional: audio routing can be provided */ +- prop = of_find_property(node, "ti,audio-routing", NULL); +- if (prop) { +- ret = snd_soc_of_parse_audio_routing(card, +- "ti,audio-routing"); +- if (ret) +- return ret; +- +- card->fully_routed = 1; +- } +- } else if (pdata) { +- if (pdata->card_name) { +- card->name = pdata->card_name; +- } else { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- if (!pdata->voice_connected) +- card->num_links = 1; +- } else { +- dev_err(&pdev->dev, "Missing pdata\n"); ++ if (!card->name) { ++ dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + ++ dai_node = of_parse_phandle(node, "ti,mcbsp", 0); ++ if (!dai_node) { ++ dev_err(&pdev->dev, "McBSP node is not provided\n"); ++ return -EINVAL; ++ } ++ omap_twl4030_dai_links[0].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[0].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[0].platforms->name = NULL; ++ omap_twl4030_dai_links[0].platforms->of_node = dai_node; ++ ++ dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); ++ if (!dai_node) { ++ card->num_links = 1; ++ } else { ++ omap_twl4030_dai_links[1].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[1].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[1].platforms->name = NULL; ++ omap_twl4030_dai_links[1].platforms->of_node = dai_node; ++ } ++ ++ /* Optional: audio routing can be provided */ ++ prop = of_find_property(node, "ti,audio-routing", NULL); ++ if (prop) { ++ ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); ++ if (ret) ++ return ret; ++ ++ card->fully_routed = 1; ++ } ++ + snd_soc_card_set_drvdata(card, priv); + ret = devm_snd_soc_register_card(&pdev->dev, card); +- if (ret) { +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/ti/rx51.c b/sound/soc/ti/rx51.c +--- a/sound/soc/ti/rx51.c ++++ b/sound/soc/ti/rx51.c +@@ -358,21 +358,22 @@ static struct snd_soc_card rx51_sound_card = { + static int rx51_soc_probe(struct platform_device *pdev) + { + struct rx51_audio_pdata *pdata; +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct snd_soc_card *card = &rx51_sound_card; + int err; + + if (!of_machine_is_compatible("nokia,omap3-n900")) + return -ENODEV; + +- card->dev = &pdev->dev; ++ card->dev = dev; + + if (np) { + struct device_node *dai_node; + + dai_node = of_parse_phandle(np, "nokia,cpu-dai", 0); + if (!dai_node) { +- dev_err(card->dev, "McBSP node is not provided\n"); ++ dev_err(dev, "McBSP node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].cpus->dai_name = NULL; +@@ -382,7 +383,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 0); + if (!dai_node) { +- dev_err(card->dev, "Codec node is not provided\n"); ++ dev_err(dev, "Codec node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].codecs->name = NULL; +@@ -390,7 +391,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 1); + if (!dai_node) { +- dev_err(card->dev, "Auxiliary Codec node is not provided\n"); ++ dev_err(dev, "Auxiliary Codec node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[0].dlc.name = NULL; +@@ -400,7 +401,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,headphone-amplifier", 0); + if (!dai_node) { +- dev_err(card->dev, "Headphone amplifier node is not provided\n"); ++ dev_err(dev, "Headphone amplifier node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[1].dlc.name = NULL; +@@ -409,40 +410,36 @@ static int rx51_soc_probe(struct platform_device *pdev) + rx51_codec_conf[1].dlc.of_node = dai_node; + } + +- pdata = devm_kzalloc(card->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, pdata); + +- pdata->tvout_selection_gpio = devm_gpiod_get(card->dev, ++ pdata->tvout_selection_gpio = devm_gpiod_get(dev, + "tvout-selection", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->tvout_selection_gpio)) { +- dev_err(card->dev, "could not get tvout selection gpio\n"); +- return PTR_ERR(pdata->tvout_selection_gpio); +- } ++ if (IS_ERR(pdata->tvout_selection_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->tvout_selection_gpio), ++ "could not get tvout selection gpio\n"); + +- pdata->eci_sw_gpio = devm_gpiod_get(card->dev, "eci-switch", ++ pdata->eci_sw_gpio = devm_gpiod_get(dev, "eci-switch", + GPIOD_OUT_HIGH); +- if (IS_ERR(pdata->eci_sw_gpio)) { +- dev_err(card->dev, "could not get eci switch gpio\n"); +- return PTR_ERR(pdata->eci_sw_gpio); +- } ++ if (IS_ERR(pdata->eci_sw_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->eci_sw_gpio), ++ "could not get eci switch gpio\n"); + +- pdata->speaker_amp_gpio = devm_gpiod_get(card->dev, ++ pdata->speaker_amp_gpio = devm_gpiod_get(dev, + "speaker-amplifier", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->speaker_amp_gpio)) { +- dev_err(card->dev, "could not get speaker enable gpio\n"); +- return PTR_ERR(pdata->speaker_amp_gpio); +- } ++ if (IS_ERR(pdata->speaker_amp_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->speaker_amp_gpio), ++ "could not get speaker enable gpio\n"); + +- err = devm_snd_soc_register_card(card->dev, card); +- if (err) { +- dev_err(card->dev, "snd_soc_register_card failed (%d)\n", err); +- return err; +- } ++ err = devm_snd_soc_register_card(dev, card); ++ if (err) ++ return dev_err_probe(dev, err, ++ "snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c +--- a/sound/soc/uniphier/aio-cpu.c ++++ b/sound/soc/uniphier/aio-cpu.c +@@ -795,16 +795,12 @@ int uniphier_aio_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(dev, &uniphier_aio_component, + chip->chip_spec->dais, + chip->chip_spec->num_dais); +- if (ret) { +- dev_err(dev, "Register component failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + ret = uniphier_aiodma_soc_register_platform(pdev); +- if (ret) { +- dev_err(dev, "Register platform failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + return 0; + +diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h +--- a/sound/soc/uniphier/aio.h ++++ b/sound/soc/uniphier/aio.h +@@ -156,7 +156,7 @@ struct uniphier_aio_selector { + int hw; + }; + +-/** ++/* + * 'SoftWare MAPping' setting of UniPhier AIO registers. + * + * We have to setup 'virtual' register maps to access 'real' registers of AIO. +diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c +--- a/sound/soc/ux500/mop500.c ++++ b/sound/soc/ux500/mop500.c +@@ -68,10 +68,11 @@ static void mop500_of_node_put(void) + of_node_put(mop500_dai_links[0].codecs->of_node); + } + +-static int mop500_of_probe(struct platform_device *pdev, +- struct device_node *np) ++static int mop500_of_probe(struct snd_soc_card *card) + { ++ struct device *dev = card->dev; + struct device_node *codec_np, *msp_np[2]; ++ struct device_node *np = dev->of_node; + int i; + + msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); +@@ -79,7 +80,7 @@ static int mop500_of_probe(struct platform_device *pdev, + codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); + + if (!(msp_np[0] && msp_np[1] && codec_np)) { +- dev_err(&pdev->dev, "Phandle missing or invalid\n"); ++ dev_err(dev, "Phandle missing or invalid\n"); + for (i = 0; i < 2; i++) + of_node_put(msp_np[i]); + of_node_put(codec_np); +@@ -95,21 +96,20 @@ static int mop500_of_probe(struct platform_device *pdev, + mop500_dai_links[i].codecs->name = NULL; + } + +- snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name"); ++ snd_soc_of_parse_card_name(card, "stericsson,card-name"); + + return 0; + } + + static int mop500_probe(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; + int ret; + + dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); + + mop500_card.dev = &pdev->dev; + +- ret = mop500_of_probe(pdev, np); ++ ret = mop500_of_probe(&mop500_card); + if (ret) + return ret; + +diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c +--- a/sound/soc/ux500/mop500_ab8500.c ++++ b/sound/soc/ux500/mop500_ab8500.c +@@ -433,5 +433,5 @@ void mop500_ab8500_remove(struct snd_soc_card *card) + clk_put(drvdata->clk_ptr_ulpclk); + clk_put(drvdata->clk_ptr_intclk); + +- snd_soc_card_set_drvdata(card, drvdata); ++ snd_soc_card_set_drvdata(card, NULL); + } +diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c +--- a/sound/soc/xilinx/xlnx_formatter_pcm.c ++++ b/sound/soc/xilinx/xlnx_formatter_pcm.c +@@ -9,8 +9,6 @@ + #include + #include + #include +-#include +-#include + #include + + #include +diff --git a/sound/soc/xilinx/xlnx_i2s.c b/sound/soc/xilinx/xlnx_i2s.c +--- a/sound/soc/xilinx/xlnx_i2s.c ++++ b/sound/soc/xilinx/xlnx_i2s.c +@@ -9,9 +9,8 @@ + + #include + #include +-#include +-#include + #include ++#include + #include + #include + +@@ -174,7 +173,6 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + int ret; + u32 format; + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) +@@ -184,18 +182,16 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + if (IS_ERR(drv_data->base)) + return PTR_ERR(drv_data->base); + +- ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels); +- if (ret < 0) { +- dev_err(dev, "cannot get supported channels\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,num-channels", &drv_data->channels); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get supported channels\n"); ++ + drv_data->channels *= 2; + +- ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width); +- if (ret < 0) { +- dev_err(dev, "cannot get data width\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,dwidth", &drv_data->data_width); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get data width\n"); ++ + switch (drv_data->data_width) { + case 16: + format = SNDRV_PCM_FMTBIT_S16_LE; +@@ -207,7 +203,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + +- if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) { ++ if (device_is_compatible(dev, "xlnx,i2s-transmitter-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_playback"; + drv_data->dai_drv.playback.stream_name = "Playback"; + drv_data->dai_drv.playback.formats = format; +@@ -215,7 +211,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + drv_data->dai_drv.playback.channels_max = drv_data->channels; + drv_data->dai_drv.playback.rates = SNDRV_PCM_RATE_8000_192000; + drv_data->dai_drv.ops = &xlnx_i2s_dai_ops; +- } else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) { ++ } else if (device_is_compatible(dev, "xlnx,i2s-receiver-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_capture"; + drv_data->dai_drv.capture.stream_name = "Capture"; + drv_data->dai_drv.capture.formats = format; +@@ -233,10 +229,8 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component, + &drv_data->dai_drv, 1); +- if (ret) { +- dev_err(&pdev->dev, "i2s component registration failed\n"); ++ if (ret) + return ret; +- } + + dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name); + +diff --git a/sound/soc/xilinx/xlnx_spdif.c b/sound/soc/xilinx/xlnx_spdif.c +--- a/sound/soc/xilinx/xlnx_spdif.c ++++ b/sound/soc/xilinx/xlnx_spdif.c +@@ -10,8 +10,7 @@ + #include + #include + #include +-#include +-#include ++#include + #include + #include + #include +@@ -242,28 +241,24 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + struct spdif_dev_data *ctx; + + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->axi_clk = devm_clk_get_enabled(dev, "s_axi_aclk"); +- if (IS_ERR(ctx->axi_clk)) { +- ret = PTR_ERR(ctx->axi_clk); +- dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret); +- return ret; +- } ++ if (IS_ERR(ctx->axi_clk)) ++ return dev_err_probe(dev, PTR_ERR(ctx->axi_clk), ++ "failed to get s_axi_aclk\n"); + + ctx->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctx->base)) + return PTR_ERR(ctx->base); + +- ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode); +- if (ret < 0) { +- dev_err(dev, "cannot get SPDIF mode\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,spdif-mode", &ctx->mode); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get SPDIF mode\n"); ++ + if (ctx->mode) { + dai_drv = &xlnx_spdif_tx_dai; + } else { +@@ -274,29 +269,23 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + ret = devm_request_irq(dev, ret, + xlnx_spdifrx_irq_handler, + 0, "XLNX_SPDIF_RX", ctx); +- if (ret) { +- dev_err(dev, "spdif rx irq request failed\n"); +- return -ENODEV; +- } ++ if (ret) ++ return ret; + + init_waitqueue_head(&ctx->chsts_q); + dai_drv = &xlnx_spdif_rx_dai; + } + +- ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk); +- if (ret < 0) { +- dev_err(dev, "cannot get aud_clk_i value\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,aud_clk_i", &ctx->aclk); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get aud_clk_i value\n"); + + dev_set_drvdata(dev, ctx); + + ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component, + dai_drv, 1); +- if (ret) { +- dev_err(dev, "SPDIF component registration failed\n"); ++ if (ret) + return ret; +- } + + writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG); + dev_info(dev, "%s DAI registered\n", dai_drv->name); +diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c +--- a/sound/soc/xtensa/xtfpga-i2s.c ++++ b/sound/soc/xtensa/xtfpga-i2s.c +@@ -533,34 +533,25 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + int err, irq; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); +- if (!i2s) { +- err = -ENOMEM; +- goto err; +- } ++ if (!i2s) ++ return -ENOMEM; ++ + platform_set_drvdata(pdev, i2s); + i2s->dev = &pdev->dev; + dev_dbg(&pdev->dev, "dev: %p, i2s: %p\n", &pdev->dev, i2s); + + i2s->regs = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(i2s->regs)) { +- err = PTR_ERR(i2s->regs); +- goto err; +- } ++ if (IS_ERR(i2s->regs)) ++ return PTR_ERR(i2s->regs); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->regs, + &xtfpga_i2s_regmap_config); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- err = PTR_ERR(i2s->regmap); +- goto err; +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), "regmap init failed\n"); + + i2s->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(i2s->clk)) { +- dev_err(&pdev->dev, "couldn't get clock\n"); +- err = PTR_ERR(i2s->clk); +- goto err; +- } ++ if (IS_ERR(i2s->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->clk), "couldn't get clock\n"); + + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, + (0x1 << XTFPGA_I2S_CONFIG_CHANNEL_BASE)); +@@ -568,41 +559,34 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, XTFPGA_I2S_INT_UNDERRUN); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- err = irq; +- goto err; +- } ++ if (irq < 0) ++ return irq; ++ + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + xtfpga_i2s_threaded_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + pdev->name, i2s); +- if (err < 0) { +- dev_err(&pdev->dev, "request_irq failed\n"); +- goto err; +- } ++ if (err < 0) ++ return err; + + err = devm_snd_soc_register_component(&pdev->dev, + &xtfpga_i2s_component, + xtfpga_i2s_dai, + ARRAY_SIZE(xtfpga_i2s_dai)); +- if (err < 0) { +- dev_err(&pdev->dev, "couldn't register component\n"); +- goto err; +- } ++ if (err < 0) ++ return err; ++ + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + err = xtfpga_i2s_runtime_resume(&pdev->dev); +- if (err) +- goto err_pm_disable; ++ if (err) { ++ pm_runtime_disable(&pdev->dev); ++ return err; ++ } + } +- return 0; + +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +-err: +- dev_err(&pdev->dev, "%s: err = %d\n", __func__, err); +- return err; ++ return 0; + } + + static void xtfpga_i2s_remove(struct platform_device *pdev) diff --git a/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..943e088592ddac5b6d4a71d6ddfd3a0517dd16e1 GIT binary patch literal 594 zcmV-Y0|Cq91Vz+kxxoQzB>1aV-c7^Q-flrov#WDBW-h+MtLVT_T^I*+)Jf_^PV8Lj-aqT zM(Sj|Wsewo?Kn#Rt55q|Pt)5r+A{3!Ia00h1vO4hhIc zsBjlAajg*uNg3})W_8encn4>FpU!;M0EGaPY`aS6WXtu+yIj_<1$-#$d~-JjA)I_= z$Pm{t{kC}M+E>386*p?nP%9!2&-R%Gay^~xkB+25g%4Km7Dz*<$lk(~&}wMZi(YU) zA1Vpwj30@|Vv2p*uT7Nzg&q^uoumT{i9hFJvXZ5q?QR+A`7>w@?;JFWJ{%+z^b4hx z@wAv4NckU2%gn?)dl;3u*1>tGKziS^%8YV^z=Tl^b)&kzm38lZLHR^%Dc|L}^S@v- z?svfXA9Jh4s>&vfbr6?bl_{G7y0S_k#}unF+f{E3&SS4S1| g0RbFFJ%AkTdGY+B3@>7}C%vd;b51P`KrrqfZOMlh3;+NC literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch b/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch new file mode 100644 index 0000000..7de337b --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch @@ -0,0 +1,661 @@ +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -179,7 +179,8 @@ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info + int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, + int mode, struct snd_rawmidi_file *rfile, + int depth); +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); + int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, +@@ -201,6 +202,11 @@ static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, + return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); + } + ++static inline int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_release_nested(rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c +--- a/sound/aoa/soundbus/i2sbus/core.c ++++ b/sound/aoa/soundbus/i2sbus/core.c +@@ -240,6 +240,8 @@ static int i2sbus_add_dev(struct macio_dev *macio, + } + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + int irq = irq_of_parse_and_map(np, i); ++ if (!irq) ++ goto err; + if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) + goto err; + dev->interrupts[i] = irq; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -571,7 +571,6 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + struct snd_rawmidi *rmidi; + + rmidi = rfile->rmidi; +- guard(mutex)(&rmidi->open_mutex); + if (rfile->input) { + close_substream(rmidi, rfile->input, 1); + rfile->input = NULL; +@@ -585,7 +584,8 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + } + + /* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth) + { + struct snd_rawmidi *rmidi; + +@@ -593,11 +593,13 @@ int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) + return -ENXIO; + + rmidi = rfile->rmidi; ++ mutex_lock_nested(&rmidi->open_mutex, depth); + rawmidi_release_priv(rfile); ++ mutex_unlock(&rmidi->open_mutex); + module_put(rmidi->card->module); + return 0; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_release); ++EXPORT_SYMBOL(snd_rawmidi_kernel_release_nested); + + static int snd_rawmidi_release(struct inode *inode, struct file *file) + { +@@ -607,7 +609,8 @@ static int snd_rawmidi_release(struct inode *inode, struct file *file) + + rfile = file->private_data; + rmidi = rfile->rmidi; +- rawmidi_release_priv(rfile); ++ scoped_guard(mutex, &rmidi->open_mutex) ++ rawmidi_release_priv(rfile); + kfree(rfile); + module = rmidi->card->module; + snd_card_file_remove(rmidi->card, file); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1184,7 +1184,8 @@ static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) + ump->legacy_substreams[dir][group] = NULL; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) +- snd_rawmidi_kernel_release(&ump->legacy_out_rfile); ++ snd_rawmidi_kernel_release_nested(&ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + } + return 0; + } +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -207,7 +207,7 @@ static void cx_remove(struct hda_codec *codec) + snd_hda_gen_remove(codec); + } + +-static void cx_process_headset_plugin(struct hda_codec *codec) ++static void cx_process_headset_detect_plug_type(struct hda_codec *codec) + { + unsigned int val; + unsigned int count = 0; +@@ -223,11 +223,9 @@ static void cx_process_headset_plugin(struct hda_codec *codec) + count++; + } while (count < 3); + val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0); +- if (val & 0x800) { +- codec_dbg(codec, "headset plugin, type is CTIA\n"); +- snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); +- } else if (val & 0x400) { +- codec_dbg(codec, "headset plugin, type is OMTP\n"); ++ if (val & 0xc00) { ++ codec_dbg(codec, "headset plugin, type is %s\n", ++ val & 0x800 ? "CTIA" : "OMTP"); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); + } else { + codec_dbg(codec, "headphone plugin\n"); +@@ -243,10 +241,12 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + * Check hp&mic tag to process headset plugin & plugout. + */ + mic_present = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0); +- if (!(mic_present & AC_PINSENSE_PRESENCE)) /* mic plugout */ ++ if (!(mic_present & AC_PINSENSE_PRESENCE)) { /* mic plugout */ + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20); +- else +- cx_process_headset_plugin(codec); ++ } else { ++ cx_process_headset_detect_plug_type(codec); ++ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); ++ } + } + + static int cx_suspend(struct hda_codec *codec) +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -1370,6 +1370,61 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); + } + ++/* ++ * HP Laptop 15-fd0xxx (SSID 103c:8bb6) Speaker Mute LED fix ++ * ++ * The speaker mute LED is controlled via VREF100 on NID 0x1a. ++ * This pin must remain powered (D0) even during suspend, otherwise ++ * the LED state is lost and the pin defaults to Hi-Z on resume, ++ * causing the LED to stop responding to mute toggles. ++ * Windows keeps this pin powered unconditionally, so matching that ++ * behavior ensures consistent LED operation across suspend/resume. ++ * The mic-mute LED is controlled via GPIO 0 with active-low polarity. ++ */ ++static unsigned int hp_8bb6_power_filter(struct hda_codec *codec, ++ hda_nid_t nid, ++ unsigned int power_state) ++{ ++ if (nid == 0x1a) ++ return AC_PWRST_D0; ++ return snd_hda_gen_path_power_filter(codec, nid, power_state); ++} ++ ++static int hp_8bb6_speaker_mute_led_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); ++ unsigned int val = (brightness == LED_OFF) ? PIN_IN : PIN_VREF100; ++ ++ snd_hda_set_pin_ctl_cache(codec, 0x1a, val); ++ return 0; ++} ++ ++static void alc236_fixup_hp_15_fd0xxx(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->micmute_led_polarity = 1; ++ alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); ++ ++ spec->mute_led_polarity = 0; ++ snd_hda_gen_add_mute_led_cdev(codec, hp_8bb6_speaker_mute_led_set); ++ ++ codec->power_filter = hp_8bb6_power_filter; ++ spec->no_shutup_pins = 1; ++ break; ++ ++ case HDA_FIXUP_ACT_INIT: ++ if (spec->gen.vmaster_mute.hook) ++ snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute); ++ break; ++ } ++} ++ + static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -4089,6 +4144,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, + ALC225_FIXUP_S3_POP_NOISE, + ALC700_FIXUP_INTEL_REFERENCE, ++ ALC700_FIXUP_INTEL_HADES_CANYON, + ALC274_FIXUP_DELL_BIND_DACS, + ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + ALC298_FIXUP_TPT470_DOCK_FIX, +@@ -4152,6 +4208,7 @@ enum { + ALC236_FIXUP_HP_GPIO_LED, + ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ++ ALC236_FIXUP_HP_15_FD0XXX, + ALC236_FIXUP_LENOVO_INV_DMIC, + ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, +@@ -4297,6 +4354,7 @@ enum { + ALC287_FIXUP_LENOVO_LEGION_AW88399, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, ++ ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4333,6 +4391,19 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + __snd_hda_apply_fixup(codec, id, action, 0); + } + ++static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ /* ++ * Windows may leave coef 0x1b bit 0x0400 cleared, causing broken ++ * analog playback after rebooting into Linux. Restore the bit ++ * during codec initialization. ++ */ ++ if (action == HDA_FIXUP_ACT_INIT) ++ alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); ++} ++ + static const struct hda_fixup alc269_fixups[] = { + [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { + .type = HDA_FIXUP_PINS, +@@ -5411,6 +5482,10 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC700_FIXUP_INTEL_HADES_CANYON] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc700_fixup_intel_hades_canyon, ++ }, + [ALC274_FIXUP_DELL_BIND_DACS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_bind_dacs, +@@ -5913,6 +5988,10 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_gpio, + }, ++ [ALC236_FIXUP_HP_15_FD0XXX] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc236_fixup_hp_15_fd0xxx, ++ }, + [ALC236_FIXUP_LENOVO_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, +@@ -7010,6 +7089,12 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_lenovo_dac_rename, + }, ++ [ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_TXNW2781_I2C, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7448,6 +7533,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), +@@ -8031,6 +8117,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), ++ SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8044,14 +8131,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), ++ SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), +- HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ ++ HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8231,6 +8319,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", ++ ALC700_FIXUP_INTEL_HADES_CANYON), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), + SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), + SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), +diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c +--- a/sound/usb/mixer.c ++++ b/sound/usb/mixer.c +@@ -1237,21 +1237,27 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) + } + + /* +- * Additional checks for sticky mixers ++ * Additional checks for sticky GET_CUR + * +- * Some devices' volume control mixers are sticky, which accept SET_CUR but +- * do absolutely nothing. ++ * Some devices' volume control mixers have sticky GET_CUR, which implies either ++ * stubbed SET_CUR or broken GET_CUR. For the former case, the mixer accepts ++ * SET_CUR but do absolutely nothing, so falling back to soft mixer is the only ++ * way to control the volume. For the latter case, the mixer has effective ++ * SET_CUR despite GET_CUR being constant, and the mixer is usable as long as we ++ * always provide mixer value from the ceche. + * + * Check the return values of GET_CUR with different SET_CUR values. Consider +- * the mixer as sticky if GET_CUR always returns a constant value. ++ * GET_CUR as sticky if GET_CUR always returns a constant value. + * +- * Some devices have effective SET_CUR despite GET_CUR being constant. Do not +- * consider the mixer as sticky if a quirk flag indicates that. ++ * Unfortunately, we can't distinguish between stubbed SET_CUR and broken ++ * GET_CUR with simple read-back tests. Disabling the mixer regardless and ++ * forcing userspace to use soft mixer instead can lead to audible distortion at ++ * low volume on some wireless headphones, probably due to their poorly- ++ * performed lossy codec. + * +- * Gate the registration of sticky mixers to prevent confusing userspace, so +- * that they won't cause ineffective volume control. However, for mixers with +- * effective SET_CUR but broken GET_CUR, the registration can continue normally +- * but further GET_CUR requests will be gated. ++ * Instead, mark GET_CUR as broken regardless and only provide mixer value from ++ * the cache. Users may opt into soft mixer in userspace audio stack if they ++ * need it. + */ + static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +@@ -1271,24 +1277,13 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + return 0; + } + +- if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { +- usb_audio_info(cval->head.mixer->chip, +- "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); +- +- cval->get_cur_broken = 1; +- return -ENXIO; +- } +- +- usb_audio_err(cval->head.mixer->chip, +- "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); + usb_audio_info(cval->head.mixer->chip, +- "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); ++ "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", ++ cval->head.id, mixer_ctrl_intf(cval->head.mixer), ++ cval->min, cval->max, cval->res, saved); + +- return -ENODEV; ++ cval->get_cur_broken = 1; ++ return -ENXIO; + } + + /* +@@ -1385,8 +1380,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + goto no_checks; + + ret = check_sticky_volume_control(cval, minchn, saved); +- if (ret == -ENODEV) +- goto sticky; + if (ret) + goto no_checks; + +@@ -1454,34 +1447,15 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + } + } + +- return 0; +- +-sticky: + /* +- * It makes no sense to restore the saved value for a sticky mixer, +- * since setting any value is a no-op. +- * +- * However, in some rare cases, SET_CUR is effective despite GET_CUR +- * always returns a constant value. These mixers are not sticky, but +- * there's no way to distinguish them. Without any additional +- * information, the best thing we can do is to set the mixer value to +- * the maximum before bailing out, so that a soft mixer can still reach +- * the maximum hardware volume if the mixer turns out to be non-sticky. +- * Meanwhile, all channels must be synchronized to prevent imbalance +- * volume. ++ * When GET_CUR is sticky, the saved value is bogus, so mixer values set ++ * by the sanity checks must be discarded through init_cur_mix_raw(). ++ * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK. + */ +- if (!cval->cmask) { +- snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); +- } else { +- idx = 0; +- for (i = 0; i < MAX_CHANNELS; i++) { +- if (cval->cmask & BIT(i)) { +- snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); +- idx++; +- } +- } +- } +- return ret; ++ if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK) ++ cval->get_cur_broken = 0; ++ ++ return 0; + } + + #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,13 +505,13 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + +-/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels +- * and sits on the monitor mixer branch, but it is traced through to the +- * Speaker output terminal and gets named "Speaker Playback Volume". Userspace +- * then adopts it as the stream's hardware volume, and any setting below 0 dB +- * attenuates some channels but not others (20 dB imbalance at 80%). Give it a +- * non-standard name so that it is no longer taken for the stream's master +- * volume, while remaining reachable for anyone who wants the monitor gain. ++/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is ++ * traced through to the Speaker output terminal, so it is named "Speaker ++ * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI ++ * testing found asymmetric attenuation within the main stereo pair. Userspace ++ * adopts this control as the stream's hardware volume, causing imbalance below ++ * 0 dB. Give it a non-standard name so that userspace no longer treats it as ++ * the stream master, while keeping the monitor gain reachable. + */ + static const struct usbmix_name_map audient_id14_map[] = { + { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ +@@ -602,7 +602,12 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .map = maya44_map, + }, + { +- /* Audient iD14 */ ++ /* Audient iD14 MkI */ ++ .id = USB_ID(0x2708, 0x0002), ++ .map = audient_id14_map, ++ }, ++ { ++ /* Audient iD14 MkII */ + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -1814,6 +1814,28 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * M-Audio Venom ++ * ++ * The AudioControl interface times out on every GET_CUR request, ++ * which adds around 47 seconds to the card registration and ++ * freezes the device, blocking streaming. ++ * Using an explicit composite quirk to skip the mixer entirely. ++ */ ++ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2084), ++ QUIRK_DRIVER_INFO { ++ .vendor_name = "M-Audio", ++ .product_name = "Venom", ++ QUIRK_DATA_COMPOSITE { ++ { QUIRK_DATA_IGNORE(0) }, ++ { QUIRK_DATA_STANDARD_AUDIO(1) }, ++ { QUIRK_DATA_STANDARD_AUDIO(2) }, ++ { QUIRK_DATA_STANDARD_MIDI(3) }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* Casio devices */ + { +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2215,10 +2215,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ +@@ -2256,8 +2256,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), ++ DEVICE_FLG(0x046d, 0x0aba, /* Logitech PRO X Wireless */ ++ QUIRK_FLAG_MIXER_GET_CUR_OK | ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ +@@ -2329,9 +2330,11 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ DEVICE_FLG(0x0763, 0x2084, /* M-Audio Venom */ ++ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ +@@ -2370,8 +2373,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ +@@ -2379,8 +2380,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ +@@ -2393,6 +2392,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ ++ QUIRK_FLAG_DSD_RAW | QUIRK_FLAG_DISABLE_AUTOSUSPEND | ++ QUIRK_FLAG_SKIP_IFACE_SETUP), + DEVICE_FLG(0x152a, 0x880a, /* NeuralDSP Quad Cortex */ + 0), /* Doesn't have the vendor quirk which would otherwise apply */ + DEVICE_FLG(0x1532, 0x055e, /* Razer Nommo V2 X */ +@@ -2505,8 +2507,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ + QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ +@@ -2647,7 +2647,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), +- QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), + QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), + QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -243,24 +243,18 @@ extern bool snd_usb_skip_validation; + * from snd_usb_handle_sync_urb. Instead fall through and enqueue a + * packet_info containing only size-0 packets, so the OUT ring keeps + * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). +- * QUIRK_FLAG_MIXER_GET_CUR_BROKEN +- * Some mixers are sticky, which means that setting their current volume is a +- * no-op, and reading the current volume returns a constant value. The sticky +- * check disables these mixers to prevent confusing userspace. However, some +- * devices do have a tunable volume despite the reported current volume being +- * constant. As the sticky check can't distinguish between the two categories, +- * setting this flag tells that the device should fall into the second +- * category when GET_CUR returns a constant value, resulting in the sticky +- * check being non-fatal and only disabling GET_CUR instead of the whole mixer. +- * The current volume will then be provided by the internal cache that stores +- * the last set volume ++ * QUIRK_FLAG_MIXER_GET_CUR_OK ++ * On some devices, whether their GET_CUR being sticky depends on whether ++ * hotpluggable components are present. When the hotpluggable components are ++ * missing on probe, their GET_CUR behavior is classified as broken. Set the ++ * flag to prevent the heuristics from gating GET_CUR. + * QUIRK_FLAG_PLAYBACK_URB_FIXUP + * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. + * This is needed for devices that exhibit boot-time audio stuttering due + * to insufficient buffer depth combined with xHCI scheduling variability. + * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling + * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. +- * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * QUIRK_FLAG_ALWAYS_SET_RATE + * Issue SET_CUR for the sample rate even when the clock already reports the + * requested rate. A device advertising a single rate is otherwise never sent + * the request at all, and some require it before streaming will start. +@@ -297,10 +291,10 @@ enum { + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, +- QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_MIXER_GET_CUR_OK = 30, + QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, + QUIRK_TYPE_ALWAYS_SET_RATE = 32, +-/* Please also edit snd_usb_audio_quirk_flag_names */ ++/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ + }; + + #define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) +@@ -335,7 +329,7 @@ enum { + #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) +-#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) + #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) + #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + diff --git a/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..cdb2260a9b787c581325e6ef0474c08502732d46 GIT binary patch literal 594 zcmV-Y0<&lXjnD(*f zV*8f_if@$jknT^|V!LAaggcXh;{{7cMx|-3;1X7SUbG}{^;&d?BubvEQ`Wrev z^r;7Q=6w8XMq)@-Nh6G|8NEWzd4&`41oZr_ZV9&|bMv2b{j}aFcXF^t6qL3!2A4%_ z8kH#zx?FApM+t1-4VRu*JaY~LTm!u1M>o7VbD1mO|MxUAsc9WJrxGB z{!R3m$k$0f)Y^1Su=(B1WtGA%HJg=*ugH`?>if>_F#+TF%v1)2p;TE%c?*BJ;Ruv4 zeu5q;WHHCE@V#h*sCfIy6Sq}Md?zqv5DuR#m%*RvdOMtL5erR2Eh|8j8b>4F)4AfF zOX+u3{_>If!fMe~91Tf}4v+9pAf$~zrOmWohm0Zob zTe>B^-yu=CB~X_j1owJHVaOnsk57YR7-gKfx*LwNXBYc{T+~BS*=8G#M8w`{8r*LQ z5y#zotiEFQJ|k(Ns}&!fZy;siQ3!>?XYO6{xGt#Uwk%5S3x>AR8pWn6>pA*pqirjH zYj4hHzoWjbK%o}RTfQ_WJw^!H^4kch*OXMK8xr>t)klOVOhF9>;iXHE8`hi~ShA9N g5pV);FBw%0G7xs#Xo>iisM`xrOQ5KJqE^$>jHk;LuK)l5 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch b/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch new file mode 100644 index 0000000..49c1eb3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch @@ -0,0 +1,871 @@ +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -237,34 +237,34 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + +-#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 +-#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 +-#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 +-#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 +-#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 +-#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 +-#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 +-#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 +-#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 +-#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 +-#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 +-#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 +-#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 +-#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 +-#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 +-#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 +-#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 +-#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 +-#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 +-#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 +-#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 +-#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 +-#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 +-#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 +-#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 +-#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 +-#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 +-#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++#define LPASS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LPASS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LPASS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LPASS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LPASS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LPASS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LPASS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LPASS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LPASS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LPASS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LPASS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LPASS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LPASS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LPASS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LPASS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LPASS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LPASS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LPASS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LPASS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LPASS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LPASS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LPASS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LPASS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LPASS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LPASS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LPASS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LPASS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LPASS_CLK_ID_VA_QAIF_IF0_EBIT 98 + + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -70,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "8EA1"), + }, + }, ++ { ++ /* HP OmniBook X Flip 16-cc0xxx */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA2"), ++ }, ++ }, + {} + }; + +@@ -209,6 +216,20 @@ static const struct config_entry config_table[] = { + {} + }, + }, ++ { ++ .flags = FLAG_AMD_LEGACY, ++ .device = ACP_PCI_DEV_ID, ++ .dmi_table = (const struct dmi_system_id []) { ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), ++ }, ++ }, ++ {} ++ }, ++ }, + }; + + static int snd_amd_acp_acpi_find_config(struct pci_dev *pci) +diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +--- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c ++++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +@@ -382,6 +382,14 @@ static const struct dmi_system_id acp3x_es83xx_dmi_table[] = { + }, + .driver_data = (void *)(ES83XX_ENABLE_DMIC), + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), ++ }, ++ .driver_data = (void *)(ES83XX_ENABLE_DMIC), ++ }, + {} + }; + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -570,6 +570,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Alienware"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R1 AMD"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c +--- a/sound/soc/codecs/max98926.c ++++ b/sound/soc/codecs/max98926.c +@@ -360,7 +360,7 @@ static int max98926_dai_set_fmt(struct snd_soc_dai *codec_dai, + regmap_write(max98926->regmap, + MAX98926_FORMAT, MAX98926_DAI_DLY_MASK); + regmap_update_bits(max98926->regmap, MAX98926_FORMAT, +- MAX98926_DAI_BCI_MASK, invert); ++ MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK, invert); + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -1779,6 +1779,7 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + dev_dbg(dev, "%s jack/mic/amp func_status=0x%x, 0x%x, 0x%x\n", + __func__, jack_func_status, mic_func_status, amp_func_status); + ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); + /* DMIC */ + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { + rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); +diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h +--- a/sound/soc/codecs/rt712-sdca.h ++++ b/sound/soc/codecs/rt712-sdca.h +@@ -74,6 +74,7 @@ struct rt712_dmic_kctrl_priv { + #define RT712_JD_PRODUCT_NUM 0x00 + #define RT712_ANALOG_BIAS_CTL3 0x04 + #define RT712_JD_CTL1 0x09 ++#define RT712_JD_CTL3 0x0b + #define RT712_IO_CTL 0x0c + #define RT712_LDO2_3_CTL1 0x0e + #define RT712_PARA_VERB_CTL 0x1a +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +--- a/sound/soc/codecs/rt766-sdca-sdw.c ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -233,6 +233,8 @@ static void rt766_sdca_sdw_remove(struct sdw_slave *slave) + static const struct sdw_device_id rt766_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x4, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x4, 0x1, 0), + {}, + }; + MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -55,6 +55,12 @@ + #define TAC_XU_BYPASS_REG(func, xu) \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##xu, \ + TAC_SDCA_CTL_XU_BYPASS, 0) ++#define TAC_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_##ch) ++#define TAC_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ ++ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_##ch) + + /* mute registers */ + #define FU21_L_MUTE_REG TAC_MUTE_REG(SA, FU21, LEFT) +@@ -123,9 +129,9 @@ static const DECLARE_TLV_DB_SCALE(tac5xx2_dvc_tlv, -7200, 50, 0); + #define TAC_DOUBLE_Q78_TLV(name, func_id, ent_id) \ + SDCA_DOUBLE_Q78_TLV(name, \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), \ + TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv) + + struct tac5xx2_prv { +@@ -381,37 +387,53 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 1, 0x71), 0x5}, + {TAC_REG_SDW(0, 1, 0x72), 0x6}, + {TAC_REG_SDW(0, 1, 0x73), 0x7}, ++ /* SA */ ++ {TAC_MUTE_REG(SA, FU21, LEFT), 0x1}, ++ {TAC_MUTE_REG(SA, FU21, RIGHT), 0x1}, ++ {TAC_VOLUME_REG(SA, FU21, LEFT), 0x9c00}, ++ {TAC_VOLUME_REG(SA, FU21, RIGHT), 0x9c00}, ++ {TAC_MUTE_REG(SA, FU23, LEFT), 0x1}, ++ {TAC_MUTE_REG(SA, FU23, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SA, FU23, LEFT), 0x0}, ++ {TAC_GAIN_REG(SA, FU23, RIGHT), 0x0}, ++ /* SM */ ++ {TAC_USAGE_REG(SM, IT11), 0x0}, ++ {TAC_USAGE_REG(SM, OT113), 0x0}, ++ {TAC_MUTE_REG(SM, FU113, LEFT), 0x1}, ++ {TAC_MUTE_REG(SM, FU113, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SM, FU113, LEFT), 0x0}, ++ {TAC_GAIN_REG(SM, FU113, RIGHT), 0x0}, ++ {TAC_MUTE_REG(SM, FU11, LEFT), 0x1}, ++ {TAC_MUTE_REG(SM, FU11, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SM, FU11, LEFT), 0x0}, ++ {TAC_GAIN_REG(SM, FU11, RIGHT), 0x0}, ++ {TAC_XU_BYPASS_REG(SM, XU12), 0x1}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_CS113, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, ++ /* UAJ */ ++ {TAC_USAGE_REG(UAJ, IT33), 0x0}, ++ {TAC_USAGE_REG(UAJ, IT41), 0x0}, ++ {TAC_USAGE_REG(UAJ, OT36), 0x0}, ++ {TAC_USAGE_REG(UAJ, OT45), 0x0}, ++ {TAC_MUTE_REG(UAJ, FU41, LEFT), 0x1}, ++ {TAC_MUTE_REG(UAJ, FU41, RIGHT), 0x1}, ++ {TAC_VOLUME_REG(UAJ, FU41, LEFT), 0x0}, ++ {TAC_VOLUME_REG(UAJ, FU41, RIGHT), 0x0}, ++ {TAC_MUTE_REG(UAJ, FU36, RIGHT), 0x1}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0x0}, ++ {TAC_XU_BYPASS_REG(UAJ, XU42), 0x0}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS36, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS41, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, + }; + + static const struct reg_sequence tac_spk_seq[] = { + REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), + REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +-}; +- +-static const struct reg_sequence tac_sm_seq[] = { +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +-}; +- +-static const struct reg_sequence tac_uaj_seq[] = { +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), 0), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), + }; + + static bool tac_volatile_reg(struct device *dev, unsigned int reg) +@@ -468,25 +490,25 @@ static int tac_sdca_mbq_size(struct device *dev, unsigned int reg) + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_MASTER_GAIN, 0): ++ TAC_SDCA_CHANNEL_GAIN, 0): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS): + return 2; + + default: +@@ -531,7 +553,7 @@ static const struct snd_kcontrol_new tac_uaj_controls[] = { + TAC_DOUBLE_Q78_TLV("UAJ Playback Volume", UAJ, FU41), + SDCA_SINGLE_Q78_TLV("UAJ Capture Volume", + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), + TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv), + }; + +@@ -1761,16 +1783,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + dev_dbg(dev, "smartmic init done\n"); +- +- if (first) { +- ret = regmap_multi_reg_write(tac_dev->regmap, tac_sm_seq, +- ARRAY_SIZE(tac_sm_seq)); +- if (ret) { +- dev_err(tac_dev->dev, +- "init writes failed, err=%d", ret); +- goto io_init_err; +- } +- } + } + + if (tac_dev->uaj_func_data) { +@@ -1783,14 +1795,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + dev_dbg(dev, "uaj init done\n"); + + if (first) { +- ret = regmap_multi_reg_write(tac_dev->regmap, tac_uaj_seq, +- ARRAY_SIZE(tac_uaj_seq)); +- if (ret) { +- dev_err(tac_dev->dev, +- "init writes failed, err=%d", ret); +- goto io_init_err; +- } +- + if (tac_dev->hs_jack) { + ret = tac5xx2_jack_init(tac_dev); + if (ret) { +diff --git a/sound/soc/codecs/tac5xx2.h b/sound/soc/codecs/tac5xx2.h +--- a/sound/soc/codecs/tac5xx2.h ++++ b/sound/soc/codecs/tac5xx2.h +@@ -174,14 +174,13 @@ + #define TAC_SDCA_ENT_SAPU29 0x35 + + /* Control selector definitions */ +-#define TAC_SDCA_MASTER_GAIN 0x0B + #define TAC_SDCA_MASTER_MUTE 0x01 + #define TAC_SDCA_CHANNEL_MUTE 0x01 +-#define TAC_SDCA_CHANNEL_GAIN 0x02 ++#define TAC_SDCA_CHANNEL_VOLUME 0x02 + #define TAC_SDCA_POSTURENUMBER 0x10 + #define TAC_SDCA_REQUESTED_PS 0x01 + #define TAC_SDCA_ACTUAL_PS 0x10 +-#define TAC_SDCA_CHANNEL_VOLUME 0x02 ++#define TAC_SDCA_CHANNEL_GAIN 0x0B + + /* 2. smart mic function */ + #define TAC_FUNCTION_ID_SM 0x2 +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -1310,6 +1310,7 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch + enum sdw_port_prep_ops pre_ops) + { + struct device *dev = &slave->dev; ++ struct tas2783_prv *tas_dev = dev_get_drvdata(dev); + struct sdw_dpn_prop *dpn_prop; + u32 addr; + int ret; +@@ -1321,6 +1322,25 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch + addr = SDW_DPN_PREPARECTRL(prep_ch->num); + switch (pre_ops) { + case SDW_OPS_PORT_PRE_PREP: ++ /* ++ * The Function has to be powered before the port can complete ++ * channel preparation. hw_params() does that when a stream is ++ * set up, but a stream that is only re-prepared - as it is ++ * after the peripheral lost power in S0i3 - does not go ++ * through hw_params() again, and the peripheral is back at its ++ * PS3 reset default. Power it up here, where it is needed. ++ */ ++ scoped_guard(mutex, &tas_dev->pde_lock) ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (ret) { ++ dev_err(dev, "power up failed for port %d, err=%d\n", ++ prep_ch->num, ret); ++ return ret; ++ } ++ + ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask); + if (ret) + dev_err(dev, "prep failed for port %d, err=%d\n", +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -55,8 +55,8 @@ static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { + .add_dapm_routes = true, + }; + +-static int tegra_machine_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *k, int event) ++static int loongson_asoc_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) + { + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); +@@ -72,9 +72,9 @@ static int tegra_machine_event(struct snd_soc_dapm_widget *w, + return 0; + } + +-static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { +- SND_SOC_DAPM_HP("Headphone", tegra_machine_event), +- SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++static const struct snd_soc_dapm_widget loongson_asoc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", loongson_asoc_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", loongson_asoc_machine_event), + }; + + /* Headphones Jack */ +@@ -314,8 +314,8 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + card->num_links = ARRAY_SIZE(loongson_dai_links); + + if (ls_priv->cfg->add_dapm_widgets) { +- card->dapm_widgets = loongson_aosc_dapm_widgets; +- card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ card->dapm_widgets = loongson_asoc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_asoc_dapm_widgets); + } + + snd_soc_card_set_drvdata(card, ls_priv); +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -103,14 +103,14 @@ static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, + int ret; + + if (!link_np) +- return -EINVAL; ++ return -ENOENT; + + struct device_node *cpu_np __free(device_node) = + of_get_child_by_name(link_np, "cpu"); + struct device_node *codec_np __free(device_node) = + of_get_child_by_name(link_np, "codec"); + if (!cpu_np || !codec_np) +- return -EINVAL; ++ return -ENOENT; + + ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); + if (ret) +@@ -172,7 +172,7 @@ int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) +- return ret == -EINVAL ? 0 : ret; ++ return ret == -ENOENT ? 0 : ret; + + return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + } +diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c +--- a/sound/soc/qcom/qdsp6/q6apm-dai.c ++++ b/sound/soc/qcom/qdsp6/q6apm-dai.c +@@ -216,7 +216,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, + { + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; +- struct audioreach_module_config cfg; ++ struct audioreach_module_config cfg = {}; + struct device *dev = component->dev; + struct q6apm_dai_data *pdata; + int ret; +@@ -814,7 +814,7 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; +- struct audioreach_module_config cfg; ++ struct audioreach_module_config cfg = {}; + struct snd_codec *codec = ¶ms->codec; + int dir = stream->direction; + int ret; +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -64,34 +64,34 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -97,34 +97,34 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 +-#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 +-#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 + + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -115,33 +115,42 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai; + struct qcom_snd_tdm_slot_cfg cpu_cfg; + struct qcom_snd_tdm_slot_cfg codec_cfg; +- unsigned int bclk_freq; ++ int bclk_freq; + int ret; + int i; + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) +- return ret == -EINVAL ? 0 : ret; ++ return ret == -ENOENT ? 0 : ret; + + if (!cpu_cfg.slots) + return 0; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- if (ret) ++ if (ret && ret != -ENOTSUPP) + return ret; + ++ if (data->priv->codec_dai_fmt) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ } ++ + ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + if (ret) + return ret; + + bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); +- if (!bclk_freq) ++ if (bclk_freq <= 0) + return -EINVAL; + + if (data->priv->mi2s_bclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, + SND_SOC_CLOCK_IN); +- if (ret) { ++ if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", + __func__, ret); + return ret; +@@ -152,7 +161,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, + SND_SOC_CLOCK_IN); +- if (ret) { ++ if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", + __func__, codec_dai->name, ret); + return ret; +diff --git a/sound/soc/sdw_utils/soc_sdw_rt_amp.c b/sound/soc/sdw_utils/soc_sdw_rt_amp.c +--- a/sound/soc/sdw_utils/soc_sdw_rt_amp.c ++++ b/sound/soc/sdw_utils/soc_sdw_rt_amp.c +@@ -252,11 +252,13 @@ int asoc_sdw_rt_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai + if (ctx->amp_dev1) { + device_remove_software_node(ctx->amp_dev1); + put_device(ctx->amp_dev1); ++ ctx->amp_dev1 = NULL; + } + + if (ctx->amp_dev2) { + device_remove_software_node(ctx->amp_dev2); + put_device(ctx->amp_dev2); ++ ctx->amp_dev2 = NULL; + } + + return 0; +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -81,6 +81,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker */ + .direction = {true, false}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -94,6 +95,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -102,6 +104,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -122,6 +125,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker with IV sense feedback */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -135,6 +139,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -143,6 +148,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -163,6 +169,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker with echo reference feedback */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -176,6 +183,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -184,6 +192,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -203,6 +212,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + { + .direction = {true, false}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tas2883", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -216,6 +226,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tas2883", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -1510,9 +1521,24 @@ int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd) + } + + switch (cmd) { ++ case SNDRV_PCM_TRIGGER_RESUME: ++ /* ++ * The peripherals lose their port configuration when the ++ * controller is power-gated during system suspend, and an ++ * application that restarts the stream with ++ * SNDRV_PCM_IOCTL_RESUME - which platforms advertising ++ * SNDRV_PCM_INFO_RESUME allow - never goes through ++ * .prepare() again. Prepare the stream here so that the ++ * ports are reprogrammed before they are enabled; ++ * sdw_prepare_stream() reapplies the parameters without ++ * recomputing them when the stream is disabled. ++ */ ++ ret = sdw_prepare_stream(sdw_stream); ++ if (ret) ++ break; ++ fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- case SNDRV_PCM_TRIGGER_RESUME: + ret = sdw_enable_stream(sdw_stream); + break; + +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -397,24 +397,8 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; + +- bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); +- bool name_exists_in_fw = false; +- +- if (has_fw_node) +- name_exists_in_fw = device_property_match_string(dev, +- "dma-names", +- name) >= 0; +- +- if (has_fw_node && name_exists_in_fw) +- dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", +- name, PTR_ERR(chan)); +- +- if (has_fw_node && !name_exists_in_fw) +- dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", +- name, PTR_ERR(chan)); +- +- if (!has_fw_node) +- dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ if (device_property_match_string(dev, "dma-names", name) >= 0) ++ dev_warn(dev, "dma-names has '%s' but request failed (%ld)\n", + name, PTR_ERR(chan)); + + pcm->chan[i] = NULL; diff --git a/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..c7079efb6b07513c9d46b4bf7eb48031d20d7c22 GIT binary patch literal 594 zcmV-Y0PAgp!_s#Iw4KIrf+N< zNlHJLSxgp>LXMw9F`7C%%Nuag0HO$?ha*s{QLwP@f#JGV-=ndc{Gifh1#q^oFspUA z%Tru56(63}tKpVELn~ODBe?eaL>n>JypJozGF_;)`CR;jZV-B(hfNN-qbB)uEpSno zev0V|k1<=4jywFLV}VjJ3!xh&T9v7ukoC_CxBa!9&%w+LKLpYILxyyMgHbmNI9wNk zcWz(?e#x1W%38_q9|;Rx@MSa`Qxq@+N0Ri?lb-8mQ^Bivh#qj5H{M zgot}vUHGnJ0eIB>1@VMe)*v&zi!j^r|6zG$2jlWB{;yt8M7-1~iy<>l zPYT)n3WMeon`%^VQzz`aDP3JNQny`(=p`<|J3_UxVH=#9PTYY|Yv>BU4m@z7z7?core.vendor_id == 0x14f11f87 && ++ power_state == AC_PWRST_D0 && ++ codec->core.dev.power.power_state.event == PM_EVENT_RESUME) ++ msleep(SN6140_S3_AFG_D0_DELAY_MS); ++ ++ snd_hda_codec_set_power_to_all(codec, fg, power_state); ++} ++ + static int cx_suspend(struct hda_codec *codec) + { + cx_auto_shutdown(codec); +@@ -1308,6 +1327,7 @@ static const struct hda_codec_ops cx_codec_ops = { + .init = cx_init, + .unsol_event = snd_hda_jack_unsol_event, + .suspend = cx_suspend, ++ .set_power_state = cx_set_power_state, + .check_power_status = snd_hda_gen_check_power_status, + .stream_pm = snd_hda_gen_stream_pm, + }; +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -2379,6 +2379,33 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec, + } + } + ++/* ++ * On the Acer Aspire A515-57G (and possibly other models sharing this ++ * board), if headphones are already inserted into the combo jack before ++ * the codec powers up (cold boot), the impedance-based headset-type ++ * sensing races and misclassifies the jack, driving the wrong output ++ * configuration (audible as missing center-panned/vocal content). A ++ * genuine physical unplug/replug after boot fixes it by forcing a fresh ++ * sense transient. Mirror that here on cold boot only: give the sense ++ * hardware time to settle, then force a fresh classification. ++ */ ++static void alc_fixup_headset_mode_acer_coldboot(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ alc_fixup_headset_mode(codec, fix, action); ++ ++ if (action == HDA_FIXUP_ACT_INIT && ++ !is_s3_resume(codec) && !is_s4_resume(codec) && ++ spec->current_headset_mode != ALC_HEADSET_MODE_UNPLUGGED) { ++ msleep(500); ++ spec->current_headset_mode = ALC_HEADSET_MODE_UNKNOWN; ++ spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN; ++ alc_fixup_headset_mode(codec, fix, action); ++ } ++} ++ + static void alc288_update_headset_jack_cb(struct hda_codec *codec, + struct hda_jack_callback *jack) + { +@@ -4248,6 +4275,7 @@ enum { + ALC282_FIXUP_ACER_DISABLE_LINEOUT, + ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST, + ALC256_FIXUP_ACER_HEADSET_MIC, ++ ALC256_FIXUP_ACER_COLDBOOT, + ALC285_FIXUP_IDEAPAD_S740_COEF, + ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST, + ALC295_FIXUP_ASUS_DACS, +@@ -6311,6 +6339,12 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, ++ [ALC256_FIXUP_ACER_COLDBOOT] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_mode_acer_coldboot, ++ .chained = true, ++ .chain_id = ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, ++ }, + [ALC285_FIXUP_IDEAPAD_S740_COEF] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, +@@ -7148,13 +7182,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), +- SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), +@@ -8100,6 +8135,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3801, "Lenovo Yoga9 14IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x3802, "DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga Pro 9 14IRP8", ALC287_FIXUP_TAS2781_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x380b, "Lenovo Yoga Slim 9 14ILL10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + /* Yoga Pro 9 16IMH9 and Legion 7 16ITHG6 share PCI SSID 17aa:3811 + * with Legion S7 15IMH05; use codec SSID to distinguish them + */ +@@ -8287,6 +8323,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1d05, 0x3034, "TongFang X6KK45xU", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d05, 0x30ba, "TongFang XxAF5xxx", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), ++ SND_PCI_QUIRK(0x1d19, 0x0006, "VAIO VJS131", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), +diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c +--- a/sound/usb/fcp.c ++++ b/sound/usb/fcp.c +@@ -191,6 +191,10 @@ static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode, + const int max_retries = 5; + int err; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + if (!private->urb) + return -ENODEV; + +@@ -1026,6 +1030,10 @@ static int fcp_init(struct usb_mixer_interface *mixer, + struct usb_device *dev = mixer->chip->dev; + int err; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + FCP_USB_REQ_STEP0, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -518,6 +518,19 @@ static const struct usbmix_name_map audient_id14_map[] = { + {} + }; + ++/* ++ * Audient iD24: feature unit 12 ("Speaker Playback Volume") sits in the ++ * monitor-mixer branch and does not apply volume to all of its channels; ++ * when userspace adopts it as the master playback volume, the left main ++ * output stays at 0 dB while the right one is attenuated, producing a ++ * stereo imbalance. Rename it so that it is not picked up as the ++ * stream's master volume control. ++ */ ++static const struct usbmix_name_map audient_id24_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial channel coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -611,6 +624,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, ++ { ++ /* Audient iD24 */ ++ .id = USB_ID(0x2708, 0x000d), ++ .map = audient_id24_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3480,6 +3480,10 @@ static int snd_rme_digiface_write_reg(struct snd_kcontrol *kcontrol, int item, u + struct usb_device *dev = chip->dev; + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + item, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +@@ -3499,6 +3503,10 @@ static int snd_rme_digiface_read_status(struct snd_kcontrol *kcontrol, u32 statu + __le32 buf[4] = {}; + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + RME_DIGIFACE_READ_STATUS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c +--- a/sound/usb/mixer_s1810c.c ++++ b/sound/usb/mixer_s1810c.c +@@ -474,6 +474,10 @@ snd_s1810c_switch_get(struct snd_kcontrol *kctl, + u32 state = 0; + int ret; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &state); + if (ret < 0) +@@ -504,6 +508,10 @@ snd_s1810c_switch_set(struct snd_kcontrol *kctl, + u32 newval = 0; + int ret = 0; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &curval); + if (ret < 0) +diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c +--- a/sound/usb/mixer_scarlett.c ++++ b/sound/usb/mixer_scarlett.c +@@ -707,6 +707,10 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + int idx = snd_usb_ctrl_intf(elem->head.mixer->hostif) | (elem->head.id << 8); + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, +diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c +--- a/sound/usb/mixer_scarlett2.c ++++ b/sound/usb/mixer_scarlett2.c +@@ -2603,9 +2603,9 @@ static int scarlett2_usb_rx(struct usb_device *dev, int interface, + } + + /* Send a proprietary format request to the Scarlett interface */ +-static int scarlett2_usb( +- struct usb_mixer_interface *mixer, u32 cmd, +- void *req_data, u16 req_size, void *resp_data, u16 resp_size) ++static int scarlett2_usb_nopm(struct usb_mixer_interface *mixer, u32 cmd, ++ void *req_data, u16 req_size, ++ void *resp_data, u16 resp_size) + { + struct scarlett2_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; +@@ -2713,6 +2713,18 @@ static int scarlett2_usb( + return err; + } + ++static int scarlett2_usb(struct usb_mixer_interface *mixer, u32 cmd, ++ void *req_data, u16 req_size, ++ void *resp_data, u16 resp_size) ++{ ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ ++ return scarlett2_usb_nopm(mixer, cmd, req_data, req_size, ++ resp_data, resp_size); ++} ++ + /* Send a USB message to get data; result placed in *buf */ + static int scarlett2_usb_get( + struct usb_mixer_interface *mixer, +@@ -3020,9 +3032,21 @@ static int scarlett2_usb_set_config_buf( + /* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */ + static void scarlett2_config_save(struct usb_mixer_interface *mixer) + { +- int err; ++ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); ++ int err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, ++ &req, sizeof(req), NULL, 0); ++ ++ if (err < 0) ++ usb_audio_err(mixer->chip, "config save failed: %d\n", err); ++} ++ ++/* The USB suspend callback must not acquire another PM reference. */ ++static void scarlett2_config_save_nopm(struct usb_mixer_interface *mixer) ++{ ++ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); ++ int err = scarlett2_usb_nopm(mixer, SCARLETT2_USB_DATA_CMD, ++ &req, sizeof(req), NULL, 0); + +- err = scarlett2_usb_activate_config(mixer, SCARLETT2_USB_CONFIG_SAVE); + if (err < 0) + usb_audio_err(mixer->chip, "config save failed: %d\n", err); + } +@@ -8639,7 +8663,7 @@ static void scarlett2_private_suspend(struct usb_mixer_interface *mixer) + struct scarlett2_data *private = mixer->private_data; + + if (cancel_delayed_work_sync(&private->work)) +- scarlett2_config_save(private->mixer); ++ scarlett2_config_save_nopm(private->mixer); + + scarlett2_cleanup_urb(mixer); + } +diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c +--- a/sound/usb/mixer_us16x08.c ++++ b/sound/usb/mixer_us16x08.c +@@ -151,6 +151,9 @@ static const char *const route_names[] = { + static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + unsigned char *buf, int size) + { ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; + + guard(mutex)(&chip->mutex); + snd_usb_ctl_msg(chip->dev, +@@ -165,6 +168,10 @@ static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + */ + static int snd_us16x08_send_urb(struct snd_usb_audio *chip, char *buf, int size) + { ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + return snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + SND_US16X08_URB_REQUEST, SND_US16X08_URB_REQUESTTYPE, + 0, 0, buf, size); diff --git a/pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..cd8ab3fcf70533ffa1c60c9c7d4c382f059da027 GIT binary patch literal 594 zcmV-Y04)TuPPiDb1u1bNSBw8cNN1n4;yT?b|h`;sVN_@kKZEtTw8iUh2GIE zey#iAiY)g3iq^6?CGBe9W}aF4e^|dfo|Km_;joYq2W>yYEn3%x!*|u*(Q5zn+$7KGW*3>-Z*#wr`~y zN2>OZwRpig!eA1B+)bY>B zqC1LyzPF4slIj4pgNPPH5Fxk^m|7<%LDkIh!0}1CV!6b(?e^Hw`x?PWHm^f<5DiCW gjDRJ`h@|v$q8pcHS|G5yJt$f+p5+;W{IeYbw;+!b)&Kwi literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch b/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch new file mode 100644 index 0000000..6a5a07c --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch @@ -0,0 +1,19 @@ +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -845,6 +845,15 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { + }, + .driver_data = (void *)(SOC_SDW_PCH_DMIC), + }, ++ { ++ /* Dell XPS 13 DX13260 (Wildcat Lake), CS42L43 + 2x CS35L63 sidecar amps */ ++ .callback = sof_sdw_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53") ++ }, ++ .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS), ++ }, + {} + }; + diff --git a/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch.sig b/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..1ccaf57e763ea9a077559c29f489db036e0cad01 GIT binary patch literal 594 zcmV-Y00 z2V|IC|1VOttskQo>iIGY*74DR8iMp4Nm0#o+{TE|^ac};L*Dz^{vLpQqcDk#tZH*(x&2-N#NK`wcJ|0yVmw#c(=#Lt2PqwFYI`1>YSp3m}r(UIP z3P8qi9C*T(0#Kx1YdY<25%QHVuG1{hJ6a^j*q>*$lVcN=!DD3=9~PpYOv`c%_J&&a z^sb(?uxFxH!)xS!^0qiG&Cbdp%fip;w1Hjw;0Z+xovs*q0*1$i(7$XIRy*?sJx@IN z^J7zOcY4}r1V8U`J%KNqSwK*Ki&ID;(huAKSHO7Hc#d&)iut2EJQl*EOpUEH9VEU`3>cbt{~iH9{0TfX8L#hZiLXYnCtQP9g)LSD*(zVx7$67M-!1Pp7PC$=k+(2U zK+YXmuzO~>()Iw+|6skLM$S=aLWIVvnQGW$wmg@}0W<*Q>VauBlh0K==yi6j1>=hP zM|ol1cD35|N*Acv#rh^zZ^80)&h70zP2qiGMCjO_E}|SC8|8Okq!j&sl|^tzy?1JW z+yS)E0@CY@z_tcq8U3;s13NDwP&UE?)tW^_P-Cf;+;(swSuTL8uR7UZ{T0faYT2Ih gcne!A-`bh+VRMDNMPOHPLEdUQcomponent; + struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; + unsigned int sampling_rate; + int retval, port; +@@ -957,7 +956,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT766_AIF1) + port = 3; + else if (dai->id == RT766_AIF2) +@@ -965,7 +963,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT766_AIF1) + port = 12; + else if (dai->id == RT766_AIF3) diff --git a/pkgbuilds/linux-omarchy-muqss/0516-rt766-stream-config-type.patch.sig b/pkgbuilds/linux-omarchy-muqss/0516-rt766-stream-config-type.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..2b79ff36494f0dbff48c642e288ce78dde8b1f7c GIT binary patch literal 594 zcmV-Y0q2x^QNM%!LaSk=~=VCdQ=2aRM*&*g(8L)x^onApGL%NYeo6`j<_& zgvfT5o}6-TVk|OB%MiV&L#Z z%-O(S$ALM9xeuqKvX8)W>wk#r4|Cc{!ujEaz$Th)?J;fQM2XTMlDSBzn5A7cx9*jJ!&yks~QqLAMo%jD)vy1c|s`UzMK-FMB82BHS?c>o;k6LjN*0#1tU^l=Hul9=ieTB?86!K!*&%4)^Cnx z_NJN$V97E{iEF?S0cKuNFF`}s3g;i73 gB|P&KH`<05u#7zBYj$>R2F9+%%?Qi!Tp1yh>;e@Yw*UYD literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch b/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch new file mode 100644 index 0000000..47dae22 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch @@ -0,0 +1,39 @@ +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4226,6 +4226,7 @@ enum { + ALC294_FIXUP_ASUS_GU502_VERBS, + ALC294_FIXUP_ASUS_G513_PINS, + ALC285_FIXUP_ASUS_G533Z_PINS, ++ ALC285_FIXUP_ASUS_G733Z_VERBS, + ALC285_FIXUP_HP_GPIO_LED, + ALC285_FIXUP_HP_MUTE_LED, + ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED, +@@ -7129,6 +7130,19 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC287_FIXUP_TXNW2781_I2C, + }, ++ [ALC285_FIXUP_ASUS_G733Z_VERBS] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ /* Enables internal speaker */ ++ {0x17, AC_VERB_SET_CONNECT_SEL, 0x00}, ++ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, ++ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, ++ {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, ++ {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, ++ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, ++ { } ++ } ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7775,6 +7789,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x12a3, "Asus N7691ZM", ALC269_FIXUP_ASUS_N7601ZM), + SND_PCI_QUIRK(0x1043, 0x12af, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x12b4, "ASUS B3405CCA / P3405CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), ++ SND_PCI_QUIRK(0x1043, 0x12bf, "ASUS ROG Strix G733ZW", ALC285_FIXUP_ASUS_G733Z_VERBS), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE), diff --git a/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig b/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..0cfe941d936844dc219afb20251867f06e7a882a GIT binary patch literal 594 zcmV-Y0dBtz z?&8^qjD9qt<~M$)!Ad4vNIC|bkNPwwE>_Ts8c#h|FFDpCY;CX^`>kKJ@6Z;MLMNfO zdmdF5E7fzgM##!pXT56vz7#WC)(L;6dB(@q(R;cn_`E3&)BDL}`AsmtzFa^!oJ*Dh g^;9?91OQs{QEh;MqQgP&>RlL%o{?`YHI;eikd~MqRR910 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch b/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch new file mode 100644 index 0000000..0d8e619 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch @@ -0,0 +1,17 @@ +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -619,6 +619,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFA16-41"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "MDU"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A314-23P"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { diff --git a/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig b/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..62063af0744a884333808ccf63ceba7af7d7ec75 GIT binary patch literal 594 zcmV-Y0Zr~ASM<%#rT2bbZ~7r>GCKnS zIDLyq)0B?m&gGfhC?FhZsDc4+{GYvKSmuK}% zyoEDyhTC8$2-z&ramGBSKfE%(%fy8lfv;k?@SK|VZfrl|rfkJB#lVE*CA9_5ti$z` zyc|RXo58W-yMr>Wp;e-<%BOK!SeS)ndrAajf775SsD@s*?s0^bkM$ddX&It$;=#Q|I z7xDGW=T8b+jqcOcBKC7!J6GWML0E*kzBp{}Ise6@JWX`ZtrRrL5AC$XJZkiPBO3Qq g%1N)X){Tout_urbs[i].submitted); +- usb_kill_urb(&rt->out_urbs[i].instance); ++ usb_kill_urb(rt->out_urbs[i].instance); + } + + rt->stream_state = STREAM_DISABLED; +@@ -215,9 +215,9 @@ static int hiface_pcm_stream_start(struct pcm_runtime *rt) + rt->stream_state = STREAM_STARTING; + for (i = 0; i < PCM_N_URBS; i++) { + memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE); +- usb_anchor_urb(&rt->out_urbs[i].instance, ++ usb_anchor_urb(rt->out_urbs[i].instance, + &rt->out_urbs[i].submitted); +- ret = usb_submit_urb(&rt->out_urbs[i].instance, ++ ret = usb_submit_urb(rt->out_urbs[i].instance, + GFP_ATOMIC); + if (ret) { + hiface_pcm_stream_stop(rt); +@@ -334,7 +334,7 @@ static void hiface_pcm_out_urb_handler(struct urb *usb_urb) + if (do_period_elapsed) + snd_pcm_period_elapsed(sub->instance); + +- ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC); ++ ret = usb_submit_urb(out_urb->instance, GFP_ATOMIC); + if (ret < 0) + goto out_fail; + +@@ -492,16 +492,18 @@ static int hiface_pcm_init_urb(struct pcm_urb *urb, + void (*handler)(struct urb *)) + { + urb->chip = chip; +- usb_init_urb(&urb->instance); ++ urb->instance = usb_alloc_urb(0, GFP_KERNEL); ++ if (!urb->instance) ++ return -ENOMEM; + + urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL); + if (!urb->buffer) + return -ENOMEM; + +- usb_fill_bulk_urb(&urb->instance, chip->dev, ++ usb_fill_bulk_urb(urb->instance, chip->dev, + usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer, + PCM_PACKET_SIZE, handler, urb); +- if (usb_urb_ep_type_check(&urb->instance)) ++ if (usb_urb_ep_type_check(urb->instance)) + return -EINVAL; + init_usb_anchor(&urb->submitted); + +@@ -520,24 +522,26 @@ void hiface_pcm_abort(struct hiface_chip *chip) + } + } + +-static void hiface_pcm_destroy(struct hiface_chip *chip) ++static void hiface_pcm_destroy(struct pcm_runtime *rt) + { +- struct pcm_runtime *rt = chip->pcm; + int i; + +- for (i = 0; i < PCM_N_URBS; i++) +- kfree(rt->out_urbs[i].buffer); ++ if (!rt) ++ return; + +- kfree(chip->pcm); +- chip->pcm = NULL; ++ if (rt->chip) ++ rt->chip->pcm = NULL; ++ ++ for (i = 0; i < PCM_N_URBS; i++) { ++ usb_free_urb(rt->out_urbs[i].instance); ++ kfree(rt->out_urbs[i].buffer); ++ } ++ kfree(rt); + } + + static void hiface_pcm_free(struct snd_pcm *pcm) + { +- struct pcm_runtime *rt = pcm->private_data; +- +- if (rt) +- hiface_pcm_destroy(rt->chip); ++ hiface_pcm_destroy(pcm->private_data); + } + + int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) +@@ -587,8 +591,6 @@ int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) + return 0; + + error: +- for (i = 0; i < PCM_N_URBS; i++) +- kfree(rt->out_urbs[i].buffer); +- kfree(rt); ++ hiface_pcm_destroy(rt); + return ret; + } +diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c +--- a/sound/usb/misc/ua101.c ++++ b/sound/usb/misc/ua101.c +@@ -109,10 +109,10 @@ struct ua101 { + unsigned int buffer_pos; + unsigned int queue_length; + struct ua101_urb { +- struct urb urb; +- struct usb_iso_packet_descriptor iso_frame_desc[1]; ++ struct urb *urb; + struct list_head ready_list; +- } *urbs[MAX_QUEUE_LENGTH]; ++ struct ua101 *ua; ++ } urbs[MAX_QUEUE_LENGTH]; + struct { + unsigned int size; + void *addr; +@@ -167,15 +167,15 @@ static void abort_usb_playback(struct ua101 *ua) + wake_up(&ua->alsa_playback_wait); + } + +-static void playback_urb_complete(struct urb *usb_urb) ++static void playback_urb_complete(struct urb *urb) + { +- struct ua101_urb *urb = (struct ua101_urb *)usb_urb; +- struct ua101 *ua = urb->urb.context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + +- if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ +- urb->urb.status == -ENODEV || /* device removed */ +- urb->urb.status == -ECONNRESET || /* unlinked */ +- urb->urb.status == -ESHUTDOWN)) { /* device disabled */ ++ if (unlikely(urb->status == -ENOENT || /* unlinked */ ++ urb->status == -ENODEV || /* device removed */ ++ urb->status == -ECONNRESET || /* unlinked */ ++ urb->status == -ESHUTDOWN)) { /* device disabled */ + abort_usb_playback(ua); + abort_alsa_playback(ua); + return; +@@ -184,18 +184,19 @@ static void playback_urb_complete(struct urb *usb_urb) + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { + /* append URB to FIFO */ + guard(spinlock_irqsave)(&ua->lock); +- list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); ++ list_add_tail(&ua_urb->ready_list, &ua->ready_playback_urbs); + if (ua->rate_feedback_count > 0) + queue_work(system_highpri_wq, &ua->playback_work); + ua->playback.substream->runtime->delay -= +- urb->urb.iso_frame_desc[0].length / ++ urb->iso_frame_desc[0].length / + ua->playback.frame_bytes; + } + } + + static void first_playback_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + + urb->complete = playback_urb_complete; + playback_urb_complete(urb); +@@ -248,7 +249,8 @@ static void playback_work(struct work_struct *work) + { + struct ua101 *ua = container_of(work, struct ua101, playback_work); + unsigned int frames; +- struct ua101_urb *urb; ++ struct ua101_urb *ua_urb; ++ struct urb *urb; + bool do_period_elapsed = false; + int err; + +@@ -275,23 +277,24 @@ static void playback_work(struct work_struct *work) + ua->rate_feedback_count--; + + /* take URB out of FIFO */ +- urb = list_first_entry(&ua->ready_playback_urbs, +- struct ua101_urb, ready_list); +- list_del(&urb->ready_list); ++ ua_urb = list_first_entry(&ua->ready_playback_urbs, ++ struct ua101_urb, ready_list); ++ list_del(&ua_urb->ready_list); ++ urb = ua_urb->urb; + + /* fill packet with data or silence */ +- urb->urb.iso_frame_desc[0].length = ++ urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + do_period_elapsed |= copy_playback_data(&ua->playback, +- &urb->urb, ++ urb, + frames); + else +- memset(urb->urb.transfer_buffer, 0, +- urb->urb.iso_frame_desc[0].length); ++ memset(urb->transfer_buffer, 0, ++ urb->iso_frame_desc[0].length); + + /* and off you go ... */ +- err = usb_submit_urb(&urb->urb, GFP_ATOMIC); ++ err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + abort_usb_playback(ua); + abort_alsa_playback(ua); +@@ -342,7 +345,8 @@ static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, + + static void capture_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + struct ua101_stream *stream = &ua->capture; + unsigned int frames, write_ptr; + bool do_period_elapsed; +@@ -413,7 +417,8 @@ static void capture_urb_complete(struct urb *urb) + + static void first_capture_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + + urb->complete = capture_urb_complete; + capture_urb_complete(urb); +@@ -427,7 +432,7 @@ static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { +- int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); ++ int err = usb_submit_urb(stream->urbs[i].urb, GFP_KERNEL); + if (err < 0) { + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); +@@ -442,8 +447,8 @@ static void kill_stream_urbs(struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) +- if (stream->urbs[i]) +- usb_kill_urb(&stream->urbs[i]->urb); ++ if (stream->urbs[i].urb) ++ usb_kill_urb(stream->urbs[i].urb); + } + + static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) +@@ -508,7 +513,7 @@ static int start_usb_capture(struct ua101 *ua) + return err; + + clear_bit(CAPTURE_URB_COMPLETED, &ua->states); +- ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; ++ ua->capture.urbs[0].urb->complete = first_capture_urb_complete; + ua->rate_feedback_start = 0; + ua->rate_feedback_count = 0; + +@@ -550,7 +555,7 @@ static int start_usb_playback(struct ua101 *ua) + return err; + + clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); +- ua->playback.urbs[0]->urb.complete = ++ ua->playback.urbs[0].urb->complete = + first_playback_urb_complete; + scoped_guard(spinlock_irq, &ua->lock) { + INIT_LIST_HEAD(&ua->ready_playback_urbs); +@@ -580,7 +585,7 @@ static int start_usb_playback(struct ua101 *ua) + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + } +- urb = &ua->playback.urbs[i]->urb; ++ urb = ua->playback.urbs[i].urb; + urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + memset(urb->transfer_buffer, 0, +@@ -1059,7 +1064,7 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + void (*urb_complete)(struct urb *)) + { + unsigned max_packet_size = stream->max_packet_bytes; +- struct ua101_urb *urb; ++ struct urb *urb; + unsigned int b, u = 0; + + for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { +@@ -1070,23 +1075,24 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + while (size >= max_packet_size) { + if (u >= stream->queue_length) + goto bufsize_error; +- urb = kmalloc_obj(*urb); ++ urb = usb_alloc_urb(1, GFP_KERNEL); + if (!urb) + return -ENOMEM; +- usb_init_urb(&urb->urb); +- urb->urb.dev = ua->dev; +- urb->urb.pipe = stream->usb_pipe; +- urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP; +- urb->urb.transfer_buffer = addr; +- urb->urb.transfer_dma = dma; +- urb->urb.transfer_buffer_length = max_packet_size; +- urb->urb.number_of_packets = 1; +- urb->urb.interval = 1; +- urb->urb.context = ua; +- urb->urb.complete = urb_complete; +- urb->urb.iso_frame_desc[0].offset = 0; +- urb->urb.iso_frame_desc[0].length = max_packet_size; +- stream->urbs[u++] = urb; ++ urb->dev = ua->dev; ++ urb->pipe = stream->usb_pipe; ++ urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ urb->transfer_buffer = addr; ++ urb->transfer_dma = dma; ++ urb->transfer_buffer_length = max_packet_size; ++ urb->number_of_packets = 1; ++ urb->interval = 1; ++ urb->context = &stream->urbs[u]; ++ urb->complete = urb_complete; ++ urb->iso_frame_desc[0].offset = 0; ++ urb->iso_frame_desc[0].length = max_packet_size; ++ stream->urbs[u].ua = ua; ++ stream->urbs[u].urb = urb; ++ u++; + size -= max_packet_size; + addr += max_packet_size; + dma += max_packet_size; +@@ -1104,8 +1110,8 @@ static void free_stream_urbs(struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { +- kfree(stream->urbs[i]); +- stream->urbs[i] = NULL; ++ usb_free_urb(stream->urbs[i].urb); ++ stream->urbs[i].urb = NULL; + } + } + +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -849,6 +849,27 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) + return 0; + } + ++/* ++ * A post configuration device descriptor read is needed to make the CM1A ++ * operational after reenumeration. ++ */ ++static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) ++{ ++ struct usb_device_descriptor *desc __free(kfree) = kmalloc_obj(*desc); ++ int err; ++ ++ if (!desc) ++ return -ENOMEM; ++ ++ err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, sizeof(*desc)); ++ if (err < 0) { ++ dev_err(&dev->dev, "failed to read device descriptor: %d\n", err); ++ return err; ++ } ++ ++ return 0; ++} ++ + /* + * Some sound cards from Native Instruments are in fact compliant to the USB + * audio standard of version 2 and other approved USB standards, even though +@@ -1681,6 +1702,8 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); ++ case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ ++ return snd_usb_cm1a_boot_quirk(dev); + } + + return 0; +@@ -2390,6 +2413,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x1397, 0x050c, /* Behringer Flow 8 */ + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), ++ DEVICE_FLG(0x1397, 0x0510, /* Behringer UV1 */ ++ QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ diff --git a/pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..59a91ae498c03e46b48d688f601457b1bc69f9e5 GIT binary patch literal 594 zcmV-Y0ZPVs3w9xj zla3vAc_>vb&S@~aGur$XhsyPafatXM^_6dod?>{PC6oGJnJGl@lSR-ftbLcU`^)2nj4 zQL!y?po!{XQ*1d_L<2<$ik`AL|tb$$lesf z8USvrc5G_dM2({oCc0u3klzaodOyWsXvi(yJv5*FyI(9fPF$`U zR~3hvVuE)gu1=4zY^zaFh+>h|Qgqo+ZrE5xuW#N15XR`*5aLeQCmLJyf@gq g-7XvrKh7J`Io|Od5wq&KEyv})oihuNindex = idx; + hlink->bus = bus; + hlink->ml_addr = bus->mlcap + AZX_ML_BASE + +diff --git a/sound/hda/core/ext/stream.c b/sound/hda/core/ext/stream.c +--- a/sound/hda/core/ext/stream.c ++++ b/sound/hda/core/ext/stream.c +@@ -102,8 +102,10 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + + for (i = 0; i < num_stream; i++) { + struct hdac_ext_stream *hext_stream = kzalloc_obj(*hext_stream); +- if (!hext_stream) ++ if (!hext_stream) { ++ snd_hdac_ext_stream_free_all(bus); + return -ENOMEM; ++ } + tag = ++stream_tag; + snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag); + idx++; +@@ -111,7 +113,6 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + } + + return 0; +- + } + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -388,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "RB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN17-41"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +@@ -899,6 +906,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "TM2423"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "HP 255R 15.6 inch G10 Notebook PC"), ++ } ++ }, + {} + }; + +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,17 +14,14 @@ + * for ST-Ericsson. + */ + +-#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include +-#include + #include + #include + #include +@@ -54,32 +51,9 @@ + /* Macrocell register definitions */ + #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ + +-/* Nr of FIR/IIR-coeff banks in ANC-block */ +-#define AB8500_NR_OF_ANC_COEFF_BANKS 2 +- +-/* Minimum duration to keep ANC IIR Init bit high or +-low before proceeding with the configuration sequence */ +-#define AB8500_ANC_SM_DELAY 2000 +- +-/* Sidetone states */ +-static const char * const enum_sid_state[] = { +- "Unconfigured", +- "Apply FIR", +- "FIR is configured", +-}; +-enum sid_state { +- SID_UNCONFIGURED = 0, +- SID_APPLY_FIR = 1, +- SID_FIR_CONFIGURED = 2, +-}; +- + /* Private data for AB8500 device-driver */ + struct ab8500_codec_drvdata { + struct regmap *regmap; +- struct mutex ctrl_lock; +- +- /* Sidetone */ +- enum sid_state sid_status; + }; + + static inline const char *amic_micbias_str(enum amic_micbias micbias) +@@ -642,9 +616,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + NULL, 0), + /* Acoustical Noise Cancellation path */ + +- SND_SOC_DAPM_INPUT("ANC Configure Input"), +- SND_SOC_DAPM_OUTPUT("ANC Configure Output"), +- + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), +@@ -687,10 +658,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + +- /* ANC Configure */ +- {"ANC Configure Input", NULL, "Main Supply"}, +- {"ANC Configure Output", NULL, "ANC Configure Input"}, +- + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, +@@ -972,75 +939,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, + }; + +-/* +- * Control-events +- */ +- +-static int sid_status_control_get(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) +-{ +- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); +- +- guard(mutex)(&drvdata->ctrl_lock); +- ucontrol->value.enumerated.item[0] = drvdata->sid_status; +- +- return 0; +-} +- +-/* Write sidetone FIR-coefficients configuration sequence */ +-static int sid_status_control_put(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) +-{ +- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); +- unsigned int param, sidconf; +- int status = 1; +- +- dev_dbg(component->dev, "%s: Enter\n", __func__); +- +- if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { +- dev_err(component->dev, +- "%s: ERROR: This control supports '%s' only!\n", +- __func__, enum_sid_state[SID_APPLY_FIR]); +- return -EIO; +- } +- +- guard(mutex)(&drvdata->ctrl_lock); +- +- sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); +- if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { +- if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { +- dev_err(component->dev, "%s: Sidetone busy while off!\n", +- __func__); +- status = -EPERM; +- } else { +- status = -EBUSY; +- } +- dev_dbg(component->dev, "%s: Exit\n", __func__); +- return status; +- } +- +- snd_soc_component_write(component, AB8500_SIDFIRADR, 0); +- +- for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { +- snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); +- snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); +- } +- +- snd_soc_component_update_bits(component, AB8500_SIDFIRADR, +- BIT(AB8500_SIDFIRADR_FIRSIDSET), +- BIT(AB8500_SIDFIRADR_FIRSIDSET)); +- snd_soc_component_update_bits(component, AB8500_SIDFIRADR, +- BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); +- +- drvdata->sid_status = SID_FIR_CONFIGURED; +- +- dev_dbg(component->dev, "%s: Exit\n", __func__); +- +- return status; +-} +- + /* + * Controls - Non-DAPM ASoC + */ +@@ -1324,9 +1222,6 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); + +-/* Sidetone */ +-static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); +- + /* ANC */ + + static struct snd_kcontrol_new ab8500_ctrls[] = { +@@ -1617,8 +1512,6 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), + + /* Sidetone */ +- SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, +- sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), + }; +@@ -2148,10 +2041,8 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + + static int ab8500_codec_probe(struct snd_soc_component *component) + { +- struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + int status; + +@@ -2184,10 +2075,6 @@ static int ab8500_codec_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + +- snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); +- +- mutex_init(&drvdata->ctrl_lock); +- + return status; + } + +@@ -2216,7 +2103,6 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; +- drvdata->sid_status = SID_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -386,11 +386,8 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); + +- if (!cs35l56->base.init_done) +- return 0; +- +- /* runtime_resume unmasks the interrupt */ +- cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->sdw_attached) ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -18,9 +18,11 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include + #include + #include +@@ -37,6 +39,13 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + ++/* ++ * snd_soc_register_component() can call component_probe() on all instances ++ * in a card, so deferred registration must be protected across all instances. ++ */ ++static DEFINE_MUTEX(cs35l56_component_register_lock); ++static bool cs35l56_shutting_down; ++ + void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* +@@ -1957,6 +1966,11 @@ static void cs35l56_component_register_work(struct work_struct *work) + component_register_work); + int ret; + ++ guard(mutex)(&cs35l56_component_register_lock); ++ ++ if (cs35l56_shutting_down) ++ return; ++ + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { +@@ -2217,6 +2231,37 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { + }; + #endif + ++static int cs35l56_reboot_notify(struct notifier_block *nb, ++ unsigned long action, void *data) ++{ ++ guard(mutex)(&cs35l56_component_register_lock); ++ cs35l56_shutting_down = true; ++ ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block cs35l56_reboot_notifier = { ++ .notifier_call = cs35l56_reboot_notify, ++}; ++ ++static int __init cs35l56_modinit(void) ++{ ++ /* ++ * Use reboot notifier to prevent race between shutdown and ++ * snd_soc_register_component(). Driver shutdown() callback would ++ * run too late, after device_shutdown() is already walking the ++ * device list that component registration can modify. ++ */ ++ return register_reboot_notifier(&cs35l56_reboot_notifier); ++} ++module_init(cs35l56_modinit); ++ ++static void __exit cs35l56_modexit(void) ++{ ++ unregister_reboot_notifier(&cs35l56_reboot_notifier); ++} ++module_exit(cs35l56_modexit); ++ + MODULE_DESCRIPTION("ASoC CS35L56 driver"); + MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); + MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -32,6 +32,7 @@ struct sdw_slave; + struct cs35l56_private { + struct wm_adsp dsp; /* must be first member */ + struct cs35l56_base base; ++ struct work_struct component_register_work; + struct work_struct dsp_work; + struct workqueue_struct *dsp_wq; + struct snd_soc_component *component; +@@ -41,6 +42,7 @@ struct cs35l56_private { + const char *fallback_fw_suffix; + bool soft_resetting; + bool sdw_attached; ++ bool component_registered; + struct completion init_completion; + + int speaker_id; +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -26,6 +26,7 @@ struct es8326_priv { + struct snd_soc_component *component; + struct delayed_work jack_detect_work; + struct delayed_work button_press_work; ++ struct delayed_work capture_pop_work; + struct snd_soc_jack *jack; + int irq; + /* The lock protects the situation that an irq is generated +@@ -628,6 +629,7 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8326->regmap, ES8326_HP_DRIVER_REF, + 0x30, 0x00); + } else { ++ cancel_delayed_work_sync(&es8326->capture_pop_work); + regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, + 0x0F, 0x0F); + if (es8326->version > ES8326_VERSION_B) { +@@ -666,8 +668,9 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x70); + regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x00); + } +- regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, +- 0x0F, 0x00); ++ ++ queue_delayed_work(system_dfl_wq, &es8326->capture_pop_work, ++ msecs_to_jiffies(40)); + } + } + return 0; +@@ -773,6 +776,15 @@ static void es8326_disable_micbias(struct snd_soc_component *component) + snd_soc_dapm_mutex_unlock(dapm); + } + ++static void es8326_capture_pop_handler(struct work_struct *work) ++{ ++ struct es8326_priv *es8326 = ++ container_of(work, struct es8326_priv, capture_pop_work.work); ++ ++ regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, ++ 0x0F, 0x00); ++} ++ + /* + * For button detection, set the following in soundcard + * snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); +@@ -1140,6 +1152,7 @@ static int es8326_suspend(struct snd_soc_component *component) + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&es8326->jack_detect_work); ++ cancel_delayed_work_sync(&es8326->capture_pop_work); + es8326_disable_micbias(component); + es8326->calibrated = false; + regmap_write(es8326->regmap, ES8326_CLK_MUX, 0x2d); +@@ -1291,6 +1304,8 @@ static int es8326_i2c_probe(struct i2c_client *i2c) + es8326_jack_detect_handler); + INIT_DELAYED_WORK(&es8326->button_press_work, + es8326_jack_button_handler); ++ INIT_DELAYED_WORK(&es8326->capture_pop_work, ++ es8326_capture_pop_handler); + /* ES8316 is level-based while ES8326 is edge-based */ + ret = devm_request_threaded_irq(&i2c->dev, es8326->irq, NULL, es8326_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -812,6 +812,23 @@ static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static void es8389_standby(struct snd_soc_component *component) ++{ ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); ++ regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); ++ usleep_range(70000, 72000); ++ regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); ++ regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); ++ regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); ++ regmap_write(es8389->regmap, ES8389_RESET, 0x3E); ++ regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); ++ usleep_range(8000, 8500); ++ regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); ++} ++ + static int es8389_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) + { +@@ -834,18 +851,7 @@ static int es8389_set_bias_level(struct snd_soc_component *component, + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); +- regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); +- usleep_range(70000, 72000); +- regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); +- regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); +- regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); +- regmap_write(es8389->regmap, ES8389_RESET, 0x3E); +- regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); +- usleep_range(8000, 8500); +- regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); +- ++ es8389_standby(component); + clk_disable_unprepare(es8389->mclk); + break; + case SND_SOC_BIAS_OFF: +@@ -1015,7 +1021,7 @@ static int es8389_suspend(struct snd_soc_component *component) + { + struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); + +- es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); ++ es8389_standby(component); + regcache_cache_only(es8389->regmap, true); + regcache_mark_dirty(es8389->regmap); + +@@ -1084,7 +1090,7 @@ static int es8389_probe(struct snd_soc_component *component) + + es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); +- es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); ++ es8389_standby(component); + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include "rt1318-sdw.h" + +@@ -564,11 +565,10 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct rt1318_sdw_priv *rt1318 = + snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels, ch_mask; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -582,23 +582,13 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + /* port 1 for playback */ +- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port = 1; +- } else { +- direction = SDW_DATA_DIR_TX; ++ else + port = 2; +- } + +- num_channels = params_channels(params); +- ch_mask = (1 << num_channels) - 1; +- +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = num_channels; +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- port_config.ch_mask = ch_mask; ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt1318->sdw_slave, &stream_config, +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -3246,7 +3246,7 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct rt1320_sdw_priv *rt1320 = + snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; + struct sdw_port_config dmic_port_config[2]; + struct sdw_stream_runtime *sdw_stream; +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include "rt712-sdca.h" + +@@ -1449,11 +1450,10 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); +@@ -1471,7 +1471,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT712_AIF1) + port = 1; + else if (dai->id == RT712_AIF2) +@@ -1479,7 +1478,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT712_AIF1) + port = 4; + else if (dai->id == RT712_AIF3) +@@ -1488,13 +1486,8 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt712->slave, &stream_config, +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + #include + + #include "rt721-sdca.h" +@@ -206,6 +207,7 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); ++ regmap_write(rt721->regmap, 0x2f5d, 0x1); + } + + static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) +@@ -1268,11 +1270,10 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt721_sdca_priv *rt721 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -1291,7 +1292,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + * RT721_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT721_AIF1) + port = 1; + else if (dai->id == RT721_AIF2) +@@ -1299,7 +1299,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT721_AIF1) + port = 2; + else if (dai->id == RT721_AIF3) +@@ -1307,13 +1306,9 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; + +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt721->slave, &stream_config, +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + #include + + #include "rt722-sdca.h" +@@ -1442,11 +1443,10 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -1465,7 +1465,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + * RT722_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT722_AIF1) + port = 1; + else if (dai->id == RT722_AIF2) +@@ -1473,7 +1472,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT722_AIF1) + port = 2; + else if (dai->id == RT722_AIF3) +@@ -1481,13 +1479,9 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; + +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt722->slave, &stream_config, +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -1001,6 +1001,31 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, + &stream_config, &port_config); ++ ++ /* ++ * The two mono amps each render one channel of the stereo stream: ++ * snd_sdw_params_to_config() hands every codec the full mask for ++ * playback, which leaves the pair in mirror mode and one channel ++ * unreproduced. Claim a single channel instead, keyed off the ++ * machine-assigned component prefix rather than the SoundWire ++ * address, which is board-specific: soc_sdw_ti_amp.c names the amps ++ * tas2783-1..4. ++ * ++ * Which side an amp then renders does not follow from the bit that ++ * is set - sdw_compute_slave_ports() advances the payload offset by ++ * the popcount of ch_mask and never looks at which bit it is - but ++ * from the amp's position in the codec order of the DAI link, which ++ * on these boards matches the prefix numbering. ++ */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && ++ params_channels(params) == 2 && component->name_prefix) { ++ const char *idx_str = strrchr(component->name_prefix, '-'); ++ unsigned long idx; ++ ++ if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) ++ port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); ++ } ++ + /* port 1 for playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port_config.num = 1; +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -15,6 +15,22 @@ + #include "../../../codecs/hda.h" + #include "../utils.h" + ++static int avs_link_startup(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ const struct snd_soc_pcm_stream *stream_info; ++ struct snd_soc_dai *codec_dai; ++ ++ codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ stream_info = snd_soc_dai_get_pcm_stream(codec_dai, substream->stream); ++ ++ return snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, stream_info->sig_bits); ++} ++ ++static const struct snd_soc_ops avs_link_ops = { ++ .startup = avs_link_startup, ++}; ++ + static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, + struct snd_soc_dai_link **links) + { +@@ -43,6 +59,7 @@ static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int + dl[i].platforms = platform; + dl[i].num_platforms = 1; + dl[i].ignore_pmdown_time = 1; ++ dl[i].ops = &avs_link_ops; + + dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -92,16 +92,28 @@ static int avs_hdac_bus_init_streams(struct hdac_bus *bus) + { + unsigned int cp_streams, pb_streams; + unsigned int gcap; ++ int ret; + + gcap = snd_hdac_chip_readw(bus, GCAP); + cp_streams = (gcap >> 8) & 0x0F; + pb_streams = (gcap >> 12) & 0x0F; + bus->num_streams = cp_streams + pb_streams; + +- snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); +- snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); ++ ret = snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); ++ if (ret) ++ return ret; ++ ret = snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); ++ if (ret) ++ goto err; + +- return snd_hdac_bus_alloc_stream_pages(bus); ++ ret = snd_hdac_bus_alloc_stream_pages(bus); ++ if (ret) ++ goto err; ++ ++ return 0; ++err: ++ snd_hdac_ext_stream_free_all(bus); ++ return ret; + } + + static bool avs_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) +@@ -383,6 +395,18 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct + struct device *dev = &pci->dev; + int ret; + ++ ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); ++ if (!ipc) ++ return -ENOMEM; ++ ++ adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); ++ if (!adev->modcfg_buf) ++ return -ENOMEM; ++ ++ ret = avs_ipc_init(ipc, dev); ++ if (ret < 0) ++ return ret; ++ + ret = snd_hdac_ext_bus_init(&bus->core, dev, NULL, &soc_hda_ext_bus_ops); + if (ret < 0) + return ret; +@@ -394,17 +418,6 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct + bus->mixer_assigned = -1; + mutex_init(&bus->prepare_mutex); + +- ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); +- if (!ipc) +- return -ENOMEM; +- ret = avs_ipc_init(ipc, dev); +- if (ret < 0) +- return ret; +- +- adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); +- if (!adev->modcfg_buf) +- return -ENOMEM; +- + adev->dev = dev; + adev->spec = (const struct avs_spec *)id->driver_data; + adev->ipc = ipc; +@@ -456,13 +469,14 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) + + ret = pcim_request_all_regions(pci, "AVS HDAudio"); + if (ret < 0) +- return ret; ++ goto err_request_regions; + + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (!bus->remap_addr) { + dev_err(bus->dev, "ioremap error\n"); +- return -ENXIO; ++ ret = -ENXIO; ++ goto err_request_regions; + } + + adev->dsp_ba = pci_ioremap_bar(pci, 4); +@@ -526,6 +540,8 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) + iounmap(adev->dsp_ba); + err_remap_bar4: + iounmap(bus->remap_addr); ++err_request_regions: ++ snd_hdac_ext_bus_exit(bus); + return ret; + } + +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,20 @@ static int strace_open(struct inode *inode, struct file *file) + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + +- if (kfifo_initialized(&adev->trace_fifo)) +- return -EBUSY; ++ if (kfifo_initialized(&adev->trace_fifo)) { ++ ret = -EBUSY; ++ goto err; ++ } + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) +- return ret; ++ goto err; + + file->private_data = adev; + return 0; ++err: ++ module_put(adev->dev->driver->owner); ++ return ret; + } + + static int strace_release(struct inode *inode, struct file *file) +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -172,7 +172,7 @@ static void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg + + /* Avoid deadlock as the exception may be the response to SET_D0IX. */ + if (current_work() != &ipc->d0ix_work.work) +- cancel_delayed_work_sync(&ipc->d0ix_work); ++ cancel_delayed_work(&ipc->d0ix_work); + ipc->in_d0ix = false; + /* Re-enabled on recovery completion. */ + pm_runtime_disable(adev->dev); +@@ -395,11 +395,11 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + struct avs_ipc *ipc = adev->ipc; + int ret; + ++ guard(mutex)(&ipc->msg_mutex); ++ + if (!ipc->ready) + return -EPERM; + +- guard(mutex)(&ipc->msg_mutex); +- + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); + avs_dsp_send_tx(adev, request, true); +diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig +--- a/sound/soc/intel/boards/Kconfig ++++ b/sound/soc/intel/boards/Kconfig +@@ -533,12 +533,14 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH + select SND_SOC_CS35L56_SPI + select SND_SOC_CS35L56_SDW + select SND_SOC_ES9356 ++ imply SND_SOC_TAC5XX2_SDW + select SND_SOC_DMIC + select SND_SOC_INTEL_HDA_DSP_COMMON + imply SND_SOC_SDW_MOCKUP + help + Add support for Intel SoundWire-based platforms connected to +- MAX98373, RT700, RT711, RT1308 and RT715 ++ MAX98373, RT700, RT711, RT1308, RT715, TAC5XX2_SDW family (including ++ TAC5572, TAC5682). + If unsure select "N". + + endif +diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c +--- a/sound/soc/intel/boards/sof_rt5682.c ++++ b/sound/soc/intel/boards/sof_rt5682.c +@@ -908,6 +908,14 @@ static const struct platform_device_id board_ids[] = { + SOF_SSP_PORT_BT_OFFLOAD(2) | + SOF_BT_OFFLOAD_PRESENT), + }, ++ { ++ .name = "nvl_rt5682_def", ++ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | ++ SOF_SSP_PORT_CODEC(0) | ++ SOF_SSP_PORT_AMP(1) | ++ SOF_SSP_PORT_BT_OFFLOAD(2) | ++ SOF_BT_OFFLOAD_PRESENT), ++ }, + { + .name = "ptl_rt5682_c1_h02", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | +diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +@@ -51,10 +51,10 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { + }, + { + .comp_ids = &nvl_rt5682_rt5682s_hp, +- .drv_name = "sof_rt5682", +- .sof_tplg_filename = "sof-nvl-rt5682", /* the tplg suffix is added at run time */ +- .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | +- SND_SOC_ACPI_TPLG_INTEL_SSP_MSB, ++ .drv_name = "nvl_rt5682_def", ++ .sof_tplg_filename = "sof-nvl", /* the tplg suffix is added at run time */ ++ .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_AMP_NAME | ++ SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME, + }, + /* place amp/hdmi-in only boards in the end of table */ + { diff --git a/pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..1e9594c2f7c5b11bbe48e2af607a1a84770a18e5 GIT binary patch literal 594 zcmV-Y0obZ z&hxDKyl8dcxCST^kX8L`wYH33?@A^VOSbU6`Iq!DQb*YWQAqNP-l}`VGjmu`6hQX_ zmJMzUe{gjwS^v8@)$xQeo1P5IHJARzR^U^OFWg}6-qmO(la^rOObZAWzF*A4I5cUn z?+z~)$|6ukmMI8dsEcpM`+ZNKGm!xC%J08+rj>=>VvD~O<&${6%)br+l0?1w(prku z8_p=}4OSc0NV^^uCIpfm0XTn|3+HOsLR$1#xJKd`MTBoSs;O{9c0Sh{p(Pd_kd@F& zzc(Bvsg1>)*zex_Blid! z5poAXDFSOzC$mi0>SAd%LQl<`U_rth5S@yz*}QnB4#j@L@RB9(&S7TmLE~f~oIM{A z2V&6D#c?yYEpv%Id;j=Y!-5@e;{x8^%{rc5oh#PWpDJ@u{{BYpXBl%gu`F(U0(KoRd>Hq)$ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch b/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch new file mode 100644 index 0000000..39c0ebf --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch @@ -0,0 +1,135 @@ +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -364,7 +364,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, + sound_debugfs_root); + #endif + #ifdef CONFIG_SND_CTL_DEBUG +- card->value_buf = kmalloc(sizeof(*card->value_buf), GFP_KERNEL); ++ card->value_buf = kmalloc_obj(*card->value_buf); + if (!card->value_buf) + return -ENOMEM; + #endif +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4304,6 +4304,7 @@ enum { + ALC287_FIXUP_LEGION_16ACHG6, + ALC287_FIXUP_CS35L41_I2C_2, + ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED, ++ ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, + ALC287_FIXUP_CS35L41_I2C_4, + ALC245_FIXUP_CS35L41_SPI_1, + ALC245_FIXUP_CS35L41_SPI_2, +@@ -6615,6 +6616,12 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC285_FIXUP_HP_MUTE_LED, + }, ++ [ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = cs35l41_fixup_i2c_two, ++ .chained = true, ++ .chain_id = ALC245_FIXUP_HP_X360_MUTE_LEDS, ++ }, + [ALC287_FIXUP_CS35L41_I2C_4] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_four, +@@ -7372,6 +7379,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC), + SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), ++ SND_PCI_QUIRK(0x103c, 0x8259, "HP OMEN 15-ax202nf", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE), +@@ -7582,6 +7590,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), +@@ -7674,7 +7683,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8dcd, "HP Victus 15-fa2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8d9b, "HP 17 Turbine OmniBook 7 UMA", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -449,7 +449,9 @@ static void push_back_to_ready_list(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx) + { + guard(spinlock_irqsave)(&ep->lock); +- list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); ++ /* ctx may still be linked: a stale completion racing a stop/restart. */ ++ if (list_empty(&ctx->ready_list)) ++ list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + } + + /* +@@ -1037,6 +1039,7 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) + */ + static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) + { ++ struct snd_urb_ctx *ctx, *n; + unsigned int i; + + if (!force && atomic_read(&ep->running)) +@@ -1046,7 +1049,9 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) + return 0; + + scoped_guard(spinlock_irqsave, &ep->lock) { +- INIT_LIST_HEAD(&ep->ready_playback_urbs); ++ /* Unlink each ctx; INIT_LIST_HEAD() alone would leave them looking linked. */ ++ list_for_each_entry_safe(ctx, n, &ep->ready_playback_urbs, ready_list) ++ list_del_init(&ctx->ready_list); + ep->next_packet_head = 0; + ep->next_packet_queued = 0; + } +diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c +--- a/sound/usb/implicit.c ++++ b/sound/usb/implicit.c +@@ -76,6 +76,7 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { + + /* Implicit feedback quirk table for capture: only FIXED type */ + static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { ++ IMPLICIT_FB_FIXED_DEV(0x1397, 0x0004, 0x01, 1), /* Behringer FCA1616 */ + {} /* terminator */ + }; + +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -531,6 +531,15 @@ static const struct usbmix_name_map audient_id24_map[] = { + {} + }; + ++/* ++ * The GC553Pro returns no data for GET_CUR on its advertised mute control. ++ * SET_CUR succeeds but does not mute capture, so skip the control entirely. ++ */ ++static const struct usbmix_name_map avermedia_gc553pro_map[] = { ++ { 3, NULL, UAC_FU_MUTE }, ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -577,6 +586,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x0763, 0x2031), + .selector_map = c400_selectors, + }, ++ { ++ .id = USB_ID(0x07ca, 0x1553), ++ .map = avermedia_gc553pro_map, ++ }, + { + .id = USB_ID(0x08bb, 0x2702), + .map = linex_map, diff --git a/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch.sig b/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..ac1447bfa2b3d8dc5f0f34ad6bcd7fbca9775e2f GIT binary patch literal 594 zcmV-Y0E`~Pus zhpD*OWSsN69{PCQ0pMI44|`>%^7bMIf6h?4Y)$X_&usstjx(_u6RqvhM-juduwe%R za~Uv2L1@_$Cigns`3GiZl>woEEHp->U=iwb*Q51y$uOALeVBXt0X=*Uv%zz)ExK|( zWNzm_jP&9Ok3n(q3sIo{RYdU!?o&hk|Y zVMijT6DP+Sg(A1_Lxn0NJ-3?uT%)mAS!7FBOAWnEf=sF!3;IeAZIK4ZLbClc8&FyW z)H`u$7BWCMldksOLJ+Yn&l%!U((+loX2AEjra(!93%r%5b=+?b%bN8tn<-H1v?_LY zRv7{BeYi2okLt#e-QGYyEYj(cX8b}}tnUT%_^wUDq>k*xPcQ}nmhRu3VQs}Ktf!~Q z1=A%F1i_$uPwy(rZq&W6j3EnY$}?SlsI>k{N$4&P^>c zC3;5^)7z2gIdwx-GL_czspuebi{^i_y`t0xv=r+su{VApqsVI!od3aax1$Dl-Qw!} zPpWv;)xmn6>-j+TIQHRhqImKD0*_z>=MLo6F&MerkIS}5^U_6IOFo4LoXo>mujqun gBa0I#mAnJJIIgrih@4Mk_UD=C$#cKW%89(RFAhl{2><{9 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch b/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch new file mode 100644 index 0000000..843a983 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch @@ -0,0 +1,549 @@ +diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h +--- a/include/sound/soc_sdw_utils.h ++++ b/include/sound/soc_sdw_utils.h +@@ -250,8 +250,6 @@ int asoc_sdw_cs_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback); +-int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, +- struct snd_soc_dai *dai); + int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix); + + /* MAXIM codec support */ +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -314,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + if (ret < 0) + return ret; + +@@ -390,7 +390,7 @@ static int mc_probe(struct platform_device *pdev) + ctx->private = amd_ctx; + card = &ctx->card; + card->dev = &pdev->dev; +- card->name = "amd-soundwire"; ++ card->name = "amd-sdw"; + card->owner = THIS_MODULE; + card->late_probe = asoc_sdw_card_late_probe; + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -524,13 +524,13 @@ config SND_SOC_ADAU1977 + tristate + + config SND_SOC_ADAU1977_SPI +- tristate ++ tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - SPI" + depends on SPI_MASTER + select SND_SOC_ADAU1977 + select REGMAP_SPI + + config SND_SOC_ADAU1977_I2C +- tristate ++ tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - I2C" + depends on I2C + select SND_SOC_ADAU1977 + select REGMAP_I2C +diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c +--- a/sound/soc/codecs/adau1977-i2c.c ++++ b/sound/soc/codecs/adau1977-i2c.c +@@ -34,9 +34,18 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { + }; + MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); + ++static const struct of_device_id adau1977_i2c_of_match[] = { ++ { .compatible = "adi,adau1977" }, ++ { .compatible = "adi,adau1978" }, ++ { .compatible = "adi,adau1979" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, adau1977_i2c_of_match); ++ + static struct i2c_driver adau1977_i2c_driver = { + .driver = { + .name = "adau1977", ++ .of_match_table = adau1977_i2c_of_match, + }, + .probe = adau1977_i2c_probe, + .id_table = adau1977_i2c_ids, +diff --git a/sound/soc/codecs/adau1977-spi.c b/sound/soc/codecs/adau1977-spi.c +--- a/sound/soc/codecs/adau1977-spi.c ++++ b/sound/soc/codecs/adau1977-spi.c +@@ -53,18 +53,18 @@ static const struct spi_device_id adau1977_spi_ids[] = { + }; + MODULE_DEVICE_TABLE(spi, adau1977_spi_ids); + +-static const struct of_device_id adau1977_spi_of_match[] __maybe_unused = { +- { .compatible = "adi,adau1977" }, +- { .compatible = "adi,adau1978" }, +- { .compatible = "adi,adau1979" }, +- { }, ++static const struct of_device_id adau1977_spi_of_match[] = { ++ { .compatible = "adi,adau1977" }, ++ { .compatible = "adi,adau1978" }, ++ { .compatible = "adi,adau1979" }, ++ { }, + }; + MODULE_DEVICE_TABLE(of, adau1977_spi_of_match); + + static struct spi_driver adau1977_spi_driver = { + .driver = { + .name = "adau1977", +- .of_match_table = of_match_ptr(adau1977_spi_of_match), ++ .of_match_table = adau1977_spi_of_match, + }, + .probe = adau1977_spi_probe, + .id_table = adau1977_spi_ids, +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -18,12 +18,16 @@ + static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0x201a ... 0x201f: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2230 ... 0x2232: + case 0x2f01 ... 0x2f0a: + case 0x2f35 ... 0x2f36: ++ case 0x2f3a: ++ case 0x2f3d: ++ case 0x2f41: + case 0x2f50: + case 0x2f54: + case 0x2f58 ... 0x2f5d: +@@ -48,6 +52,7 @@ static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) + static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0x201b: + case 0x201c: + case 0x201d: +@@ -56,6 +61,9 @@ static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) + case 0x2230: + case 0x2f01: + case 0x2f35: ++ case 0x2f3a: ++ case 0x2f3d: ++ case 0x2f41: + case 0x320c: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT_GE49, RT712_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUNC_NUM_HID, RT712_SDCA_ENT_HID01, RT712_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -73,14 +73,57 @@ static int rt712_sdca_index_update_bits(struct rt712_sdca_priv *rt712, + return rt712_sdca_index_write(rt712, nid, reg, tmp); + } + ++static void rt712_sdca_clk_patch(struct rt712_sdca_priv *rt712) ++{ ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); ++ usleep_range(1000, 1100); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0017); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0001); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0081); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0080); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0010); ++ usleep_range(1000, 1100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); ++} ++ ++static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) ++{ ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0x0800, ++ 0x0000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0xf000, ++ 0x0000); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x0c, 0xc000, ++ 0xc000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x00, 0xc000, ++ 0xc000); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); ++ usleep_range(1000, 1100); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++} ++ + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) + { + unsigned int val, loop_rc = 0, loop_dc = 0; + struct device *dev; + struct regmap *regmap = rt712->regmap; ++ unsigned int clk_base; + int chk_cnt = 100; + int ret = 0; + ++ regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); ++ + mutex_lock(&rt712->calibrate_mutex); + dev = regmap_get_device(regmap); + +@@ -109,8 +152,35 @@ static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) + if (ret < 0) + goto _cali_fail_; + } +- if (loop_dc == chk_cnt) +- dev_err(dev, "%s, calibration time-out!\n", __func__); ++ ++ if (loop_dc == chk_cnt) { ++ if (clk_base == RT712_CLK_FREQ_24_576MHZ) { ++ rt712_sdca_clk_patch(rt712); ++ rt712_sdca_clk_patch2(rt712); ++ } ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_FSM_CTL, 0x4100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, 0x7883); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, 0xf893); ++ rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, &val); ++ ++ for (loop_dc = 0; loop_dc < chk_cnt && ++ (val & RT712_DAC_DC_CALI_TRIGGER); loop_dc++) { ++ usleep_range(10000, 11000); ++ ret = rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, &val); ++ ++ if (ret < 0) ++ goto _cali_fail_; ++ } ++ ++ if (loop_dc == chk_cnt) ++ dev_err(dev, "%s, calibration time-out!\n", __func__); ++ else ++ dev_dbg(dev, "%s, calibration success!\n", __func__); ++ } + + if (loop_dc == chk_cnt || loop_rc == chk_cnt) + ret = -ETIMEDOUT; +@@ -1759,9 +1829,13 @@ static void rt712_sdca_va_io_init(struct rt712_sdca_priv *rt712) + + static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + { +- int ret = 0; + unsigned int jack_func_status, mic_func_status, amp_func_status; + struct device *dev = &rt712->slave->dev; ++ unsigned int clk_base; ++ int ret = 0; ++ ++ regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); ++ dev_dbg(dev, "%s clk_base=%x", __func__, clk_base); + + regmap_read(rt712->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT0, RT712_SDCA_CTL_FUNC_STATUS, 0), &jack_func_status); +@@ -1773,6 +1847,12 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + __func__, jack_func_status, mic_func_status, amp_func_status); + + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); ++ ++ if (clk_base == RT712_CLK_FREQ_24_576MHZ) { ++ rt712_sdca_clk_patch(rt712); ++ rt712_sdca_clk_patch2(rt712); ++ } ++ + /* DMIC */ + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { + rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); +diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h +--- a/sound/soc/codecs/rt712-sdca.h ++++ b/sound/soc/codecs/rt712-sdca.h +@@ -162,6 +162,16 @@ struct rt712_dmic_kctrl_priv { + #define RT712_EAPD_HIGH 0x2 + #define RT712_EAPD_LOW 0x0 + ++/* SDW clock root frequency */ ++#define RT712_SDW_ROOT_CLK 0x004d ++#define RT712_SDW_SCALE_CLK0 0x0062 ++#define RT712_SDW_SCALE_CLK1 0x0072 ++ ++/* PLL2 config */ ++#define RT712_PLL2_CONF1 0x2f3a ++#define RT712_PLL2_CONF2 0x2f3d ++#define RT712_PLL2_CONF3 0x2f41 ++ + /* RC Calibration register */ + #define RT712_RC_CAL 0x3201 + +@@ -254,6 +264,14 @@ enum rt712_sdca_version { + RT712_VB, + }; + ++enum { ++ RT712_CLK_FREQ_19_2_MHZ = 1, ++ RT712_CLK_FREQ_24MHZ = 2, ++ RT712_CLK_FREQ_24_576MHZ = 3, ++ RT712_CLK_FREQ_22_5792MHZ = 4, ++}; ++ ++ + int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave); + int rt712_sdca_init(struct device *dev, struct regmap *regmap, + struct regmap *mbq_regmap, struct sdw_slave *slave); +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -70,6 +70,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re + case 0x0310100: + case 0x2000000 ... 0x2000003: + case 0x2000013: ++ case 0x2000026: + case 0x200002c: + case 0x200003c: + case 0x2000046: +@@ -142,6 +143,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re + case 0x200000d: + case 0x2000019: + case 0x2000020: ++ case 0x2000026: + case 0x200002c: + case 0x2000030: + case 0x2000046: +@@ -155,6 +157,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re + case 0x5810039: + case 0x5b10018: + case 0x5b10019: ++ case 0x6100006: + return true; + default: + return false; +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -1497,6 +1497,15 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, + &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); + } + ++static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) ++{ ++ rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_REG, ++ RT721_VD_HIDDEN_CTRL, RT721_HIDDEN_REG_SW_RESET, ++ RT721_HIDDEN_REG_SW_RESET); ++ rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, ++ RT721_HDA_LEGACY_RESET_CTL, 0x1, 0x1); ++} ++ + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) + { + struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); +@@ -1530,9 +1539,17 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) + } + + pm_runtime_get_noresume(&slave->dev); ++ ++ if (!rt721->first_hw_init) ++ rt721_sdca_reset(rt721); ++ + rt721_sdca_dmic_preset(rt721); + rt721_sdca_amp_preset(rt721); + rt721_sdca_jack_preset(rt721); ++ ++ if (rt721->hs_jack && (!rt721->first_hw_init)) ++ rt721_sdca_jack_init(rt721); ++ + if (rt721->first_hw_init) { + regcache_cache_bypass(rt721->regmap, false); + regcache_mark_dirty(rt721->regmap); +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -775,9 +775,12 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, + s++; + } + ++ adsp_dbg(dsp, "Try '%s'\n", fw->filename); + ret = wm_adsp_firmware_request(&fw->firmware, fw->filename, cs_dsp->dev); + if (ret < 0) { +- adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); ++ if (ret != -ENOENT) ++ adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); ++ + kfree(fw->filename); + fw->filename = NULL; + if (ret != -ENOENT) +diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c +--- a/sound/soc/intel/boards/sof_es8336.c ++++ b/sound/soc/intel/boards/sof_es8336.c +@@ -360,6 +360,15 @@ static const struct dmi_system_id sof_es8336_quirk_table[] = { + .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | + SOC_ES8336_HEADSET_MIC1) + }, ++ { ++ .callback = sof_es8336_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "NDZ-WXX9"), ++ }, ++ .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | ++ SOC_ES8336_HEADSET_MIC1) ++ }, + {} + }; + +diff --git a/sound/soc/sdw_utils/soc_sdw_cs_amp.c b/sound/soc/sdw_utils/soc_sdw_cs_amp.c +--- a/sound/soc/sdw_utils/soc_sdw_cs_amp.c ++++ b/sound/soc/sdw_utils/soc_sdw_cs_amp.c +@@ -14,7 +14,6 @@ + #include + #include + +-#define CS_AMP_CHANNELS_PER_AMP 4 + #define CS35L56_SPK_VOLUME_0DB 400 /* 0dB Max */ + + int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix) +@@ -64,51 +63,6 @@ int asoc_sdw_cs_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai + } + EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_rtd_init, "SND_SOC_SDW_UTILS"); + +-int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +-{ +- const struct snd_soc_dai_link *dai_link = rtd->dai_link; +- const struct snd_soc_dai_link_ch_map *ch_map; +- const struct snd_soc_dai_link_component *codec_dlc; +- struct snd_soc_dai *codec_dai; +- u8 ch_slot[8] = {}; +- unsigned int amps_per_bus, ch_per_amp, mask; +- int i, ret; +- +- WARN_ON(dai_link->num_cpus > ARRAY_SIZE(ch_slot)); +- +- /* +- * CS35L56 has 4 TX channels. When the capture is aggregated the +- * same bus slots will be allocated to all the amps on a bus. Only +- * one amp on that bus can be transmitting in each slot so divide +- * the available 4 slots between all the amps on a bus. +- */ +- amps_per_bus = dai_link->num_codecs / dai_link->num_cpus; +- if ((amps_per_bus == 0) || (amps_per_bus > CS_AMP_CHANNELS_PER_AMP)) { +- dev_err(rtd->card->dev, "Illegal num_codecs:%u / num_cpus:%u\n", +- dai_link->num_codecs, dai_link->num_cpus); +- return -EINVAL; +- } +- +- ch_per_amp = CS_AMP_CHANNELS_PER_AMP / amps_per_bus; +- +- for_each_rtd_ch_maps(rtd, i, ch_map) { +- codec_dlc = snd_soc_link_to_codec(rtd->dai_link, i); +- codec_dai = snd_soc_find_dai(codec_dlc); +- mask = GENMASK(ch_per_amp - 1, 0) << ch_slot[ch_map->cpu]; +- +- ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, mask, 4, 32); +- if (ret < 0) { +- dev_err(rtd->card->dev, "Failed to set TDM slot:%d\n", ret); +- return ret; +- } +- +- ch_slot[ch_map->cpu] += ch_per_amp; +- } +- +- return 0; +-} +-EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_feedback_rtd_init, "SND_SOC_SDW_UTILS"); +- + int asoc_sdw_cs_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -811,7 +811,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -840,7 +839,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -869,7 +867,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -898,7 +895,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -1565,7 +1561,7 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link_ch_map *ch_maps; + int ch = params_channels(params); +- unsigned int ch_mask; ++ unsigned int cpu_ch_mask, codec_ch_mask; + int num_codecs; + int step; + int i; +@@ -1575,8 +1571,9 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + + /* Identical data will be sent to all codecs in playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- ch_mask = GENMASK(ch - 1, 0); ++ cpu_ch_mask = GENMASK(ch - 1, 0); + step = 0; ++ codec_ch_mask = 0; + } else { + num_codecs = rtd->dai_link->num_codecs; + +@@ -1586,17 +1583,24 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- ch_mask = GENMASK(ch / num_codecs - 1, 0); +- step = hweight_long(ch_mask); ++ cpu_ch_mask = GENMASK(ch / num_codecs - 1, 0); ++ step = hweight_long(cpu_ch_mask); ++ codec_ch_mask = cpu_ch_mask; + } + + /* + * The captured data will be combined from each cpu DAI if the dai + * link has more than one codec DAIs. Set codec channel mask and + * ASoC will set the corresponding channel numbers for each cpu dai. ++ * ++ * sdw_stream_add_slave() assigns different payload offsets to each ++ * codec in a capture stream, so that the same channels on each ++ * codec map to different channels on the CPU. + */ +- for_each_link_ch_maps(rtd->dai_link, i, ch_maps) +- ch_maps->cpu_ch_mask = ch_mask << (i * step); ++ for_each_link_ch_maps(rtd->dai_link, i, ch_maps) { ++ ch_maps->cpu_ch_mask = cpu_ch_mask << (i * step); ++ ch_maps->codec_ch_mask = codec_ch_mask; ++ } + + return 0; + } diff --git a/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..0b22640354a66d5c2ae79bfa95daa8a3a3cc874d GIT binary patch literal 594 zcmV-Y0und&Q11rtff%(Po z4ImBwGb`!AyLEjatV{~}_&jkewFB(1Sz zlvU8^IR!Gh=7i+=IfnS8yXexMJl})yEd>t}ES@uxLi79q;%nf|uNN8LN4{b8!qQ{M|RGeBe~UqAdev, "Unexpected PCI revision: 0x%x\n", acp_data->pci_rev); ++ return -EINVAL; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); diff --git a/pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig b/pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..086972eb415a3df9800732a957deaf38f4f1235e GIT binary patch literal 594 zcmV-Y02zc=XnZ|BrALpR1WlQCUg zsfG6g)b5PG--RP%H`wQ;UEig%Y*r9!DEc5q}zHGgAlfN%kJ0JY6*di_!2PS)Na(>T{ zJgjtqW2KG$*1gHJcfZ+RqQi>bD^K3)7DBUjVKbOzOEElZB4!8GU0_u)%{7=1fibWC z+gaFG`k&%`l=xa;1hZP@R{$h1#$Oj&>WGt;r@;?f>VXxZ*1i0g_Ldc2Wvu@vHOOgm zm)NN@gM7u-fIrKnWfflP$ch(Pn)=k%akSA4M5Z0-74pX;@(xjTUKQ9Is*pD2Q9^mx gh%UatGI4)Tj(S?E9YWb6Y*aV%ZZm-xoB#j- literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch b/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch new file mode 100644 index 0000000..f9378d4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch @@ -0,0 +1,513 @@ +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -340,6 +340,15 @@ static inline void snd_card_unref(struct snd_card *card) + + DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T)) + ++/* ++ * For automatic error handling at probe time, assign the card like: ++ * struct snd_card *card __free(snd_card_free) = NULL; ++ * then create and process as usual. ++ * But, don't forget to clear to NULL at successful return for avoiding ++ * the unexpected cleanup! ++ */ ++DEFINE_FREE(snd_card_free, struct snd_card *, if (_T) snd_card_free(_T)) ++ + #define snd_card_set_dev(card, devptr) ((card)->dev = (devptr)) + + /* device.c */ +diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c +--- a/sound/core/seq/seq_compat.c ++++ b/sound/core/seq/seq_compat.c +@@ -44,7 +44,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned + return -EFAULT; + data->kernel = NULL; + +- err = snd_seq_kernel_client_ctl(client->number, cmd, data); ++ scoped_guard(mutex, &client->ioctl_mutex) { ++ err = snd_seq_kernel_client_ctl(client->number, cmd, data); ++ } + if (err < 0) + return err; + +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4025,6 +4025,7 @@ enum { + ALC269_FIXUP_SONY_VAIO, + ALC275_FIXUP_SONY_VAIO_GPIO2, + ALC269_FIXUP_DELL_M101Z, ++ ALC256_FIXUP_ASUS_PM3406CHA, + ALC269_FIXUP_SKU_IGNORE, + ALC269_FIXUP_ASUS_G73JW, + ALC269_FIXUP_ASUS_N7601ZM_PINS, +@@ -4124,6 +4125,7 @@ enum { + ALC285_FIXUP_HP_SPECTRE_X360_EB1, + ALC285_FIXUP_HP_SPECTRE_X360_DF1, + ALC285_FIXUP_HP_ENVY_X360, ++ ALC285_FIXUP_HP_ENVY_13AQ, + ALC288_FIXUP_DELL_HEADSET_MODE, + ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC288_FIXUP_DELL_XPS_13, +@@ -4199,6 +4201,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE, + ALC225_FIXUP_WYSE_AUTO_MUTE, + ALC225_FIXUP_WYSE_DISABLE_MIC_VREF, ++ ALC225_FIXUP_HP_AIO_SPK, + ALC286_FIXUP_ACER_AIO_HEADSET_MIC, + ALC256_FIXUP_ASUS_HEADSET_MIC, + ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4385,6 +4388,7 @@ enum { + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, + ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ++ ALC256_FIXUP_IPASON_SMARTBOOK_S1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4482,6 +4486,14 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC256_FIXUP_ASUS_PM3406CHA] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, ++ { 0x20, AC_VERB_SET_PROC_COEF, 0x7f20 }, ++ {} ++ } ++ }, + [ALC269_FIXUP_SKU_IGNORE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_sku_ignore, +@@ -5720,6 +5732,14 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, ++ [ALC225_FIXUP_HP_AIO_SPK] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, ++ { 0x20, AC_VERB_SET_PROC_COEF, 0x0220 }, ++ { } ++ }, ++ }, + [ALC286_FIXUP_ACER_AIO_HEADSET_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { +@@ -6401,6 +6421,15 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, + }, ++ [ALC285_FIXUP_HP_ENVY_13AQ] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170150 }, /* front speakers */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, ++ }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, +@@ -7088,6 +7117,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC236_FIXUP_DELL_HP_POP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_invalidate_dacs, ++ .chained = true, ++ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC274_FIXUP_HP_89E9_GPIO] = { + .type = HDA_FIXUP_FUNC, +@@ -7150,6 +7181,13 @@ static const struct hda_fixup alc269_fixups[] = { + { } + } + }, ++ [ALC256_FIXUP_IPASON_SMARTBOOK_S1] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x1b, 0x90170110 }, /* the real internal speaker */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7197,6 +7235,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), ++ SND_PCI_QUIRK(0x1025, 0x151e, "Acer Nitro AN515-45", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1539, "Acer Nitro 5 AN515-57", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC287_FIXUP_ACER_MICMUTE_LED), +@@ -7210,8 +7249,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x172c, "Acer Nitro ANV16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x173b, "Acer Swift SFG16-72", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), +@@ -7317,6 +7358,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), + SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x2164, "HP Pavilion 15 Notebook PC", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), +@@ -7399,9 +7441,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x854a, "HP EliteBook 830 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x854d, "HP EliteBook 840 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x856a, "HP Pavilion 15-cs1xxx", ALC295_FIXUP_HP_PAVILION_MUTE_LED_1B), ++ HDA_CODEC_QUIRK(0x103c, 0x85ba, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK), + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f1, "HP Laptop 15-dw1xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), +@@ -7409,6 +7453,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x86ad, "HP ENVY 13-aq1xxx", ALC285_FIXUP_HP_ENVY_13AQ), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), + SND_PCI_QUIRK(0x103c, 0x86c8, "HP Laptop 14s-dr1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7437,6 +7482,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x87b2, "HP Pavilion Gaming Laptop 15-ec1xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x87b7, "HP Laptop 14-fq0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87cb, "HP Pavilion 15-eg0xxx", ALC287_FIXUP_HP_GPIO_LED), +@@ -7497,6 +7543,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8975, "HP EliteBook x360 840 Aero G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x897d, "HP mt440 Mobile Thin Client U74", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8981, "HP Elite Dragonfly G3", ALC245_FIXUP_CS35L41_SPI_4), ++ SND_PCI_QUIRK(0x103c, 0x8987, "HP Pavilion 14-dv1001TU", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x898a, "HP Pavilion 15-eg100", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x898e, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x898f, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7534,7 +7581,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8a2d, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x103c, 0x8a31, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8a31, "HP ENVY x360 2-in-1 Laptop 15-ey0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a36, "HP Pavilion Plus 14-eh0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a3d, "HP Victus 15-fb0xxx (MB 8A3D)", ALC245_FIXUP_HP_MUTE_LED_V2_COEFBIT), +@@ -7595,6 +7642,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8bc7, "HP Pavilion 15-eh3174nw", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bd4, "HP Victus 16-s0xxx (MB 8BD4)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), +@@ -7617,6 +7665,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c2f, "HP Victus 15-fb2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7726,6 +7775,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8e37, "HP 16 Piston OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7829,6 +7879,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1652, "ASUS ROG Zephyrus Do 15 SE", ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), + SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), +@@ -7931,7 +7982,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x3ef0, "ASUS Strix G635LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f00, "ASUS Strix G815LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f10, "ASUS Strix G835LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), +- SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x3f30, "ASUS Strix G815LR_LW", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3fd0, "ASUS B3605CVA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3ff0, "ASUS B5405CVA", ALC245_FIXUP_CS35L41_SPI_2), +@@ -7954,6 +8005,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), + SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), + SND_PCI_QUIRK(0x10ec, 0x10f2, "Intel Reference board", ALC700_FIXUP_INTEL_REFERENCE), ++ SND_PCI_QUIRK(0x10ec, 0x111e, "Higole F9B", ALC290_FIXUP_MONO_SPEAKERS), + SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE), + SND_PCI_QUIRK(0x10ec, 0x119e, "Positivo SU C1400", ALC269_FIXUP_ASPIRE_HEADSET_MIC), + SND_PCI_QUIRK(0x10ec, 0x11bc, "VAIO VJFE-IL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), +@@ -8179,7 +8231,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), +- SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8204,6 +8255,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8254,6 +8306,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38d5, "Yoga S990-16 Pro IMH YC Quad", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38d6, "Yoga S990-16 Pro IMH VECO Quad", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38d7, "Lenovo Yoga 9 14IMH9", ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN), ++ SND_PCI_QUIRK(0x17aa, 0x38dc, "Lenovo ThinkBook G8", ALC285_FIXUP_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +@@ -8273,7 +8326,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), + SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), +@@ -8369,6 +8422,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x2782, 0x0206, "IPASON SmartBook S1", ALC256_FIXUP_IPASON_SMARTBOOK_S1), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c +--- a/sound/hda/codecs/realtek/realtek.c ++++ b/sound/hda/codecs/realtek/realtek.c +@@ -1292,6 +1292,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) + } + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1407,6 +1408,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_write_coef_idx(codec, 0x45, 0xc489); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); +@@ -1562,6 +1564,7 @@ static void alc_headset_mode_default(struct hda_codec *codec) + alc_process_coef_fw(codec, coef0225); + alc_hp_enable_unmute(codec, 75); + break; ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1663,6 +1666,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1780,6 +1784,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1881,6 +1886,7 @@ static void alc_determine_headset_type(struct hda_codec *codec) + } + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + msleep(300); +diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c +--- a/sound/hda/controllers/tegra.c ++++ b/sound/hda/controllers/tegra.c +@@ -374,7 +374,8 @@ static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) + * in powers of 2, next available ratio is 16 which can be + * used as a limiting factor here. + */ +- if (of_device_is_compatible(np, "nvidia,tegra30-hda")) ++ if (of_device_is_compatible(np, "nvidia,tegra30-hda") || ++ of_device_is_compatible(np, "nvidia,tegra194-hda")) + chip->bus.core.sdo_limit = 16; + + /* codec detection */ +diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c +--- a/sound/pci/ctxfi/cthw20k2.c ++++ b/sound/pci/ctxfi/cthw20k2.c +@@ -1895,6 +1895,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) + + if (hw->model == CTSB1270) { + /* Set up the PCM4220 ADC on Titanium HD */ ++ data = hw_read_20kx(hw, GPIO_DATA); + data &= ~0x0C; + if (1 == info->msr) + data |= 0x00; /* Single Speed Mode 32-50kHz */ +@@ -1903,6 +1904,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) + else + data |= 0x04; /* Quad Speed Mode 108kHz-216kHz */ + hw_write_20kx(hw, GPIO_DATA, data); ++ usleep_range(10000, 11000); + } + + hw_adc_start(hw); +diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c +--- a/sound/pci/ice1712/ice1712.c ++++ b/sound/pci/ice1712/ice1712.c +@@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) + { + static int dev; +- struct snd_card *card __free(snd_card_unref) = NULL; ++ struct snd_card *card __free(snd_card_free) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; +diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c +--- a/sound/usb/caiaq/device.c ++++ b/sound/usb/caiaq/device.c +@@ -562,6 +562,9 @@ static int snd_probe(struct usb_interface *intf, + ret = init_card(caiaqdev(card)); + if (ret < 0) { + dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret); ++#ifdef CONFIG_SND_USB_CAIAQ_INPUT ++ snd_usb_caiaq_input_disconnect(caiaqdev(card)); ++#endif + snd_card_free(card); + return ret; + } +diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c +--- a/sound/usb/line6/playback.c ++++ b/sound/usb/line6/playback.c +@@ -182,6 +182,13 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) + + fsize *= bytes_per_frame; + ++ if (fsize > line6pcm->max_packet_size_out) { ++ dev_err(line6pcm->line6->ifcdev, ++ "playback packet too large: %d > %d\n", ++ fsize, line6pcm->max_packet_size_out); ++ return -EMSGSIZE; ++ } ++ + fout->offset = urb_size; + fout->length = fsize; + urb_size += fsize; +diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c +--- a/sound/usb/misc/ua101.c ++++ b/sound/usb/misc/ua101.c +@@ -1007,6 +1007,15 @@ static int detect_usb_format(struct ua101 *ua) + } + ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->playback.max_packet_bytes = usb_endpoint_maxp(epd); ++ ++ if (ua->capture.max_packet_bytes / ua->capture.frame_bytes * ++ ua->playback.frame_bytes > ua->playback.max_packet_bytes) { ++ dev_err(&ua->dev->dev, ++ "playback packet size %u too small for %u capture frames\n", ++ ua->playback.max_packet_bytes, ++ ua->capture.max_packet_bytes / ua->capture.frame_bytes); ++ return -ENXIO; ++ } + return 0; + } + +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -749,6 +749,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x0db0, 0xa073), + .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, + }, ++ { /* MSI MAG B850M MORTAR WIFI */ ++ .id = USB_ID(0x0db0, 0xcc78), ++ .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, ++ }, + { /* MSI TRX40 */ + .id = USB_ID(0x0db0, 0x543d), + .map = trx40_mobo_map, +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -1702,10 +1702,12 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); +- case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ +- return snd_usb_cm1a_boot_quirk(dev); + } + ++ /* Behringer devices may need explicit device descriptor read at boot */ ++ if (USB_ID_VENDOR(id) == 0x1397) ++ return snd_usb_cm1a_boot_quirk(dev); ++ + return 0; + } + +@@ -2366,6 +2368,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ + QUIRK_FLAG_ALWAYS_SET_RATE), ++ DEVICE_FLG(0x0b05, 0x1826, /* ASUS SupremeFX Hi-Fi */ ++ QUIRK_FLAG_DISABLE_AUTOSUSPEND), ++ DEVICE_FLG(0x0b05, 0x1827, /* ASUS SupremeFX Hi-Fi */ ++ QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2510,8 +2516,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x262a, 0x9302, /* ddHiFi TC44C */ + QUIRK_FLAG_DSD_RAW), +- DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ +- QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x2772, 0x0502, /* Musical Fidelity M6s DAC */ + 0), /* for avoiding QUIRK_FLAG_DSD_RAW with vendor match */ + DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ +@@ -2542,6 +2546,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x3255, 0x0000, /* Luxman D-10X */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), ++ DEVICE_FLG(0x32bb, 0x0004, /* HiBy FC4 */ ++ QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x3302, 0x17c2, /* TTGK Technology USB-C Audio */ + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x339b, 0x3a07, /* Synaptics HONOR USB-C HEADSET */ +@@ -2608,6 +2614,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2622, /* IAG Limited devices */ + QUIRK_FLAG_DSD_RAW), ++ VENDOR_FLG(0x2708, /* Audient devices */ ++ QUIRK_FLAG_IGNORE_CTL_ERROR), + VENDOR_FLG(0x2772, /* Musical Fidelity devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x278b, /* Rotel? */ diff --git a/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch.sig b/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..26547921e5bbb10128f4be3fc296c5b238d98469 GIT binary patch literal 593 zcmV-X0HQvoeaw+wO7xWON<^wY2hqKN2w`{mjkYgL z0p?WCe$HZ$QruIZh28;<(5!IQJEDo!?W()Qlil1i!uh(i_x7=LM#~)%! z1Vx{O_f$G!jB43DMNw(9B%uTt2acbZeeoY(4w+0stpeA06n9;-17|fpM)VPVAi02^ zdgAP|v5NI*if9pBPtC^Vq@rs~EfQC}g2w6WV!_|FMSFN-qpg;oQ#ruU2Fdh}S`7X8 z-p(K8b}KF^_P>pPTwWC7gqi$&xUNPdHp_^ekGV2CgmNwjYS1)731q>_9_xzIn>kI9 zqJVR_o|$6?+#E`Lm0J&0D9J+U9-o)a05XiDZSHci!5JDI1xW~x@nxlWj<~?0#GfM3 zAxScg{P(7+2KYCNp@n0QG z6@PY~soUB4U;y~xVe2rr3YliJLL|S6Bu(pN{fCHLS{S7B5pYWQjbr$aj4vd0!>lDX fQmM$?-McHvDP^I+F4HU@2AXgj0QM&rs{Z*vvu_er literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch b/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch new file mode 100644 index 0000000..2290ae4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch @@ -0,0 +1,523 @@ +diff --git a/drivers/spi/spi-cs42l43.c b/drivers/spi/spi-cs42l43.c +--- a/drivers/spi/spi-cs42l43.c ++++ b/drivers/spi/spi-cs42l43.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -215,12 +216,108 @@ static size_t cs42l43_spi_max_length(struct spi_device *spi) + return CS42L43_SPI_MAX_LENGTH; + } + ++/* ++ * Workaround needed for two speaker ID pins in one ACPI GpioIo() but ++ * the Linux-specific _DSD property only contains one pin. ++ * Create a temporary acpi_gpio_mapping pointing at both pins. ++ */ ++static const struct acpi_gpio_params cs42l43_2bit_speaker_id_from_one_gpioio_params[] = { ++ [0] = { ++ .crs_entry_index = 0, ++ .line_index = 0, ++ }, ++ [1] = { ++ .crs_entry_index = 0, ++ .line_index = 1, ++ }, ++}; ++ ++static const struct acpi_gpio_mapping cs42l43_2bit_speaker_id_from_one_gpioio_mapping[] = { ++ { ++ .name = "spk-id-quirk-gpios", ++ .data = cs42l43_2bit_speaker_id_from_one_gpioio_params, ++ .size = ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params), ++ }, ++ { } ++}; ++ ++static int cs42l43_get_2bit_speaker_id_from_one_gpioio(struct cs42l43_spi *priv, int *result) ++{ ++ struct fwnode_reference_args args; ++ struct acpi_device *adev; ++ struct gpio_desc *desc; ++ u32 spkid = 0; ++ int i, ret; ++ ++ /* Use the _DSD property to get the node containing the GpioIo() */ ++ ret = fwnode_property_get_reference_args(dev_fwnode(priv->dev), "spk-id-gpios", ++ NULL, 3, 0, &args); ++ if (ret) ++ return ret; ++ ++ struct fwnode_handle *fwnode __free(fwnode_handle) = args.fwnode; ++ ++ /* An acpi_gpio_mapping must be added to the node that contains the GpioIo() */ ++ adev = to_acpi_device_node(fwnode); ++ if (!adev) ++ return -EINVAL; ++ ++ ret = acpi_dev_add_driver_gpios(adev, cs42l43_2bit_speaker_id_from_one_gpioio_mapping); ++ if (ret) ++ return ret; ++ ++ /* gpiod_get_array() can't read from a mapping in a child node */ ++ for (i = 0; i < ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params); i++) { ++ desc = fwnode_gpiod_get_index(fwnode, "spk-id-quirk", i, GPIOD_IN, ++ dev_name(priv->dev)); ++ if (IS_ERR(desc)) { ++ ret = PTR_ERR(desc); ++ goto out; ++ } ++ ++ ret = gpiod_get_value_cansleep(desc); ++ gpiod_put(desc); ++ if (ret < 0) ++ goto out; ++ ++ spkid |= (u32)ret << i; ++ } ++ ++ dev_dbg(priv->dev, "spk-id-gpios = %u\n", spkid); ++ *result = spkid; ++ ret = 0; ++out: ++ acpi_dev_remove_driver_gpios(adev); ++ ++ return ret; ++} ++ ++static const struct dmi_system_id cs42l43_spk_id_quirks[] = { ++ { ++ .ident = "Dell XPS 13 DX13260", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53"), ++ }, ++ .driver_data = cs42l43_get_2bit_speaker_id_from_one_gpioio, ++ }, ++ { } ++}; ++ + static int cs42l43_get_speaker_id_gpios(struct cs42l43_spi *priv, int *result) + { ++ const struct dmi_system_id *dmi_id; + struct gpio_descs *descs; + u32 spkid; + int i, ret; + ++ dmi_id = dmi_first_match(cs42l43_spk_id_quirks); ++ if (dmi_id) { ++ int (*get_speaker_id)(struct cs42l43_spi *priv, int *result) = dmi_id->driver_data; ++ ++ return get_speaker_id(priv, result); ++ } ++ + descs = gpiod_get_array_optional(priv->dev, "spk-id", GPIOD_IN); + if (!descs) + return 0; +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -51,6 +51,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "83V9"), + }, + }, ++ { ++ /* Lenovo Yoga Pro 7 14ASP9 (Strix Point, ACP 7.0) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83HN"), ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), +@@ -63,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* ASUS Zenbook S14 UM5406GA (Strix Point, ACP 7.0) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook S14 UM5406GA"), ++ }, ++ }, + { + /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ + .matches = { +diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig +--- a/sound/soc/amd/acp/Kconfig ++++ b/sound/soc/amd/acp/Kconfig +@@ -181,6 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH + select SND_SOC_RT715_SDW + select SND_SOC_RT715_SDCA_SDW + select SND_SOC_RT722_SDCA_SDW ++ select SND_SOC_TAS2783_SDW + help + This option enables Legacy(No DSP) sound card support for SoundWire + enabled AMD platforms along with ACP PDM controller. +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -748,7 +748,27 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_only[] = { + {} + }; + ++static const struct snd_soc_acpi_link_adr acp70_tas2783a_l0_rt712_vb_l1[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783_2_adr), ++ .adr_d = tas2783_2_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_1_group1_adr), ++ .adr_d = rt712_vb_1_group1_adr, ++ }, ++ {} ++}; ++ + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783a_l0_rt712_vb_l1, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0), + .links = acp70_tas2783_2, +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -248,6 +248,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "21J6"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "21JN"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +@@ -381,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "83Q3"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83UU"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -1989,9 +1989,7 @@ config SND_SOC_RT715_SDCA_SDW + config SND_SOC_RT766_SDCA_SDW + tristate "Realtek RT766 SDCA Codec - SDW" + depends on SOUNDWIRE +- depends on SND_SOC_SDCA +- select SND_SOC_SDCA_HID +- select SND_SOC_SDCA_IRQ ++ depends on SND_SOC_SDCA_CLASS + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -1841,6 +1841,7 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ unsigned int gain; + u16 gain_reg; + u16 reg; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); +@@ -1882,6 +1883,8 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_ENABLE); + } ++ gain = snd_soc_component_read(component, gain_reg); ++ snd_soc_component_write(component, gain_reg, gain); + wsa_macro_config_ear_spkr_gain(component, wsa, + event, gain_reg); + break; +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -200,7 +200,7 @@ static int max98363_sdw_dai_hw_params(struct snd_pcm_substream *substream, + struct max98363_priv *max98363 = + snd_soc_component_get_drvdata(component); + +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_runtime *stream; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -573,11 +574,10 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt1017_sdca_priv *rt1017 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels, ch_mask; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -591,28 +591,17 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + /* port 1 for playback */ +- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port = 1; +- } else { +- direction = SDW_DATA_DIR_TX; ++ else + port = 2; +- } + +- num_channels = params_channels(params); +- ch_mask = (1 << num_channels) - 1; +- +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = num_channels; +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- port_config.ch_mask = ch_mask; ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + dev_dbg(dai->dev, "frame_rate %d, ch_count %d, bps %d, direction %d, ch_mask %d, port: %d\n", +- params_rate(params), num_channels, snd_pcm_format_width(params_format(params)), +- direction, ch_mask, port); ++ stream_config.frame_rate, stream_config.ch_count, stream_config.bps, ++ stream_config.direction, port_config.ch_mask, port); + + retval = sdw_stream_add_slave(rt1017->sdw_slave, &stream_config, + &port_config, 1, sdw_stream); +diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c +--- a/sound/soc/codecs/rt274.c ++++ b/sound/soc/codecs/rt274.c +@@ -619,9 +619,11 @@ static int rt274_hw_params(struct snd_pcm_substream *substream, + unsigned int val = 0; + int d_len_code = 0, c_len_code = 0; + ++ /* bit 14 Sample Rate Base, 0: 48kHz 1: 44.1kHz */ + switch (params_rate(params)) { +- /* bit 14 0:48K 1:44.1K */ + case 44100: ++ val |= BIT(14); ++ break; + case 48000: + break; + default: +diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c +--- a/sound/soc/codecs/rt286.c ++++ b/sound/soc/codecs/rt286.c +@@ -953,7 +953,7 @@ static int rt286_probe(struct snd_soc_component *component) + + if (rt286->i2c->irq) + schedule_delayed_work(&rt286->jack_detect_work, +- msecs_to_jiffies(50)); ++ msecs_to_jiffies(1250)); + return 0; + } + +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -676,7 +676,7 @@ SOC_ENUM("DRC Decay Rate", drc_decay), + SOC_ENUM("DRC FF Delay", drc_ff_delay), + SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), + SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), +-SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), ++SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_1, 6, 3, 0, drc_tlv_max), + SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), + SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), + SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), +diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c +--- a/sound/soc/intel/boards/bytcr_rt5651.c ++++ b/sound/soc/intel/boards/bytcr_rt5651.c +@@ -410,6 +410,23 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { + BYT_RT5651_HP_LR_SWAPPED | + BYT_RT5651_MONO_SPEAKER), + }, ++ { ++ /* Chuwi Hi8 (CWI513) with a BIOS using generic DMI strings */ ++ .callback = byt_rt5651_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), ++ DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), ++ /* Above matches are too generic, add bios-date match */ ++ DMI_MATCH(DMI_BIOS_DATE, "05/21/2016"), ++ }, ++ .driver_data = (void *)(BYT_RT5651_MCLK_EN | ++ BYT_RT5651_JD1_1 | ++ BYT_RT5651_OVCD_TH_2000UA | ++ BYT_RT5651_OVCD_SF_1P0 | ++ BYT_RT5651_IN2_MAP | ++ BYT_RT5651_HP_LR_SWAPPED | ++ BYT_RT5651_MONO_SPEAKER), ++ }, + { + /* Chuwi Vi8 Plus (CWI519) */ + .callback = byt_rt5651_quirk_cb, +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -745,6 +745,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_cs42l43_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0.tplg", ++ .machine_check = snd_soc_acpi_intel_force_function_topology, + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,22 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++static const struct dmi_system_id force_function_topology_quirk_table[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83R0"), ++ }, ++ }, ++ {} ++}; ++ ++bool snd_soc_acpi_intel_force_function_topology(void *arg) ++{ ++ return !dmi_check_system(force_function_topology_quirk_table); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_force_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) + { + return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,6 +11,7 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_force_function_topology(void *arg); + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c +--- a/sound/soc/qcom/lpass-cpu.c ++++ b/sound/soc/qcom/lpass-cpu.c +@@ -1234,8 +1234,8 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) + } + if (drvdata->mi2s_playback_sd_mode[dai_id] == + LPAIF_I2SCTL_MODE_QUAD01) { +- variant->dai_driver[dai_id].playback.channels_min = 4; +- variant->dai_driver[dai_id].playback.channels_max = 4; ++ variant->dai_driver[i].playback.channels_min = 4; ++ variant->dai_driver[i].playback.channels_max = 4; + } + } + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -388,20 +388,27 @@ static int class_function_probe(struct auxiliary_device *auxdev, + + ret = devm_pm_runtime_enable(dev); + if (ret) +- return ret; ++ goto err_pm; + + ret = class_function_boot(drv); + if (ret) +- return ret; ++ goto err_pm; + + ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais); +- if (ret) +- return dev_err_probe(dev, ret, "failed to register component\n"); ++ if (ret) { ++ dev_err_probe(dev, ret, "failed to register component\n"); ++ goto err_pm; ++ } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; ++ ++err_pm: ++ pm_runtime_put_sync(dev); ++ ++ return ret; + } + + static void class_function_remove(struct auxiliary_device *auxdev) +@@ -490,8 +497,6 @@ static int class_function_suspend(struct device *dev) + struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); + int ret; + +- drv->suspended = true; +- + /* Ensure runtime resume runs on resume */ + ret = pm_runtime_resume_and_get(dev); + if (ret) { +@@ -499,6 +504,8 @@ static int class_function_suspend(struct device *dev) + return ret; + } + ++ drv->suspended = true; ++ + sdca_irq_disable(drv->function, drv->core->irq_info); + + ret = pm_runtime_force_suspend(dev); +diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c +--- a/sound/soc/tegra/tegra186_asrc.c ++++ b/sound/soc/tegra/tegra186_asrc.c +@@ -828,7 +828,7 @@ static const struct snd_kcontrol_new tegra186_asrc_controls[] = { + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream6 Input Threshold", +- ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, ++ ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 5), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + diff --git a/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch.sig b/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..ad208a3c4838155633c10be433c56a64d3dbeeb8 GIT binary patch literal 594 zcmV-Y0s2G8%&bVgqmzTxtef_dhm3`3KzBZ zs+S3U-M~r3&;M`+dYOkvjhkEt4qmKB!}mI`w}ZDaESeFk1#T~@BFOGMw6r*HZ`p#Y z>`?;h<5<7)KvGvtOy}gAq9+9{p3>Gy3Gt?!y22|CA-t3!&Q6P7C_A;E=m=dg_ z8Xbc_#jBM2Oc=-|Rf-q_RRBJ>o|+ti)5ARxT?!_MkiGVQ^Aub2{w7$M=PO)74;k34 zC{qf0BBr%`K>srN&OI`O)d5i2PD@vuWD^D=UGCx z20rnz42!4O_x4UUwNn+Ek|T7RLW7Dn$adO^0@dg1CUKgO1W$>N!@reW=8Y}aov)*C z*=S25!nwHn;Gx-bsX7DSsUELerMUra8VKf18TWF|1yHNyb`{q<&}MRr50RDB-e10O z*+B;HV!x=QLb*@%fvvp9c&HhcLf!E3n-|lyKG(Q=d?pAO@Y=v8Zu@KTf}lf(ya?bO z)=Q$Wl7v^z8tPuZBdr>JM$*NR_>Y|Ax@Qz%u30Q!9@c&8scJ&sI|6wBL<$g@wl1Fu zuTiz;2{Y2d;_~Y$^;98chmFJG;nr$f0Aet*_nE6@g4T`3AS5LeDhbSp!|vWpO?E=4 gk2|k(*-Hb9G~=TCNOaWNrm&rpY(o2($<0c#LWLnA+5i9m literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch b/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch new file mode 100644 index 0000000..6a78d2e --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch @@ -0,0 +1,33 @@ +diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c +--- a/drivers/media/i2c/cvs/core.c ++++ b/drivers/media/i2c/cvs/core.c +@@ -723,8 +723,6 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) + } + + if (ctx->res == ICVS_FULLCAP) { +- struct gpio_desc *wake; +- + ctx->rst = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->rst)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->rst), +@@ -732,14 +730,12 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) + goto err_put_ipu; + } + +- wake = devm_gpiod_get(dev, "wake", GPIOD_IN); +- if (IS_ERR(wake)) { +- ret = dev_err_probe(dev, PTR_ERR(wake), +- "failed to get wake GPIO\n"); +- goto err_put_ipu; +- } +- +- ctx->irq = gpiod_to_irq(wake); ++ /* ++ * Do not request the line: another device's _CRS may list ++ * the same pin, and its driver would then fail with -EBUSY. ++ */ ++ ctx->irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), ++ "wake", 0); + if (ctx->irq < 0) { + ret = dev_err_probe(dev, ctx->irq, + "failed to get wake IRQ\n"); diff --git a/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig b/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..69eeeeae97d7c42d1c5458a501381885bd099862 GIT binary patch literal 594 zcmV-Y09?zW$rQy5x{rl*ld$&bok>8$C^px94*8B_wp5BN$#1 zQrtli)hrMEBu4Bz;8(mSx(x5$nXE)gv2>yR&nlwaIna+x`cU&2Jh{q9q7gUZV_BH< zQvwZarQ^vV&KU%Qq)aZP$R}cN>V3ljs=O<#iJa^Ochnr?kS)?$Pr6}cZ^~=U2l^i_ zi?%m7N2yJL)Fb3}NZ9qsRuaz?^0b-)ZqG*e?3TDknIuhFlw7;;w&vyyM{-4&_3;DkqbD0Z z=i_&&;ey}f;%uSb3_uz7LUeQOk)Zx8wh6!-098*gVgUH~mVg!zJt%mSpU$Cq6%)Yo g#nr_of_lanes, +- .param.conf.link_freq = ctx->link_freq, ++ .param.conf.link_freq = link_freq, + }; + size_t cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.conf); + +@@ -91,7 +92,7 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(ctx->remote->entity); + struct device *dev = cvs_dev(ctx); +- s64 freq; ++ s64 link_freq; + int ret; + + /* cvs_set_link_owner(ICVS_CSI_LINK_HOST) */ +@@ -99,15 +100,14 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + if (ret < 0) + return ret; + +- freq = v4l2_get_link_freq(ctx->remote, 0, 0); +- if (freq < 0) { +- ret = freq; ++ link_freq = v4l2_get_link_freq(ctx->remote, 0, 0); ++ if (link_freq < 0) { ++ ret = link_freq; + goto err_rpm_put; + } +- ctx->link_freq = freq; + + if (ctx->i2c_client) { +- ret = csi_set_link_cfg(ctx); ++ ret = csi_set_link_cfg(ctx, link_freq); + if (ret < 0) + goto err_rpm_put_sync; + } +@@ -345,7 +345,6 @@ static int cvs_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + if (freq < 0) + return -EINVAL; + +- ctx->link_freq = freq; + cfg->link_freq = freq; + + return 0; +diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c +--- a/drivers/media/i2c/imx355.c ++++ b/drivers/media/i2c/imx355.c +@@ -9,74 +9,97 @@ + #include + #include + #include ++#include + #include + #include + ++#include + #include + #include + #include + #include + +-#define IMX355_REG_MODE_SELECT 0x0100 ++#define IMX355_REG_MODE_SELECT CCI_REG8(0x0100) + #define IMX355_MODE_STANDBY 0x00 + #define IMX355_MODE_STREAMING 0x01 + + /* Chip ID */ +-#define IMX355_REG_CHIP_ID 0x0016 ++#define IMX355_REG_CHIP_ID CCI_REG16(0x0016) + #define IMX355_CHIP_ID 0x0355 + ++#define IMX355_REG_LANE_SEL CCI_REG8(0x0114) ++ ++/* PLL registers that depend on the external clock frequency */ ++#define IMX355_REG_EXTCLK_FREQ CCI_REG16(0x0136) ++#define IMX355_REG_PLL_OP_PREDIV CCI_REG8(0x030d) ++#define IMX355_REG_PLL_OP_MUL CCI_REG16(0x030e) ++#define IMX355_REG_PLL_IVT_PCK_DIV CCI_REG8(0x0301) ++#define IMX355_REG_PLL_IVT_SYSCK_DIV CCI_REG8(0x0303) ++#define IMX355_PLL_OP_PREDIV 2 ++#define IMX355_PLL_IVT_PCK_DIV 5 ++ + /* V_TIMING internal */ +-#define IMX355_REG_FLL 0x0340 ++#define IMX355_REG_FLL CCI_REG16(0x0340) + #define IMX355_FLL_MAX 0xffff ++#define IMX355_VBLANK_MIN 20 ++ ++#define IMX355_REG_LLP CCI_REG16(0x0342) ++#define IMX355_LLP_MAX 0xffff ++ ++#define IMX355_REG_X_ADD_START CCI_REG16(0x0344) ++#define IMX355_REG_Y_ADD_START CCI_REG16(0x0346) ++#define IMX355_REG_X_ADD_END CCI_REG16(0x0348) ++#define IMX355_REG_Y_ADD_END CCI_REG16(0x034a) ++#define IMX355_REG_X_OUT_SIZE CCI_REG16(0x034c) ++#define IMX355_REG_Y_OUT_SIZE CCI_REG16(0x034e) + + /* Exposure control */ +-#define IMX355_REG_EXPOSURE 0x0202 ++#define IMX355_REG_EXPOSURE CCI_REG16(0x0202) + #define IMX355_EXPOSURE_MIN 1 + #define IMX355_EXPOSURE_STEP 1 + #define IMX355_EXPOSURE_DEFAULT 0x0282 ++#define IMX355_EXPOSURE_OFFSET 10 + + /* Analog gain control */ +-#define IMX355_REG_ANALOG_GAIN 0x0204 ++#define IMX355_REG_ANALOG_GAIN CCI_REG16(0x0204) + #define IMX355_ANA_GAIN_MIN 0 + #define IMX355_ANA_GAIN_MAX 960 + #define IMX355_ANA_GAIN_STEP 1 + #define IMX355_ANA_GAIN_DEFAULT 0 + + /* Digital gain control */ +-#define IMX355_REG_DPGA_USE_GLOBAL_GAIN 0x3070 +-#define IMX355_REG_DIG_GAIN_GLOBAL 0x020e ++#define IMX355_REG_DPGA_USE_GLOBAL_GAIN CCI_REG8(0x3070) ++#define IMX355_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) + #define IMX355_DGTL_GAIN_MIN 256 + #define IMX355_DGTL_GAIN_MAX 4095 + #define IMX355_DGTL_GAIN_STEP 1 + #define IMX355_DGTL_GAIN_DEFAULT 256 + + /* Test Pattern Control */ +-#define IMX355_REG_TEST_PATTERN 0x0600 ++#define IMX355_REG_TEST_PATTERN CCI_REG16(0x0600) + #define IMX355_TEST_PATTERN_DISABLED 0 + #define IMX355_TEST_PATTERN_SOLID_COLOR 1 + #define IMX355_TEST_PATTERN_COLOR_BARS 2 + #define IMX355_TEST_PATTERN_GRAY_COLOR_BARS 3 + #define IMX355_TEST_PATTERN_PN9 4 + ++#define IMX355_REG_REQ_LINK_BIT_RATE CCI_REG16(0x0820) ++ ++#define IMX355_REG_BINNING_MODE CCI_REG8(0x0900) ++#define IMX355_REG_BINNING_TYPE CCI_REG8(0x0901) ++#define IMX355_REG_BINNING_WEIGHTING CCI_REG8(0x0902) ++ + /* Flip Control */ +-#define IMX355_REG_ORIENTATION 0x0101 ++#define IMX355_REG_ORIENTATION CCI_REG8(0x0101) + +-/* default link frequency and external clock */ +-#define IMX355_LINK_FREQ_DEFAULT 360000000LL +-#define IMX355_EXT_CLK 19200000 +-#define IMX355_LINK_FREQ_INDEX 0 +- +-/* number of data lanes */ +-#define IMX355_DATA_LANES 4 +- +-struct imx355_reg { +- u16 address; +- u8 val; +-}; ++#define IMX355_PIXEL_ARRAY_TOP 0 ++#define IMX355_PIXEL_ARRAY_LEFT 0 ++#define IMX355_PIXEL_ARRAY_WIDTH 3280 ++#define IMX355_PIXEL_ARRAY_HEIGHT 2464 + + struct imx355_reg_list { + u32 num_of_regs; +- const struct imx355_reg *regs; ++ const struct cci_reg_sequence *regs; + }; + + /* Mode : resolution and related config&values */ +@@ -85,28 +108,58 @@ struct imx355_mode { + u32 width; + /* Frame height */ + u32 height; ++ struct v4l2_rect crop; + + /* V-timing */ + u32 fll_def; +- u32 fll_min; + + /* H-timing */ + u32 llp; + +- /* index of link frequency */ +- u32 link_freq_index; +- + /* Default register values */ + struct imx355_reg_list reg_list; + }; + ++struct imx355_clk_params { ++ u32 ext_clk; ++ u16 extclk_freq; /* External clock (MHz) in 8.8 fixed point) */ ++ u16 pll_op_mpy[2]; /* OP system PLL multiplier */ ++ u8 pll_op_prediv[2]; /* OP system pre PLL d */ ++}; ++ ++/* ++ * The clock tree is in single PLL mode, so PREDIV_VT and MPY_IVT do nothing. ++ * In 4 lane mode the MIPI rate is 360Mhz (720Mbit/s) and pixel rate is ++ * 288MPix/s. ++ * In 2 lane mode the MIPI rate is 444MHz (888Mbit/s) and pixel rate ++ * 177.6MPix/s with a 24MHz clock, and 441.6MHz (883.2Mbit/s) and 176.6MPix/s ++ * with a 19.2MHz clock. ++ */ ++static const struct imx355_clk_params imx355_clk_params[] = { ++ { ++ .ext_clk = 19200000, ++ .extclk_freq = 0x1333, ++ .pll_op_mpy = { 75, 92 }, ++ .pll_op_prediv = { 2, 2 } ++ }, ++ { ++ .ext_clk = 24000000, ++ .extclk_freq = 0x1800, ++ .pll_op_mpy = { 60, 111 }, ++ .pll_op_prediv = { 2, 3 } ++ }, ++}; ++ + struct imx355_hwcfg { ++ s64 link_freq_menu; + unsigned long link_freq_bitmap; ++ unsigned int num_lanes; + }; + + struct imx355 { + struct device *dev; + struct clk *clk; ++ struct regmap *regmap; + + struct v4l2_subdev sd; + struct media_pad pad; +@@ -114,24 +167,14 @@ struct imx355 { + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; +- struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + +- /* Current mode */ +- const struct imx355_mode *cur_mode; +- + struct imx355_hwcfg *hwcfg; +- +- /* +- * Mutex for serialized access: +- * Protect sensor set pad format and start/stop streaming safely. +- * Protect access to sensor v4l2 controls. +- */ +- struct mutex mutex; ++ const struct imx355_clk_params *clk_params; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +@@ -143,750 +186,147 @@ static const struct regulator_bulk_data imx355_supplies[] = { + { .supply = "dovdd" }, + }; + +-static const struct imx355_reg imx355_global_regs[] = { +- { 0x0136, 0x13 }, +- { 0x0137, 0x33 }, +- { 0x304e, 0x03 }, +- { 0x4348, 0x16 }, +- { 0x4350, 0x19 }, +- { 0x4408, 0x0a }, +- { 0x440c, 0x0b }, +- { 0x4411, 0x5f }, +- { 0x4412, 0x2c }, +- { 0x4623, 0x00 }, +- { 0x462c, 0x0f }, +- { 0x462d, 0x00 }, +- { 0x462e, 0x00 }, +- { 0x4684, 0x54 }, +- { 0x480a, 0x07 }, +- { 0x4908, 0x07 }, +- { 0x4909, 0x07 }, +- { 0x490d, 0x0a }, +- { 0x491e, 0x0f }, +- { 0x4921, 0x06 }, +- { 0x4923, 0x28 }, +- { 0x4924, 0x28 }, +- { 0x4925, 0x29 }, +- { 0x4926, 0x29 }, +- { 0x4927, 0x1f }, +- { 0x4928, 0x20 }, +- { 0x4929, 0x20 }, +- { 0x492a, 0x20 }, +- { 0x492c, 0x05 }, +- { 0x492d, 0x06 }, +- { 0x492e, 0x06 }, +- { 0x492f, 0x06 }, +- { 0x4930, 0x03 }, +- { 0x4931, 0x04 }, +- { 0x4932, 0x04 }, +- { 0x4933, 0x05 }, +- { 0x595e, 0x01 }, +- { 0x5963, 0x01 }, +- { 0x3030, 0x01 }, +- { 0x3031, 0x01 }, +- { 0x3045, 0x01 }, +- { 0x4010, 0x00 }, +- { 0x4011, 0x00 }, +- { 0x4012, 0x00 }, +- { 0x4013, 0x01 }, +- { 0x68a8, 0xfe }, +- { 0x68a9, 0xff }, +- { 0x6888, 0x00 }, +- { 0x6889, 0x00 }, +- { 0x68b0, 0x00 }, +- { 0x3058, 0x00 }, +- { 0x305a, 0x00 }, ++static const struct cci_reg_sequence imx355_global_regs[] = { ++ { CCI_REG8(0x304e), 0x03 }, ++ { CCI_REG8(0x4348), 0x16 }, ++ { CCI_REG8(0x4350), 0x19 }, ++ { CCI_REG8(0x4408), 0x0a }, ++ { CCI_REG8(0x440c), 0x0b }, ++ { CCI_REG8(0x4411), 0x5f }, ++ { CCI_REG8(0x4412), 0x2c }, ++ { CCI_REG8(0x4623), 0x00 }, ++ { CCI_REG8(0x462c), 0x0f }, ++ { CCI_REG8(0x462d), 0x00 }, ++ { CCI_REG8(0x462e), 0x00 }, ++ { CCI_REG8(0x4684), 0x54 }, ++ { CCI_REG8(0x480a), 0x07 }, ++ { CCI_REG8(0x4908), 0x07 }, ++ { CCI_REG8(0x4909), 0x07 }, ++ { CCI_REG8(0x490d), 0x0a }, ++ { CCI_REG8(0x491e), 0x0f }, ++ { CCI_REG8(0x4921), 0x06 }, ++ { CCI_REG8(0x4923), 0x28 }, ++ { CCI_REG8(0x4924), 0x28 }, ++ { CCI_REG8(0x4925), 0x29 }, ++ { CCI_REG8(0x4926), 0x29 }, ++ { CCI_REG8(0x4927), 0x1f }, ++ { CCI_REG8(0x4928), 0x20 }, ++ { CCI_REG8(0x4929), 0x20 }, ++ { CCI_REG8(0x492a), 0x20 }, ++ { CCI_REG8(0x492c), 0x05 }, ++ { CCI_REG8(0x492d), 0x06 }, ++ { CCI_REG8(0x492e), 0x06 }, ++ { CCI_REG8(0x492f), 0x06 }, ++ { CCI_REG8(0x4930), 0x03 }, ++ { CCI_REG8(0x4931), 0x04 }, ++ { CCI_REG8(0x4932), 0x04 }, ++ { CCI_REG8(0x4933), 0x05 }, ++ { CCI_REG8(0x595e), 0x01 }, ++ { CCI_REG8(0x5963), 0x01 }, ++ { CCI_REG8(0x3030), 0x01 }, ++ { CCI_REG8(0x3031), 0x01 }, ++ { CCI_REG8(0x3045), 0x01 }, ++ { CCI_REG8(0x4010), 0x00 }, ++ { CCI_REG8(0x4011), 0x00 }, ++ { CCI_REG8(0x4012), 0x00 }, ++ { CCI_REG8(0x4013), 0x01 }, ++ { CCI_REG8(0x68a8), 0xfe }, ++ { CCI_REG8(0x68a9), 0xff }, ++ { CCI_REG8(0x6888), 0x00 }, ++ { CCI_REG8(0x6889), 0x00 }, ++ { CCI_REG8(0x68b0), 0x00 }, ++ { CCI_REG8(0x3058), 0x00 }, ++ { CCI_REG8(0x305a), 0x00 }, ++ { CCI_REG8(0x0112), 0x0a }, ++ { CCI_REG8(0x0113), 0x0a }, ++ { IMX355_REG_PLL_IVT_PCK_DIV, IMX355_PLL_IVT_PCK_DIV }, ++ { CCI_REG8(0x0303), 0x01 }, ++ { CCI_REG8(0x0305), 0x02 }, ++ { CCI_REG8(0x0306), 0x00 }, ++ { CCI_REG8(0x0307), 0x78 }, ++ { CCI_REG8(0x030b), 0x01 }, ++ { IMX355_REG_PLL_OP_PREDIV, IMX355_PLL_OP_PREDIV }, ++ { CCI_REG8(0x0310), 0x00 }, ++ { CCI_REG8(0x0220), 0x00 }, ++ { CCI_REG8(0x0222), 0x01 }, ++ { CCI_REG8(0x3088), 0x04 }, ++ { CCI_REG8(0x6813), 0x02 }, ++ { CCI_REG8(0x6835), 0x07 }, ++ { CCI_REG8(0x6836), 0x01 }, ++ { CCI_REG8(0x6837), 0x04 }, ++ { CCI_REG8(0x684d), 0x07 }, ++ { CCI_REG8(0x684e), 0x01 }, ++ { CCI_REG8(0x684f), 0x04 }, + }; + +-static const struct imx355_reg_list imx355_global_setting = { +- .num_of_regs = ARRAY_SIZE(imx355_global_regs), +- .regs = imx355_global_regs, ++static const struct cci_reg_sequence mode_3268x2448_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3268x2448_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x08 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x08 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcb }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x97 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xc4 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0x90 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_3264x2448_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3264x2448_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x08 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x08 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xc7 }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x97 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xc0 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0x90 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_3280x2464_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3280x2464_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xd0 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0xa0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1940x1096_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1940x1096_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa0 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xac }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x33 }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xf3 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x94 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x48 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1936x1096_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1936x1096_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa0 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xac }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x2f }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xf3 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x90 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x48 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1924x1080_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1924x1080_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa8 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xb4 }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x2b }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xeb }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x84 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x38 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1920x1080_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1920x1080_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa8 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xb4 }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x27 }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xeb }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x80 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x38 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1640x1232_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1640x1232_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x06 }, +- { 0x034d, 0x68 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1640x922_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1640x922_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0x30 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x08 }, +- { 0x034b, 0x63 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x06 }, +- { 0x034d, 0x68 }, +- { 0x034e, 0x03 }, +- { 0x034f, 0x9a }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1300x736_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1300x736_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x58 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0xf0 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x7f }, +- { 0x034a, 0x07 }, +- { 0x034b, 0xaf }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x14 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xe0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1296x736_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1296x736_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x58 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0xf0 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x77 }, +- { 0x034a, 0x07 }, +- { 0x034b, 0xaf }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x10 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xe0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1284x720_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1284x720_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x68 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x6f }, +- { 0x034a, 0x07 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x04 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1280x720_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1280x720_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x68 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x67 }, +- { 0x034a, 0x07 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x00 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, +-}; +- +-static const struct imx355_reg mode_820x616_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x02 }, +- { 0x0341, 0x8c }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x44 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x03 }, +- { 0x034d, 0x34 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0x68 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x02 }, +- { 0x0701, 0x78 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_820x616_regs[] = { ++ { CCI_REG8(0x0700), 0x02 }, ++ { CCI_REG8(0x0701), 0x78 }, + }; + + static const char * const imx355_test_pattern_menu[] = { +@@ -897,23 +337,19 @@ static const char * const imx355_test_pattern_menu[] = { + "Pseudorandom Sequence (PN9)", + }; + +-/* +- * When adding more than the one below, make sure the disallowed ones will +- * actually be disabled in the LINK_FREQ control. +- */ +-static const s64 link_freq_menu_items[] = { +- IMX355_LINK_FREQ_DEFAULT, +-}; +- + /* Mode configs */ + static const struct imx355_mode supported_modes[] = { + { + .width = 3280, + .height = 2464, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), + .regs = mode_3280x2464_regs, +@@ -922,10 +358,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 3268, + .height = 2448, ++ .crop = { ++ .width = 3268, ++ .height = 2448, ++ .left = 8, ++ .top = 8, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3268x2448_regs), + .regs = mode_3268x2448_regs, +@@ -934,10 +374,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 3264, + .height = 2448, ++ .crop = { ++ .width = 3264, ++ .height = 2448, ++ .left = 8, ++ .top = 8, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs), + .regs = mode_3264x2448_regs, +@@ -946,10 +390,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1940, + .height = 1096, ++ .crop = { ++ .width = 1940, ++ .height = 1096, ++ .left = 672, ++ .top = 684, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1940x1096_regs), + .regs = mode_1940x1096_regs, +@@ -958,10 +406,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1936, + .height = 1096, ++ .crop = { ++ .width = 1936, ++ .height = 1096, ++ .left = 672, ++ .top = 684, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1936x1096_regs), + .regs = mode_1936x1096_regs, +@@ -970,10 +422,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1924, + .height = 1080, ++ .crop = { ++ .width = 1924, ++ .height = 1080, ++ .left = 680, ++ .top = 692, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1924x1080_regs), + .regs = mode_1924x1080_regs, +@@ -982,10 +438,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1920, + .height = 1080, ++ .crop = { ++ .width = 1920, ++ .height = 1080, ++ .left = 680, ++ .top = 692, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), + .regs = mode_1920x1080_regs, +@@ -994,10 +454,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1640, + .height = 1232, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs), + .regs = mode_1640x1232_regs, +@@ -1006,10 +470,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1640, + .height = 922, ++ .crop = { ++ .width = 3280, ++ .height = 1844, ++ .left = 0, ++ .top = 304, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x922_regs), + .regs = mode_1640x922_regs, +@@ -1018,10 +486,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1300, + .height = 736, ++ .crop = { ++ .width = 2600, ++ .height = 1472, ++ .left = 344, ++ .top = 496, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1300x736_regs), + .regs = mode_1300x736_regs, +@@ -1030,10 +502,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1296, + .height = 736, ++ .crop = { ++ .width = 2592, ++ .height = 1472, ++ .left = 344, ++ .top = 496, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x736_regs), + .regs = mode_1296x736_regs, +@@ -1042,10 +518,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1284, + .height = 720, ++ .crop = { ++ .width = 2568, ++ .height = 1440, ++ .left = 360, ++ .top = 512, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1284x720_regs), + .regs = mode_1284x720_regs, +@@ -1054,10 +534,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1280, + .height = 720, ++ .crop = { ++ .width = 2560, ++ .height = 1440, ++ .left = 360, ++ .top = 512, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, +@@ -1066,10 +550,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 820, + .height = 616, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 652, +- .fll_min = 652, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_820x616_regs), + .regs = mode_820x616_regs, +@@ -1095,116 +583,28 @@ static u32 imx355_get_format_code(struct imx355 *imx355) + { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, }, + }; + +- lockdep_assert_held(&imx355->mutex); + code = codes[imx355->vflip->val][imx355->hflip->val]; + + return code; + } + +-/* Read registers up to 4 at a time */ +-static int imx355_read_reg(struct imx355 *imx355, u16 reg, u32 len, u32 *val) +-{ +- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); +- struct i2c_msg msgs[2]; +- u8 addr_buf[2]; +- u8 data_buf[4] = { 0 }; +- int ret; +- +- if (len > 4) +- return -EINVAL; +- +- put_unaligned_be16(reg, addr_buf); +- /* Write register address */ +- msgs[0].addr = client->addr; +- msgs[0].flags = 0; +- msgs[0].len = ARRAY_SIZE(addr_buf); +- msgs[0].buf = addr_buf; +- +- /* Read data from register */ +- msgs[1].addr = client->addr; +- msgs[1].flags = I2C_M_RD; +- msgs[1].len = len; +- msgs[1].buf = &data_buf[4 - len]; +- +- ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); +- if (ret != ARRAY_SIZE(msgs)) +- return -EIO; +- +- *val = get_unaligned_be32(data_buf); +- +- return 0; +-} +- +-/* Write registers up to 4 at a time */ +-static int imx355_write_reg(struct imx355 *imx355, u16 reg, u32 len, u32 val) +-{ +- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); +- u8 buf[6]; +- +- if (len > 4) +- return -EINVAL; +- +- put_unaligned_be16(reg, buf); +- put_unaligned_be32(val << (8 * (4 - len)), buf + 2); +- if (i2c_master_send(client, buf, len + 2) != len + 2) +- return -EIO; +- +- return 0; +-} +- +-/* Write a list of registers */ +-static int imx355_write_regs(struct imx355 *imx355, +- const struct imx355_reg *regs, u32 len) +-{ +- int ret; +- u32 i; +- +- for (i = 0; i < len; i++) { +- ret = imx355_write_reg(imx355, regs[i].address, 1, regs[i].val); +- if (ret) { +- dev_err_ratelimited(imx355->dev, +- "write reg 0x%4.4x return err %d", +- regs[i].address, ret); +- +- return ret; +- } +- } +- +- return 0; +-} +- +-/* Open sub-device */ +-static int imx355_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +-{ +- struct imx355 *imx355 = to_imx355(sd); +- struct v4l2_mbus_framefmt *try_fmt = +- v4l2_subdev_state_get_format(fh->state, 0); +- +- mutex_lock(&imx355->mutex); +- +- /* Initialize try_fmt */ +- try_fmt->width = imx355->cur_mode->width; +- try_fmt->height = imx355->cur_mode->height; +- try_fmt->code = imx355_get_format_code(imx355); +- try_fmt->field = V4L2_FIELD_NONE; +- +- mutex_unlock(&imx355->mutex); +- +- return 0; +-} +- + static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) + { + struct imx355 *imx355 = container_of(ctrl->handler, + struct imx355, ctrl_handler); ++ const struct v4l2_mbus_framefmt *format = NULL; ++ struct v4l2_subdev_state *state; + s64 max; + int ret; + ++ state = v4l2_subdev_get_locked_active_state(&imx355->sd); ++ format = v4l2_subdev_state_get_format(state, 0); ++ + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ +- max = imx355->cur_mode->height + ctrl->val - 10; ++ max = format->height + ctrl->val - IMX355_EXPOSURE_OFFSET; + __v4l2_ctrl_modify_range(imx355->exposure, + imx355->exposure->minimum, + max, imx355->exposure->step, max); +@@ -1221,31 +621,31 @@ static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + /* Analog gain = 1024/(1024 - ctrl->val) times */ +- ret = imx355_write_reg(imx355, IMX355_REG_ANALOG_GAIN, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_ANALOG_GAIN, ++ ctrl->val, NULL); + break; + case V4L2_CID_DIGITAL_GAIN: +- ret = imx355_write_reg(imx355, IMX355_REG_DIG_GAIN_GLOBAL, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_DIG_GAIN_GLOBAL, ++ ctrl->val, NULL); + break; + case V4L2_CID_EXPOSURE: +- ret = imx355_write_reg(imx355, IMX355_REG_EXPOSURE, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_EXPOSURE, ++ ctrl->val, NULL); + break; + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ +- ret = imx355_write_reg(imx355, IMX355_REG_FLL, 2, +- imx355->cur_mode->height + ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_FLL, ++ format->height + ctrl->val, NULL); + break; + case V4L2_CID_TEST_PATTERN: +- ret = imx355_write_reg(imx355, IMX355_REG_TEST_PATTERN, +- 2, ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_TEST_PATTERN, ++ ctrl->val, NULL); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: +- ret = imx355_write_reg(imx355, IMX355_REG_ORIENTATION, 1, +- imx355->hflip->val | +- imx355->vflip->val << 1); ++ ret = cci_write(imx355->regmap, IMX355_REG_ORIENTATION, ++ imx355->hflip->val | imx355->vflip->val << 1, ++ NULL); + break; + default: + ret = -EINVAL; +@@ -1272,9 +672,7 @@ static int imx355_enum_mbus_code(struct v4l2_subdev *sd, + if (code->index > 0) + return -EINVAL; + +- mutex_lock(&imx355->mutex); + code->code = imx355_get_format_code(imx355); +- mutex_unlock(&imx355->mutex); + + return 0; + } +@@ -1288,12 +686,9 @@ static int imx355_enum_frame_size(struct v4l2_subdev *sd, + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + +- mutex_lock(&imx355->mutex); + if (fse->code != imx355_get_format_code(imx355)) { +- mutex_unlock(&imx355->mutex); + return -EINVAL; + } +- mutex_unlock(&imx355->mutex); + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; +@@ -1311,36 +706,10 @@ static void imx355_update_pad_format(struct imx355 *imx355, + fmt->format.height = mode->height; + fmt->format.code = imx355_get_format_code(imx355); + fmt->format.field = V4L2_FIELD_NONE; +-} +- +-static int imx355_do_get_pad_format(struct imx355 *imx355, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_format *fmt) +-{ +- struct v4l2_mbus_framefmt *framefmt; +- +- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); +- fmt->format = *framefmt; +- } else { +- imx355_update_pad_format(imx355, imx355->cur_mode, fmt); +- } +- +- return 0; +-} +- +-static int imx355_get_pad_format(struct v4l2_subdev *sd, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_format *fmt) +-{ +- struct imx355 *imx355 = to_imx355(sd); +- int ret; +- +- mutex_lock(&imx355->mutex); +- ret = imx355_do_get_pad_format(imx355, sd_state, fmt); +- mutex_unlock(&imx355->mutex); +- +- return ret; ++ fmt->format.colorspace = V4L2_COLORSPACE_RAW; ++ fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_601; ++ fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; ++ fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + } + + static int +@@ -1351,13 +720,8 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + struct imx355 *imx355 = to_imx355(sd); + const struct imx355_mode *mode; + struct v4l2_mbus_framefmt *framefmt; +- s32 vblank_def; +- s32 vblank_min; ++ struct v4l2_rect *crop; + s64 h_blank; +- u64 pixel_rate; +- u32 height; +- +- mutex_lock(&imx355->mutex); + + /* + * Only one bayer order is supported. +@@ -1370,23 +734,25 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + width, height, + fmt->format.width, fmt->format.height); + imx355_update_pad_format(imx355, mode, fmt); +- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); +- *framefmt = fmt->format; +- } else { +- imx355->cur_mode = mode; +- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4; +- do_div(pixel_rate, 10); +- __v4l2_ctrl_s_ctrl_int64(imx355->pixel_rate, pixel_rate); ++ framefmt = v4l2_subdev_state_get_format(sd_state, 0); ++ ++ *framefmt = fmt->format; ++ ++ crop = v4l2_subdev_state_get_crop(sd_state, 0); ++ crop->width = mode->crop.width; ++ crop->height = mode->crop.height; ++ crop->left = mode->crop.left; ++ crop->top = mode->crop.top; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Update limits and set FPS to default */ +- height = imx355->cur_mode->height; +- vblank_def = imx355->cur_mode->fll_def - height; +- vblank_min = imx355->cur_mode->fll_min - height; +- height = IMX355_FLL_MAX - height; +- __v4l2_ctrl_modify_range(imx355->vblank, vblank_min, height, 1, +- vblank_def); +- __v4l2_ctrl_s_ctrl(imx355->vblank, vblank_def); +- h_blank = mode->llp - imx355->cur_mode->width; ++ __v4l2_ctrl_modify_range(imx355->vblank, IMX355_VBLANK_MIN, ++ IMX355_FLL_MAX - mode->height, 1, ++ mode->fll_def - mode->height); ++ __v4l2_ctrl_s_ctrl(imx355->vblank, mode->fll_def - mode->height); ++ ++ h_blank = mode->llp - mode->width; ++ + /* + * Currently hblank is not changeable. + * So FPS control is done only by vblank. +@@ -1395,7 +761,42 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + h_blank, 1, h_blank); + } + +- mutex_unlock(&imx355->mutex); ++ return 0; ++} ++ ++static int imx355_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(sd_state, 0); ++ return 0; ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = IMX355_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX355_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX355_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX355_PIXEL_ARRAY_HEIGHT; ++ ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx355_entity_init_state(struct v4l2_subdev *subdev, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { }; ++ ++ fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; ++ fmt.format.code = MEDIA_BUS_FMT_SRGGB10_1X10; ++ fmt.format.width = supported_modes[0].width; ++ fmt.format.height = supported_modes[0].height; ++ ++ imx355_set_pad_format(subdev, sd_state, &fmt); + + return 0; + } +@@ -1403,52 +804,98 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + /* Start streaming */ + static int imx355_start_streaming(struct imx355 *imx355) + { +- const struct imx355_reg_list *reg_list; +- int ret; ++ const struct v4l2_mbus_framefmt *fmt; ++ struct v4l2_subdev_state *state; ++ const struct imx355_mode *mode; ++ int lane_idx = imx355->hwcfg->num_lanes == 4 ? 0 : 1; ++ struct v4l2_rect *crop; ++ u64 link_bitrate; ++ u8 binning_mode; ++ int ret = 0; + + /* Global Setting */ +- reg_list = &imx355_global_setting; +- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); +- if (ret) { +- dev_err(imx355->dev, "failed to set global settings"); +- return ret; +- } ++ cci_multi_reg_write(imx355->regmap, imx355_global_regs, ++ ARRAY_SIZE(imx355_global_regs), &ret); + +- /* Apply default values of current mode */ +- reg_list = &imx355->cur_mode->reg_list; +- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); +- if (ret) { +- dev_err(imx355->dev, "failed to set mode"); +- return ret; +- } ++ /* Apply values of current mode */ ++ state = v4l2_subdev_get_locked_active_state(&imx355->sd); ++ fmt = v4l2_subdev_state_get_format(state, 0); ++ crop = v4l2_subdev_state_get_crop(state, 0); ++ mode = v4l2_find_nearest_size(supported_modes, ++ ARRAY_SIZE(supported_modes), ++ width, height, fmt->width, fmt->height); ++ cci_multi_reg_write(imx355->regmap, mode->reg_list.regs, ++ mode->reg_list.num_of_regs, &ret); ++ ++ /* Set readout crop and size registers */ ++ cci_write(imx355->regmap, IMX355_REG_X_ADD_START, crop->left, ++ &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_START, crop->top, &ret); ++ cci_write(imx355->regmap, IMX355_REG_X_ADD_END, ++ crop->width + crop->left - 1, &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_END, ++ crop->height + crop->top - 1, &ret); ++ cci_write(imx355->regmap, IMX355_REG_X_OUT_SIZE, fmt->width, &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_OUT_SIZE, fmt->height, &ret); ++ ++ binning_mode = ((crop->width / fmt->width) << 4) | ++ (crop->height / fmt->height); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_MODE, ++ binning_mode == 0x11 ? 0x00 : 0x01, &ret); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_TYPE, binning_mode, &ret); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_WEIGHTING, 0x00, &ret); ++ ++ /* Set PLL registers for the external clock frequency */ ++ cci_write(imx355->regmap, IMX355_REG_EXTCLK_FREQ, ++ imx355->clk_params->extclk_freq, &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_MUL, ++ imx355->clk_params->pll_op_mpy[lane_idx], &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_PREDIV, ++ imx355->clk_params->pll_op_prediv[lane_idx], &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_IVT_SYSCK_DIV, ++ lane_idx ? 2 : 1, &ret); ++ ++ /* Set MIPI configuration */ ++ cci_write(imx355->regmap, IMX355_REG_LANE_SEL, ++ imx355->hwcfg->num_lanes - 1, &ret); ++ ++ link_bitrate = imx355->link_freq->qmenu_int[imx355->link_freq->val] * ++ imx355->hwcfg->num_lanes * 2; ++ do_div(link_bitrate, 1000000); ++ cci_write(imx355->regmap, IMX355_REG_REQ_LINK_BIT_RATE, link_bitrate, ++ &ret); + + /* set digital gain control to all color mode */ +- ret = imx355_write_reg(imx355, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, 1); +- if (ret) +- return ret; ++ cci_write(imx355->regmap, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, &ret); ++ ++ /* set line length */ ++ cci_write(imx355->regmap, IMX355_REG_LLP, ++ imx355->hblank->val + fmt->width, &ret); + + /* Apply customized values from user */ +- ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); +- if (ret) +- return ret; ++ if (!ret) ++ ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); + +- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, +- 1, IMX355_MODE_STREAMING); ++ cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, IMX355_MODE_STREAMING, ++ &ret); ++ ++ return ret; + } + + /* Stop streaming */ + static int imx355_stop_streaming(struct imx355 *imx355) + { +- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, +- 1, IMX355_MODE_STANDBY); ++ return cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, ++ IMX355_MODE_STANDBY, NULL); + } + + static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + { + struct imx355 *imx355 = to_imx355(sd); ++ struct v4l2_subdev_state *state; + int ret = 0; + +- mutex_lock(&imx355->mutex); ++ state = v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) { + ret = pm_runtime_resume_and_get(imx355->dev); +@@ -1464,21 +911,21 @@ static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + goto err_rpm_put; + } else { + imx355_stop_streaming(imx355); +- pm_runtime_put(imx355->dev); ++ pm_runtime_put_autosuspend(imx355->dev); + } + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx355->vflip, enable); + __v4l2_ctrl_grab(imx355->hflip, enable); + +- mutex_unlock(&imx355->mutex); ++ v4l2_subdev_unlock_state(state); + + return ret; + + err_rpm_put: +- pm_runtime_put(imx355->dev); ++ pm_runtime_put_autosuspend(imx355->dev); + err_unlock: +- mutex_unlock(&imx355->mutex); ++ v4l2_subdev_unlock_state(state); + + return ret; + } +@@ -1487,14 +934,14 @@ static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + static int imx355_identify_module(struct imx355 *imx355) + { + int ret; +- u32 val; ++ u64 val; + +- ret = imx355_read_reg(imx355, IMX355_REG_CHIP_ID, 2, &val); ++ ret = cci_read(imx355->regmap, IMX355_REG_CHIP_ID, &val, NULL); + if (ret) + return ret; + + if (val != IMX355_CHIP_ID) { +- dev_err(imx355->dev, "chip id mismatch: %x!=%x", ++ dev_err(imx355->dev, "chip id mismatch: %x!=%llx", + IMX355_CHIP_ID, val); + return -EIO; + } +@@ -1512,9 +959,10 @@ static const struct v4l2_subdev_video_ops imx355_video_ops = { + + static const struct v4l2_subdev_pad_ops imx355_pad_ops = { + .enum_mbus_code = imx355_enum_mbus_code, +- .get_fmt = imx355_get_pad_format, ++ .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx355_set_pad_format, + .enum_frame_size = imx355_enum_frame_size, ++ .get_selection = imx355_get_selection, + }; + + static const struct v4l2_subdev_ops imx355_subdev_ops = { +@@ -1528,7 +976,7 @@ static const struct media_entity_operations imx355_subdev_entity_ops = { + }; + + static const struct v4l2_subdev_internal_ops imx355_internal_ops = { +- .open = imx355_open, ++ .init_state = imx355_entity_init_state, + }; + + static int imx355_power_off(struct device *dev) +@@ -1582,13 +1030,11 @@ static int imx355_init_controls(struct imx355 *imx355) + { + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; ++ const struct imx355_mode *mode = &supported_modes[0]; + s64 exposure_max; + s64 vblank_def; +- s64 vblank_min; + s64 hblank; + u64 pixel_rate; +- const struct imx355_mode *mode; +- u32 max; + int ret; + + ctrl_hdlr = &imx355->ctrl_handler; +@@ -1596,40 +1042,34 @@ static int imx355_init_controls(struct imx355 *imx355) + if (ret) + return ret; + +- ctrl_hdlr->lock = &imx355->mutex; +- max = ARRAY_SIZE(link_freq_menu_items) - 1; + imx355->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_LINK_FREQ, max, 0, +- link_freq_menu_items); ++ V4L2_CID_LINK_FREQ, 0, 0, ++ &imx355->hwcfg->link_freq_menu); + if (imx355->link_freq) + imx355->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ +- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4; ++ pixel_rate = imx355->hwcfg->link_freq_menu * 2 * imx355->hwcfg->num_lanes; + do_div(pixel_rate, 10); +- /* By default, PIXEL_RATE is read only */ +- imx355->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_PIXEL_RATE, pixel_rate, +- pixel_rate, 1, pixel_rate); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_PIXEL_RATE, ++ pixel_rate, pixel_rate, 1, pixel_rate); + + /* Initialize vblank/hblank/exposure parameters based on current mode */ +- mode = imx355->cur_mode; + vblank_def = mode->fll_def - mode->height; +- vblank_min = mode->fll_min - mode->height; + imx355->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_VBLANK, vblank_min, ++ V4L2_CID_VBLANK, IMX355_VBLANK_MIN, + IMX355_FLL_MAX - mode->height, + 1, vblank_def); + + hblank = mode->llp - mode->width; +- imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_HBLANK, hblank, hblank, +- 1, hblank); ++ imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK, ++ hblank, hblank, 1, hblank); + if (imx355->hblank) + imx355->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* fll >= exposure time + adjust parameter (default value is 10) */ +- exposure_max = mode->fll_def - 10; ++ exposure_max = mode->fll_def - IMX355_EXPOSURE_OFFSET; + imx355->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX355_EXPOSURE_MIN, exposure_max, +@@ -1683,14 +1123,17 @@ static int imx355_init_controls(struct imx355 *imx355) + return ret; + } + +-static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) ++static struct imx355_hwcfg *imx355_get_hwcfg(struct imx355 *imx355) + { ++ struct device *dev = imx355->dev; + struct imx355_hwcfg *cfg; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; ++ const struct imx355_clk_params *clk = imx355->clk_params; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); ++ int lane_idx; + int ret; + + if (!fwnode) +@@ -1708,13 +1151,18 @@ static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) + if (!cfg) + goto out_err; + +- if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX355_DATA_LANES) ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2 && ++ bus_cfg.bus.mipi_csi2.num_data_lanes != 4) + goto out_err; + ++ cfg->num_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; ++ ++ lane_idx = cfg->num_lanes == 4 ? 0 : 1; ++ cfg->link_freq_menu = (clk->ext_clk * clk->pll_op_mpy[lane_idx]) / ++ (clk->pll_op_prediv[lane_idx] * 2); + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, +- link_freq_menu_items, +- ARRAY_SIZE(link_freq_menu_items), ++ &cfg->link_freq_menu, 1, + &cfg->link_freq_bitmap); + if (ret) + goto out_err; +@@ -1741,7 +1189,10 @@ static int imx355_probe(struct i2c_client *client) + + imx355->dev = &client->dev; + +- mutex_init(&imx355->mutex); ++ imx355->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(imx355->regmap)) ++ return dev_err_probe(imx355->dev, PTR_ERR(imx355->regmap), ++ "Unable to initialize I2C\n"); + + imx355->clk = devm_v4l2_sensor_clk_get(imx355->dev, NULL); + if (IS_ERR(imx355->clk)) +@@ -1749,7 +1200,13 @@ static int imx355_probe(struct i2c_client *client) + "failed to get clock\n"); + + freq = clk_get_rate(imx355->clk); +- if (freq != IMX355_EXT_CLK) ++ for (unsigned int i = 0; i < ARRAY_SIZE(imx355_clk_params); i++) { ++ if (freq == imx355_clk_params[i].ext_clk) { ++ imx355->clk_params = &imx355_clk_params[i]; ++ break; ++ } ++ } ++ if (!imx355->clk_params) + return dev_err_probe(imx355->dev, -EINVAL, + "external clock %lu is not supported\n", + freq); +@@ -1760,7 +1217,7 @@ static int imx355_probe(struct i2c_client *client) + &imx355->supplies); + if (ret) { + dev_err_probe(imx355->dev, ret, "could not get regulators"); +- goto error_probe; ++ return ret; + } + + imx355->reset_gpio = devm_gpiod_get_optional(imx355->dev, "reset", +@@ -1768,22 +1225,21 @@ static int imx355_probe(struct i2c_client *client) + if (IS_ERR(imx355->reset_gpio)) { + ret = dev_err_probe(imx355->dev, PTR_ERR(imx355->reset_gpio), + "failed to get gpios"); +- goto error_probe; ++ return ret; + } + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx355->sd, client, &imx355_subdev_ops); + +- imx355->hwcfg = imx355_get_hwcfg(imx355->dev); ++ imx355->hwcfg = imx355_get_hwcfg(imx355); + if (!imx355->hwcfg) { + dev_err(imx355->dev, "failed to get hwcfg"); +- ret = -ENODEV; +- goto error_probe; ++ return -ENODEV; + } + + ret = imx355_power_on(imx355->dev); + if (ret) +- goto error_probe; ++ return ret; + + /* Check module identity */ + ret = imx355_identify_module(imx355); +@@ -1792,9 +1248,6 @@ static int imx355_probe(struct i2c_client *client) + goto error_power_off; + } + +- /* Set default mode to max resolution */ +- imx355->cur_mode = &supported_modes[0]; +- + ret = imx355_init_controls(imx355); + if (ret) { + dev_err(imx355->dev, "failed to init controls: %d", ret); +@@ -1816,24 +1269,36 @@ static int imx355_probe(struct i2c_client *client) + goto error_handler_free; + } + ++ imx355->sd.state_lock = imx355->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&imx355->sd); ++ if (ret < 0) { ++ dev_err_probe(imx355->dev, ret, "subdev init error\n"); ++ goto error_media_entity_free; ++ } ++ + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(imx355->dev); + pm_runtime_enable(imx355->dev); ++ pm_runtime_set_autosuspend_delay(imx355->dev, 1000); ++ pm_runtime_use_autosuspend(imx355->dev); + + ret = v4l2_async_register_subdev_sensor(&imx355->sd); + if (ret < 0) +- goto error_media_entity_runtime_pm; ++ goto error_subdev_cleanup_runtime_pm; + + pm_runtime_idle(imx355->dev); + + return 0; + +-error_media_entity_runtime_pm: ++error_subdev_cleanup_runtime_pm: + pm_runtime_disable(imx355->dev); + pm_runtime_set_suspended(imx355->dev); ++ pm_runtime_dont_use_autosuspend(imx355->dev); ++ v4l2_subdev_cleanup(&imx355->sd); ++error_media_entity_free: + media_entity_cleanup(&imx355->sd.entity); + + error_handler_free: +@@ -1842,9 +1307,6 @@ static int imx355_probe(struct i2c_client *client) + error_power_off: + imx355_power_off(imx355->dev); + +-error_probe: +- mutex_destroy(&imx355->mutex); +- + return ret; + } + +@@ -1854,6 +1316,7 @@ static void imx355_remove(struct i2c_client *client) + struct imx355 *imx355 = to_imx355(sd); + + v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + +@@ -1864,7 +1327,7 @@ static void imx355_remove(struct i2c_client *client) + pm_runtime_set_suspended(imx355->dev); + } + +- mutex_destroy(&imx355->mutex); ++ pm_runtime_dont_use_autosuspend(imx355->dev); + } + + static const struct acpi_device_id imx355_acpi_ids[] __maybe_unused = { +diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c +new file mode 100644 +--- /dev/null ++++ b/drivers/media/i2c/imx471.c +@@ -0,0 +1,957 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * imx471.c - imx471 sensor driver ++ * ++ * Copyright (C) 2025 Intel Corporation ++ * Copyright (C) 2026 Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define IMX471_REG_MODE_SELECT CCI_REG8(0x0100) ++#define IMX471_MODE_STANDBY 0x00 ++#define IMX471_MODE_STREAMING 0x01 ++ ++/* Chip ID */ ++#define IMX471_REG_CHIP_ID CCI_REG16(0x0016) ++#define IMX471_CHIP_ID 0x0471 ++ ++/* V_TIMING internal */ ++#define IMX471_REG_FLL CCI_REG16(0x0340) ++#define IMX471_FLL_MAX 0xffff ++ ++/* Exposure control */ ++#define IMX471_REG_EXPOSURE CCI_REG16(0x0202) ++#define IMX471_EXPOSURE_MIN 1 ++#define IMX471_EXPOSURE_STEP 1 ++#define IMX471_EXPOSURE_DEFAULT 1270 ++ ++/* Default exposure margin */ ++#define IMX471_EXPOSURE_MARGIN 18 ++ ++/* Analog gain control */ ++#define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) ++#define IMX471_ANA_GAIN_MIN 0 ++#define IMX471_ANA_GAIN_MAX 800 ++#define IMX471_ANA_GAIN_STEP 1 ++#define IMX471_ANA_GAIN_DEFAULT 0 ++ ++/* Digital gain control */ ++#define IMX471_REG_DPGA_USE_GLOBAL_GAIN CCI_REG16(0x3ff9) ++#define IMX471_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) ++#define IMX471_DGTL_GAIN_MIN 256 ++#define IMX471_DGTL_GAIN_MAX 4095 ++#define IMX471_DGTL_GAIN_STEP 1 ++#define IMX471_DGTL_GAIN_DEFAULT 256 ++ ++/* HFLIP and VFLIP control */ ++#define IMX471_REG_ORIENTATION CCI_REG8(0x0101) ++ ++/* Test Pattern Control */ ++#define IMX471_REG_TEST_PATTERN CCI_REG8(0x0600) ++ ++/* default link frequency and external clock */ ++#define IMX471_LINK_FREQ_DEFAULT 200000000LL ++#define IMX471_EXT_CLK 19200000 ++ ++/* PLL */ ++#define IMX471_REG_VTPXCK_DIV CCI_REG8(0x0301) ++#define IMX471_REG_VTSYCK_DIV CCI_REG8(0x0303) ++#define IMX471_REG_PREPLLCK_VT_DIV CCI_REG8(0x0305) ++#define IMX471_REG_PLL_VT_MPY CCI_REG16(0x0306) ++#define IMX471_REG_OPPXCK_DIV CCI_REG8(0x0309) ++#define IMX471_REG_OPSYCK_DIV CCI_REG8(0x030b) ++#define IMX471_REG_PLL_MULT_DRIV CCI_REG8(0x0310) ++#define IMX471_PLL_SINGLE 0 ++#define IMX471_PLL_DUAL 1 ++ ++/* IMX471 native and active pixel array size */ ++#define IMX471_NATIVE_WIDTH 4672 ++#define IMX471_NATIVE_HEIGHT 3512 ++#define IMX471_PIXEL_ARRAY_LEFT 8 ++#define IMX471_PIXEL_ARRAY_TOP 8 ++#define IMX471_PIXEL_ARRAY_WIDTH 4656 ++#define IMX471_PIXEL_ARRAY_HEIGHT 3496 ++ ++#define IMX471_REG_EXCK_FREQ CCI_REG16(0x0136) ++ ++#define IMX471_REG_CSI_DATA_FORMAT CCI_REG16(0x0112) ++#define IMX471_CSI_DATA_FORMAT_RAW10 0x0a0a ++ ++#define IMX471_REG_CSI_LANE_MODE CCI_REG8(0x0114) ++#define IMX471_CSI_2_LANE_MODE 1 ++#define IMX471_CSI_4_LANE_MODE 3 ++ ++#define IMX471_REG_X_ADD_STA CCI_REG16(0x0344) ++#define IMX471_REG_Y_ADD_STA CCI_REG16(0x0346) ++#define IMX471_REG_X_ADD_END CCI_REG16(0x0348) ++#define IMX471_REG_Y_ADD_END CCI_REG16(0x034a) ++#define IMX471_REG_X_OUTPUT_SIZE CCI_REG16(0x034c) ++#define IMX471_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e) ++#define IMX471_REG_X_EVEN_INC CCI_REG8(0x0381) ++#define IMX471_REG_X_ODD_INC CCI_REG8(0x0383) ++#define IMX471_REG_Y_EVEN_INC CCI_REG8(0x0385) ++#define IMX471_REG_Y_ODD_INC CCI_REG8(0x0387) ++ ++#define IMX471_REG_DIG_CROP_X_OFFSET CCI_REG16(0x0408) ++#define IMX471_REG_DIG_CROP_Y_OFFSET CCI_REG16(0x040a) ++#define IMX471_REG_DIG_CROP_WIDTH CCI_REG16(0x040c) ++#define IMX471_REG_DIG_CROP_HEIGHT CCI_REG16(0x040e) ++ ++/* Binning mode */ ++#define IMX471_REG_BINNING_MODE CCI_REG8(0x0900) ++#define IMX471_BINNING_NONE 0 ++#define IMX471_BINNING_ENABLE 1 ++#define IMX471_REG_BINNING_TYPE CCI_REG8(0x0901) ++#define IMX471_REG_BINNING_WEIGHTING CCI_REG8(0x0902) ++ ++#define to_imx471(_sd) container_of_const(_sd, struct imx471, sd) ++ ++static const char * const imx471_supply_name[] = { ++ "vana", ++}; ++ ++struct imx471_mode { ++ u32 width; ++ u32 height; ++ ++ /* V-timing */ ++ u32 fll_def; ++ u32 fll_min; ++ ++ /* H-timing */ ++ u32 llp; ++ ++ const struct cci_reg_sequence *default_mode_regs; ++ unsigned int default_mode_regs_length; ++}; ++ ++struct imx471 { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ ++ struct v4l2_ctrl_handler ctrl_handler; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *exposure; ++ ++ struct gpio_desc *reset_gpio; ++ struct regulator_bulk_data supplies[ARRAY_SIZE(imx471_supply_name)]; ++ struct clk *img_clk; ++ ++ struct device *dev; ++ struct regmap *regmap; ++}; ++ ++static const struct cci_reg_sequence imx471_global_regs[] = { ++ { IMX471_REG_EXCK_FREQ, 0x1333 }, ++ { CCI_REG8(0x3c7e), 0x08 }, ++ { CCI_REG8(0x3c7f), 0x05 }, ++ { CCI_REG8(0x3e35), 0x00 }, ++ { CCI_REG8(0x3e36), 0x00 }, ++ { CCI_REG8(0x3e37), 0x00 }, ++ { CCI_REG8(0x3f7f), 0x01 }, ++ { CCI_REG8(0x4431), 0x04 }, ++ { CCI_REG8(0x531c), 0x01 }, ++ { CCI_REG8(0x531d), 0x02 }, ++ { CCI_REG8(0x531e), 0x04 }, ++ { CCI_REG8(0x5928), 0x00 }, ++ { CCI_REG8(0x5929), 0x2f }, ++ { CCI_REG8(0x592a), 0x00 }, ++ { CCI_REG8(0x592b), 0x85 }, ++ { CCI_REG8(0x592c), 0x00 }, ++ { CCI_REG8(0x592d), 0x32 }, ++ { CCI_REG8(0x592e), 0x00 }, ++ { CCI_REG8(0x592f), 0x88 }, ++ { CCI_REG8(0x5930), 0x00 }, ++ { CCI_REG8(0x5931), 0x3d }, ++ { CCI_REG8(0x5932), 0x00 }, ++ { CCI_REG8(0x5933), 0x93 }, ++ { CCI_REG8(0x5938), 0x00 }, ++ { CCI_REG8(0x5939), 0x24 }, ++ { CCI_REG8(0x593a), 0x00 }, ++ { CCI_REG8(0x593b), 0x7a }, ++ { CCI_REG8(0x593c), 0x00 }, ++ { CCI_REG8(0x593d), 0x24 }, ++ { CCI_REG8(0x593e), 0x00 }, ++ { CCI_REG8(0x593f), 0x7a }, ++ { CCI_REG8(0x5940), 0x00 }, ++ { CCI_REG8(0x5941), 0x2f }, ++ { CCI_REG8(0x5942), 0x00 }, ++ { CCI_REG8(0x5943), 0x85 }, ++ { CCI_REG8(0x5f0e), 0x6e }, ++ { CCI_REG8(0x5f11), 0xc6 }, ++ { CCI_REG8(0x5f17), 0x5e }, ++ { CCI_REG8(0x7990), 0x01 }, ++ { CCI_REG8(0x7993), 0x5d }, ++ { CCI_REG8(0x7994), 0x5d }, ++ { CCI_REG8(0x7995), 0xa1 }, ++ { CCI_REG8(0x799a), 0x01 }, ++ { CCI_REG8(0x799d), 0x00 }, ++ { CCI_REG8(0x8169), 0x01 }, ++ { CCI_REG8(0x8359), 0x01 }, ++ { CCI_REG8(0x9302), 0x1e }, ++ { CCI_REG8(0x9306), 0x1f }, ++ { CCI_REG8(0x930a), 0x26 }, ++ { CCI_REG8(0x930e), 0x23 }, ++ { CCI_REG8(0x9312), 0x23 }, ++ { CCI_REG8(0x9316), 0x2c }, ++ { CCI_REG8(0x9317), 0x19 }, ++ { CCI_REG8(0xb046), 0x01 }, ++ { CCI_REG8(0xb048), 0x01 }, ++}; ++ ++static const struct cci_reg_sequence mode_1928x1088_regs[] = { ++ { IMX471_REG_CSI_DATA_FORMAT, IMX471_CSI_DATA_FORMAT_RAW10 }, ++ { IMX471_REG_CSI_LANE_MODE, IMX471_CSI_4_LANE_MODE }, ++ { IMX471_REG_X_ADD_STA, 8 }, ++ { IMX471_REG_Y_ADD_STA, 408 }, ++ { IMX471_REG_X_ADD_END, 4647 }, ++ { IMX471_REG_Y_ADD_END, 3051 }, ++ { IMX471_REG_X_EVEN_INC, 1 }, ++ { IMX471_REG_X_ODD_INC, 1 }, ++ { IMX471_REG_Y_EVEN_INC, 1 }, ++ { IMX471_REG_Y_ODD_INC, 1 }, ++ { IMX471_REG_BINNING_MODE, IMX471_BINNING_ENABLE }, ++ { IMX471_REG_BINNING_TYPE, 0x22 }, ++ { IMX471_REG_BINNING_WEIGHTING, 0x08 }, ++ { IMX471_REG_DIG_CROP_X_OFFSET, 208 }, ++ { IMX471_REG_DIG_CROP_Y_OFFSET, 108 }, ++ { IMX471_REG_DIG_CROP_WIDTH, 1928 }, ++ { IMX471_REG_DIG_CROP_HEIGHT, 1088 }, ++ { IMX471_REG_X_OUTPUT_SIZE, 1928 }, ++ { IMX471_REG_Y_OUTPUT_SIZE, 1088 }, ++ { IMX471_REG_VTPXCK_DIV, 0x06 }, ++ { IMX471_REG_VTSYCK_DIV, 0x02 }, ++ { IMX471_REG_PREPLLCK_VT_DIV, 0x02 }, ++ { IMX471_REG_PLL_VT_MPY, 0x0079 }, ++ { IMX471_REG_OPSYCK_DIV, 0x01 }, ++ { CCI_REG8(0x030d), 0x02 }, ++ { CCI_REG8(0x030e), 0x00 }, ++ { CCI_REG8(0x030f), 0x53 }, ++ { IMX471_REG_PLL_MULT_DRIV, IMX471_PLL_DUAL }, ++ { CCI_REG8(0x3f4c), 0x81 }, ++ { CCI_REG8(0x3f4d), 0x81 }, ++ { CCI_REG8(0x3f78), 0x01 }, ++ { CCI_REG8(0x3f79), 0x31 }, ++ { CCI_REG8(0x3ffe), 0x00 }, ++ { CCI_REG8(0x3fff), 0x8a }, ++ { CCI_REG8(0x5f0a), 0xb6 }, ++}; ++ ++static const char * const imx471_test_pattern_menu[] = { ++ "Disabled", ++ "Solid Colour", ++ "Eight Vertical Colour Bars", ++ "Colour Bars With Fade to Grey", ++ "Pseudorandom Sequence (PN9)", ++}; ++ ++static const s64 link_freq_menu_items[] = { ++ IMX471_LINK_FREQ_DEFAULT, ++}; ++ ++/* ++ * The Bayer formats for the flipping. ++ * - no flip ++ * - h flip ++ * - v flip ++ * - h and v flips ++ */ ++static const u32 imx471_hv_flips_bayer_order[] = { ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++}; ++ ++static const struct imx471_mode imx471_modes[] = { ++ { ++ .width = 1928, ++ .height = 1088, ++ .fll_def = 1308, ++ .fll_min = 1308, ++ .llp = 2328, ++ .default_mode_regs = mode_1928x1088_regs, ++ .default_mode_regs_length = ARRAY_SIZE(mode_1928x1088_regs), ++ }, ++}; ++ ++static int imx471_get_regulators(struct device *dev, struct imx471 *sensor) ++{ ++ for (unsigned int i = 0; i < ARRAY_SIZE(imx471_supply_name); i++) ++ sensor->supplies[i].supply = imx471_supply_name[i]; ++ ++ return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++} ++ ++static int imx471_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct imx471 *sensor = container_of_const(ctrl->handler, ++ struct imx471, ++ ctrl_handler); ++ struct v4l2_subdev_state *state = ++ v4l2_subdev_get_locked_active_state(&sensor->sd); ++ const struct v4l2_mbus_framefmt *format = ++ v4l2_subdev_state_get_format(state, 0); ++ int ret; ++ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ s64 exposure_max = format->height + ctrl->val - ++ IMX471_EXPOSURE_MARGIN; ++ ret = __v4l2_ctrl_modify_range(sensor->exposure, ++ sensor->exposure->minimum, ++ exposure_max, ++ sensor->exposure->step, ++ exposure_max); ++ if (ret) ++ return ret; ++ } ++ ++ if (!pm_runtime_get_if_in_use(sensor->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = cci_write(sensor->regmap, IMX471_REG_ANALOG_GAIN, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = cci_write(sensor->regmap, IMX471_REG_DIG_GAIN_GLOBAL, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = cci_write(sensor->regmap, IMX471_REG_EXPOSURE, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_VBLANK: ++ /* Update FLL that meets expected vertical blanking */ ++ ret = cci_write(sensor->regmap, IMX471_REG_FLL, ++ format->height + ctrl->val, NULL); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = cci_write(sensor->regmap, IMX471_REG_TEST_PATTERN, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_HFLIP: ++ case V4L2_CID_VFLIP: ++ ret = cci_write(sensor->regmap, IMX471_REG_ORIENTATION, ++ sensor->hflip->val | sensor->vflip->val << 1, ++ NULL); ++ break; ++ default: ++ ret = -EINVAL; ++ dev_err(sensor->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n", ++ ctrl->id, ctrl->val); ++ break; ++ } ++ ++ pm_runtime_put(sensor->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops imx471_ctrl_ops = { ++ .s_ctrl = imx471_set_ctrl, ++}; ++ ++static u32 imx471_get_format_code(struct imx471 *sensor) ++{ ++ unsigned int i; ++ ++ i = (sensor->vflip->val ? 2 : 0) | (sensor->hflip->val ? 1 : 0); ++ ++ return imx471_hv_flips_bayer_order[i]; ++} ++ ++static int imx471_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ ++ if (code->index >= (ARRAY_SIZE(imx471_hv_flips_bayer_order) / 4)) ++ return -EINVAL; ++ ++ code->code = imx471_get_format_code(sensor); ++ ++ return 0; ++} ++ ++static int imx471_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->index >= ARRAY_SIZE(imx471_modes)) ++ return -EINVAL; ++ ++ fse->min_width = imx471_modes[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = imx471_modes[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static void imx471_update_pad_format(struct imx471 *sensor, ++ const struct imx471_mode *mode, ++ struct v4l2_subdev_format *fmt) ++{ ++ fmt->format.code = imx471_get_format_code(sensor); ++ fmt->format.width = mode->width; ++ fmt->format.height = mode->height; ++ fmt->format.field = V4L2_FIELD_NONE; ++} ++ ++static int imx471_set_pad_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ const struct imx471_mode *mode; ++ int h_blank, ret; ++ ++ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes), ++ width, height, fmt->format.width, ++ fmt->format.height); ++ ++ imx471_update_pad_format(sensor, mode, fmt); ++ ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ return 0; ++ ++ if (media_entity_is_streaming(&sensor->sd.entity)) ++ return -EBUSY; ++ ++ ret = __v4l2_ctrl_modify_range(sensor->vblank, ++ mode->fll_min - mode->height, ++ IMX471_FLL_MAX - mode->height, ++ 1, ++ mode->fll_def - mode->height); ++ if (ret) ++ return ret; ++ ++ h_blank = mode->llp - mode->width; ++ /* ++ * Currently hblank is not changeable. ++ * So FPS control is done only by vblank. ++ */ ++ return __v4l2_ctrl_modify_range(sensor->hblank, h_blank, ++ h_blank, 1, h_blank); ++} ++ ++static int imx471_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad); ++ break; ++ ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = IMX471_NATIVE_WIDTH; ++ sel->r.height = IMX471_NATIVE_HEIGHT; ++ return 0; ++ ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.top = IMX471_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX471_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX471_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX471_PIXEL_ARRAY_HEIGHT; ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx471_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_TRY, ++ .format = { ++ .code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .width = imx471_modes[0].width, ++ .height = imx471_modes[0].height, ++ }, ++ }; ++ ++ return imx471_set_pad_format(sd, sd_state, &fmt); ++} ++ ++static int imx471_identify_module(struct imx471 *sensor) ++{ ++ int ret; ++ u64 val; ++ ++ ret = cci_read(sensor->regmap, IMX471_REG_CHIP_ID, &val, NULL); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to read chip id\n"); ++ ++ if (val != IMX471_CHIP_ID) ++ return dev_err_probe(sensor->dev, -EIO, ++ "chip id mismatch: %x!=%llx\n", ++ IMX471_CHIP_ID, val); ++ ++ return 0; ++} ++ ++static int imx471_power_off(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx471 *sensor = to_imx471(sd); ++ ++ clk_disable_unprepare(sensor->img_clk); ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ ++ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ ++ return 0; ++} ++ ++static int imx471_power_on(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx471 *sensor = to_imx471(sd); ++ int ret; ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ if (ret < 0) { ++ dev_err(dev, "failed to enable regulators: %d\n", ret); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(sensor->img_clk); ++ if (ret < 0) { ++ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ dev_err(dev, "failed to enable imaging clock: %d\n", ret); ++ return ret; ++ } ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ ++ usleep_range(10000, 15000); ++ ++ return 0; ++} ++ ++static int imx471_enable_stream(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ const struct imx471_mode *mode; ++ struct v4l2_mbus_framefmt *fmt; ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(sensor->dev); ++ if (ret) ++ return ret; ++ ++ ret = imx471_identify_module(sensor); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = cci_multi_reg_write(sensor->regmap, imx471_global_regs, ++ ARRAY_SIZE(imx471_global_regs), NULL); ++ if (ret) { ++ dev_err(sensor->dev, "failed to set global settings: %d\n", ++ ret); ++ goto error_powerdown; ++ } ++ ++ fmt = v4l2_subdev_state_get_format(state, 0); ++ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes), ++ width, height, fmt->width, fmt->height); ++ ++ ret = cci_multi_reg_write(sensor->regmap, mode->default_mode_regs, ++ mode->default_mode_regs_length, NULL); ++ if (ret) { ++ dev_err(sensor->dev, "failed to set mode: %d\n", ret); ++ goto error_powerdown; ++ } ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_DPGA_USE_GLOBAL_GAIN, 1, ++ NULL); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = __v4l2_ctrl_handler_setup(&sensor->ctrl_handler); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT, ++ IMX471_MODE_STREAMING, NULL); ++ if (ret) ++ goto error_powerdown; ++ ++ __v4l2_ctrl_grab(sensor->vflip, true); ++ __v4l2_ctrl_grab(sensor->hflip, true); ++ ++ return ret; ++ ++error_powerdown: ++ pm_runtime_put(sensor->dev); ++ ++ return ret; ++} ++ ++static int imx471_disable_stream(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ int ret; ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT, ++ IMX471_MODE_STANDBY, NULL); ++ pm_runtime_put(sensor->dev); ++ ++ if (ret) ++ dev_err(sensor->dev, ++ "failed to disable stream with return value: %d\n", ++ ret); ++ ++ __v4l2_ctrl_grab(sensor->vflip, false); ++ __v4l2_ctrl_grab(sensor->hflip, false); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops imx471_video_ops = { ++ .s_stream = v4l2_subdev_s_stream_helper, ++}; ++ ++static const struct v4l2_subdev_pad_ops imx471_pad_ops = { ++ .enum_mbus_code = imx471_enum_mbus_code, ++ .get_fmt = v4l2_subdev_get_fmt, ++ .set_fmt = imx471_set_pad_format, ++ .get_selection = imx471_get_selection, ++ .enum_frame_size = imx471_enum_frame_size, ++ .enable_streams = imx471_enable_stream, ++ .disable_streams = imx471_disable_stream, ++}; ++ ++static const struct v4l2_subdev_ops imx471_subdev_ops = { ++ .video = &imx471_video_ops, ++ .pad = &imx471_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops imx471_internal_ops = { ++ .init_state = imx471_init_state, ++}; ++ ++static int imx471_init_controls(struct imx471 *sensor) ++{ ++ const struct imx471_mode *mode = &imx471_modes[0]; ++ struct v4l2_fwnode_device_properties props; ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ struct v4l2_ctrl *link_freq; ++ s64 exposure_max, hblank; ++ u64 pixel_rate; ++ int ret; ++ ++ ret = v4l2_fwnode_device_parse(sensor->dev, &props); ++ if (ret) { ++ dev_err(sensor->dev, "failed to parse fwnode: %d\n", ret); ++ return ret; ++ } ++ ++ ctrl_hdlr = &sensor->ctrl_handler; ++ v4l2_ctrl_handler_init(ctrl_hdlr, 12); ++ ++ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx471_ctrl_ops, &props); ++ ++ link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, ++ &imx471_ctrl_ops, ++ V4L2_CID_LINK_FREQ, ++ ARRAY_SIZE(link_freq_menu_items) - 1, ++ 0, ++ link_freq_menu_items); ++ ++ /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ ++ pixel_rate = div_u64(IMX471_LINK_FREQ_DEFAULT * 2 * 4, 10); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_PIXEL_RATE, pixel_rate, ++ pixel_rate, 1, pixel_rate); ++ ++ sensor->vblank = v4l2_ctrl_new_std(ctrl_hdlr, ++ &imx471_ctrl_ops, ++ V4L2_CID_VBLANK, ++ mode->fll_min - mode->height, ++ IMX471_FLL_MAX - mode->height, ++ 1, ++ mode->fll_def - mode->height); ++ ++ hblank = mode->llp - mode->width; ++ sensor->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_HBLANK, hblank, hblank, ++ 1, hblank); ++ ++ /* fll >= exposure time + adjust parameter (default value is 18) */ ++ exposure_max = mode->fll_def - IMX471_EXPOSURE_MARGIN; ++ sensor->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_EXPOSURE, ++ IMX471_EXPOSURE_MIN, exposure_max, ++ IMX471_EXPOSURE_STEP, ++ IMX471_EXPOSURE_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, ++ IMX471_ANA_GAIN_MIN, IMX471_ANA_GAIN_MAX, ++ IMX471_ANA_GAIN_STEP, IMX471_ANA_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_DIGITAL_GAIN, ++ IMX471_DGTL_GAIN_MIN, IMX471_DGTL_GAIN_MAX, ++ IMX471_DGTL_GAIN_STEP, IMX471_DGTL_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(imx471_test_pattern_menu) - 1, ++ 0, 0, imx471_test_pattern_menu); ++ ++ sensor->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ ++ sensor->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ ++ if (ctrl_hdlr->error) { ++ dev_err(sensor->dev, "%s control init failed: %d\n", ++ __func__, ctrl_hdlr->error); ++ goto error; ++ } ++ ++ link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ ++ sensor->sd.ctrl_handler = ctrl_hdlr; ++ ++ return 0; ++ ++error: ++ v4l2_ctrl_handler_free(ctrl_hdlr); ++ ++ return ctrl_hdlr->error; ++} ++ ++static int imx471_check_hwcfg(struct imx471 *sensor) ++{ ++ struct v4l2_fwnode_endpoint bus_cfg = { ++ .bus_type = V4L2_MBUS_CSI2_DPHY, ++ }; ++ struct fwnode_handle *ep, *fwnode = dev_fwnode(sensor->dev); ++ unsigned long link_freq_bitmap; ++ struct clk *clk; ++ int ret; ++ ++ clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL); ++ if (IS_ERR(clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(clk), ++ "can't get clock frequency\n"); ++ ++ if (clk_get_rate(clk) != IMX471_EXT_CLK) ++ return dev_err_probe(sensor->dev, -EINVAL, ++ "external clock %lu is not supported\n", ++ clk_get_rate(clk)); ++ ++ ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, 0); ++ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); ++ fwnode_handle_put(ep); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "parsing endpoint failed\n"); ++ ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { ++ ret = dev_err_probe(sensor->dev, -EINVAL, ++ "number of CSI2 data lanes %u is not supported\n", ++ bus_cfg.bus.mipi_csi2.num_data_lanes); ++ goto done_endpoint_free; ++ } ++ ++ ret = v4l2_link_freq_to_bitmap(sensor->dev, bus_cfg.link_frequencies, ++ bus_cfg.nr_of_link_frequencies, ++ link_freq_menu_items, ++ ARRAY_SIZE(link_freq_menu_items), ++ &link_freq_bitmap); ++ ++done_endpoint_free: ++ v4l2_fwnode_endpoint_free(&bus_cfg); ++ ++ return ret; ++} ++ ++static int imx471_probe(struct i2c_client *client) ++{ ++ struct imx471 *sensor; ++ int ret; ++ ++ sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return dev_err_probe(&client->dev, -ENOMEM, ++ "failed to allocate memory\n"); ++ ++ sensor->dev = &client->dev; ++ ++ ret = imx471_check_hwcfg(sensor); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to check hwcfg: %d\n", ret); ++ ++ ret = imx471_get_regulators(sensor->dev, sensor); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to get regulators\n"); ++ ++ sensor->reset_gpio = devm_gpiod_get_optional(sensor->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset_gpio), ++ "failed to get reset gpio\n"); ++ ++ sensor->img_clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL); ++ if (IS_ERR(sensor->img_clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->img_clk), ++ "failed to get imaging clock\n"); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &imx471_subdev_ops); ++ ++ sensor->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(sensor->regmap)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->regmap), ++ "failed to initialize CCI\n"); ++ ++ ret = imx471_power_on(sensor->dev); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to power on\n"); ++ ++ ret = imx471_identify_module(sensor); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, "failed to find sensor: %d\n", ++ ret); ++ goto error_power_off; ++ } ++ ++ ret = imx471_init_controls(sensor); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, "failed to init controls: %d\n", ++ ret); ++ goto error_power_off; ++ } ++ ++ sensor->sd.internal_ops = &imx471_internal_ops; ++ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, ++ "failed to init entity pads: %d\n", ret); ++ goto error_v4l2_ctrl_handler_free; ++ } ++ ++ sensor->sd.state_lock = sensor->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&sensor->sd); ++ if (ret < 0) { ++ dev_err_probe(sensor->dev, ret, "failed to init subdev: %d\n", ++ ret); ++ goto error_media_entity_pm; ++ } ++ ++ pm_runtime_set_active(sensor->dev); ++ pm_runtime_enable(sensor->dev); ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret < 0) ++ goto error_v4l2_subdev_cleanup; ++ ++ pm_runtime_idle(sensor->dev); ++ ++ return 0; ++ ++error_v4l2_subdev_cleanup: ++ pm_runtime_disable(sensor->dev); ++ pm_runtime_set_suspended(sensor->dev); ++ v4l2_subdev_cleanup(&sensor->sd); ++ ++error_media_entity_pm: ++ media_entity_cleanup(&sensor->sd.entity); ++ ++error_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); ++ ++error_power_off: ++ imx471_power_off(sensor->dev); ++ ++ return ret; ++} ++ ++static void imx471_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ ++ v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&sd->entity); ++ v4l2_ctrl_handler_free(sd->ctrl_handler); ++ ++ pm_runtime_disable(&client->dev); ++ ++ if (!pm_runtime_status_suspended(&client->dev)) { ++ imx471_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++ } ++} ++ ++static DEFINE_RUNTIME_DEV_PM_OPS(imx471_pm_ops, imx471_power_off, ++ imx471_power_on, NULL); ++ ++static const struct acpi_device_id imx471_acpi_ids[] __maybe_unused = { ++ { "SONY471A" }, ++ { "TBE20A0" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(acpi, imx471_acpi_ids); ++ ++static struct i2c_driver imx471_i2c_driver = { ++ .driver = { ++ .name = "imx471", ++ .acpi_match_table = ACPI_PTR(imx471_acpi_ids), ++ .pm = pm_sleep_ptr(&imx471_pm_ops), ++ }, ++ .probe = imx471_probe, ++ .remove = imx471_remove, ++}; ++module_i2c_driver(imx471_i2c_driver); ++ ++MODULE_AUTHOR("Jimmy Su "); ++MODULE_AUTHOR("Serin Yeh "); ++MODULE_AUTHOR("Kate Hsuan "); ++MODULE_DESCRIPTION("Sony imx471 sensor driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c +--- a/drivers/media/i2c/ov2740.c ++++ b/drivers/media/i2c/ov2740.c +@@ -1241,41 +1241,42 @@ static int ov2740_nvmem_read(void *priv, unsigned int off, void *val, + return ret; + } + ++static const struct regmap_config regmap_config = { ++ .val_bits = 8, ++ .reg_bits = 16, ++ .disable_locking = true, ++}; ++ + static int ov2740_register_nvmem(struct i2c_client *client, + struct ov2740 *ov2740) + { + struct nvm_data *nvm; +- struct regmap_config regmap_config = { }; +- struct nvmem_config nvmem_config = { }; + struct regmap *regmap; + struct device *dev = ov2740->dev; ++ struct nvmem_config nvmem_config = { ++ .name = dev_name(dev), ++ .dev = dev, ++ .read_only = true, ++ .root_only = true, ++ .owner = THIS_MODULE, ++ .compat = true, ++ .base_dev = dev, ++ .reg_read = ov2740_nvmem_read, ++ .stride = 1, ++ .word_size = 1, ++ .size = CUSTOMER_USE_OTP_SIZE, ++ }; + + nvm = devm_kzalloc(dev, sizeof(*nvm), GFP_KERNEL); + if (!nvm) + return -ENOMEM; + +- regmap_config.val_bits = 8; +- regmap_config.reg_bits = 16; +- regmap_config.disable_locking = true; + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvm->regmap = regmap; +- +- nvmem_config.name = dev_name(dev); +- nvmem_config.dev = dev; +- nvmem_config.read_only = true; +- nvmem_config.root_only = true; +- nvmem_config.owner = THIS_MODULE; +- nvmem_config.compat = true; +- nvmem_config.base_dev = dev; +- nvmem_config.reg_read = ov2740_nvmem_read; +- nvmem_config.reg_write = NULL; + nvmem_config.priv = nvm; +- nvmem_config.stride = 1; +- nvmem_config.word_size = 1; +- nvmem_config.size = CUSTOMER_USE_OTP_SIZE; + + nvm->nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvm->nvmem)) +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -49,6 +49,8 @@ + * Please keep the list sorted by ACPI HID. + */ + static const struct ipu_sensor_config ipu_supported_sensors[] = { ++ /* Himax HM1092 */ ++ IPU_SENSOR_CONFIG("HIMX1092", 2, 180000000, 180480000), + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ +@@ -97,6 +99,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), + /* Sony IMX471 */ + IPU_SENSOR_CONFIG("SONY471A", 1, 200000000), ++ /* Sony IMX471 (found on Lenovo X1 Carbon G14) */ ++ IPU_SENSOR_CONFIG("TBE20A0", 1, 200000000), + /* Toshiba T4KA3 */ + IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), + }; +@@ -194,8 +198,8 @@ static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev + for (i = 0; i < ARRAY_SIZE(ivsc_acpi_ids); i++) { + const struct acpi_device_id *acpi_id = &ivsc_acpi_ids[i]; + struct acpi_device *consumer, *ivsc_adev; +- + acpi_handle handle = acpi_device_handle(ACPI_PTR(adev)); ++ + for_each_acpi_dev_match(ivsc_adev, acpi_id->id, NULL, -1) + /* camera sensor depends on IVSC in DSDT if exist */ + for_each_acpi_consumer_dev(ivsc_adev, consumer) diff --git a/pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch.sig b/pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..2f69b33dcba4b687535e6e8c2a0e1c5693d430f9 GIT binary patch literal 594 zcmV-Y0A-6D zu6zQ{GE?u)otHCYJq4pbl$#b1AzAb%^-;nl!oz7bt8`yYfT$Rbsd0^OBw zJkPQxlEe+1rB83iqqwV&g0bWM6m1@py~QKRrBUlaHa)V(RoqM@Tb%wn9oyeDTGir{ z_8=37L8#|{*0G`-a+l5g@^zsN&>TVAWugptW8Q)TJ|VuCBJv?hlm0>K}J#m#mhhcSwZ;AAi z^R24~vMmMYbPUS*+AO8dj}Dc+2baolFDuc5 zg^{@MV9kw z4wJ0Zka!@iR`t8pOj{&HO$;&1!Gze;?a}hPpJ0GmO0WS7UpZYrR8a|?$EU@yk(YSX z!`2fW|2*DDlxs-{USmeT&5zY0lKzxvxFswJ+H=BQ87FacB^7 zUBwfLHa8#@pW?=TN#Yc75jhLY(h4X!*&~USf+38q10ys~!7JJDYOY~Gp)}7MZxh=U g)Jzb4?4GH!WtY;oF0d5G)QrVeW5@J^wxhwffhHRsVE_OC literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch b/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch new file mode 100644 index 0000000..7c15eb7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch @@ -0,0 +1,17 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -138,6 +138,13 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "OVTI02C1", + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 16 (Dell 16 Premium) DA16250"), ++ }, ++ .driver_data = "OVTI02C1", ++ }, + /* + * The first four characters of DMI_BOARD_NAME identify the Lenovo + * machine type/model. For example, a DMI_BOARD_NAME starting with diff --git a/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig b/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..1c5aff21f4cad815728b6ffaad7ee30481cb3de9 GIT binary patch literal 594 zcmV-Y00I+NU5vSPUFpL;wd|SC2 zPe1=mK0naPL}@ug+L@$V&|wY1JF6>5-Lo}g)VuDl&)FcXtw}rBV_wRS?1R|Uxmy0ZBw|D>*wVp}>vA8h(L>L;200FFCSI&+JkAw=GKS{C zF*Ga#s@9bU0#aUfXyn0WNZ?%3S%+jk8xHpC17nLtd(zjp3UI^T$$S5Yo=W$Smsu;} z+DVUl#VF*_6j=xW>};DORK|;VqYByX;_4&Mr+uM${R7{Q3Z`xRhBT#X-rTe|oKUj^zDW*vu&}Oqv zhh7}|@ZM43rn@F8bL0j&ph-?cY7wY)Qi#jU$7EUbC2=Ym;-xE;^hvHofC0`F6J{{( ge^9+eU_P6K_lkElIy4ip=ILi}Cs=77o&W#< literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch b/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch new file mode 100644 index 0000000..a42d186 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -184,6 +184,14 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "SONY471A", + }, ++ { ++ /* Samsung Galaxy Book5 Pro 360 */ ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "960QHA"), ++ }, ++ .driver_data = "OVTI02E1", ++ }, + {} /* Terminating entry */ + }; + diff --git a/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig b/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..537609911ea12508949411b76d178d5e3a6edf77 GIT binary patch literal 594 zcmV-Y0Mkxo_W1-Q5glj(wo(}le=GXTFK|x`G~Neve_~u#jqz9(IkpvPF*sO^#_a(AiAgqS1v^?vwSj)5~ zHxLmeV8$un3U>jD*$EoNX~C%FEv+jo2rjS+ep$F|uOV8L5TXmXY8$(Gc59XU&=+gR zQx$z5BN;wvhf1=*J9RI<$UO-)OFSrT^6xV&d=%KlAACeIO-qu%k80(69->3_FOE^> zMSNGKlc1d3_vp!`op!04cuFnq@txUqsykX8d+zw&qb$VY*+p#cv!Jn@jyN)Fot3%B gHSAA0x;nwJe23p~1dcMo0Ov6rB>X`xDsUk3P5QJGS^xk5 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch b/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch new file mode 100644 index 0000000..db38adc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -164,6 +164,24 @@ static const struct int3472_gpio_map int3472_gpio_map[] = { + .con_id = "dvdd", + .enable_time_us = 45 * USEC_PER_MSEC, + }, ++ { /* imx471 expects "vana" as con_id for power enable */ ++ .hid = "SONY471A", ++ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .con_id = "vana", ++ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, ++ }, ++ { ++ /* ++ * imx471 (on Lenovo ThinkPads X1 G14) expects "vana" as con_id ++ * for power enable ++ */ ++ .hid = "TBE20A0", ++ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .con_id = "vana", ++ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, ++ }, + }; + + static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type, diff --git a/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch.sig b/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..b2651bd56ab6e85c43e3fbd3fc59f79770073cce GIT binary patch literal 594 zcmV-Y0>COh>d!LxQp7`vO8NhN zQ8tm^kKLS1HdNBXWBK2fHe=jBy}=$hkS z2~mW90(H}4?r`~+-TPeBEJtVf)|18B+c7>J#jL-nKk@URIKc3|@Sx*aqCU&>V&za$ zeaDCOYL`q<#oOtk)rAsqZ~j zpNWLSf?!R-dIEdO7`?H_hlWDY^1ZP`gLOgquOI^KHXzBZ>r3CaNh0JdSJn2ag*>K0 g(z3?U9O+bl_dw)t1bi2(nz3dg9_Bl@JaoCjbBd literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch b/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch new file mode 100644 index 0000000..b041d90 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch @@ -0,0 +1,37 @@ +diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c +--- a/drivers/media/i2c/cvs/v4l2.c ++++ b/drivers/media/i2c/cvs/v4l2.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -400,7 +401,7 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_async_connection *asc) + { + struct icvs *ctx = notifier_to_csi(notifier); +- int pad; ++ int pad, ret; + + pad = media_entity_get_fwnode_pad(&sd->entity, asc->match.fwnode, + MEDIA_PAD_FL_SOURCE); +@@ -409,9 +410,13 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + + ctx->remote = &sd->entity.pads[pad]; + +- return media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, +- ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | +- MEDIA_LNK_FL_IMMUTABLE); ++ ret = media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, ++ ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | ++ MEDIA_LNK_FL_IMMUTABLE); ++ if (ret) ++ return ret; ++ ++ return v4l2_device_register_subdev_nodes(sd->v4l2_dev); + } + + /** diff --git a/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch.sig b/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..b58a4dd84ffc77c921c3b66df194fb11061c8174 GIT binary patch literal 594 zcmV-Y0`% zB5`oWSMt`1xh7SKut-h62{cVb1awXpJJQw@foTzVI8yf~V_LPY)}NjHmsP!TeqxdO zr52hl7MvHCq8tH35CQ}B{oR>x)6PZtyeYQ-*3yJA3~v>@{Qeu@!4oxhBw~4G6|kvT z4Z)UcNC{qVvXa3-yp^*~K212M;iJhn>Gyk`>=+ZHt!GYSDYiq4pw#h~btFQfin;g4BZbCAJj>>&^c79` zlVfi@OV5J$<%B%Y3im*%f-K(<+)QWwyDp$ICnU+~@lZE%7lAbfke$d85i82RoGJBf z-;~q;ih zWd|9yNDgNi8(zqKF>y!?B5On^n_x7O+J5226lK}o`NnHkM^qkSKmzO?D*E6}eTvk6 gNb9g_u>DF~l?wp4q|3DH%k;B8XExdG9Ak7oM?5AL=>Px# literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch b/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch new file mode 100644 index 0000000..a11c4f2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c +--- a/drivers/media/i2c/imx471.c ++++ b/drivers/media/i2c/imx471.c +@@ -41,7 +41,7 @@ + /* Analog gain control */ + #define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) + #define IMX471_ANA_GAIN_MIN 0 +-#define IMX471_ANA_GAIN_MAX 800 ++#define IMX471_ANA_GAIN_MAX 960 + #define IMX471_ANA_GAIN_STEP 1 + #define IMX471_ANA_GAIN_DEFAULT 0 + diff --git a/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch.sig b/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..283ab9e5442871518a2ff2b74f4a279d4908a1cc GIT binary patch literal 594 zcmV-Y0w=*A^teW$-x^6Wg zf2+zGK&jB%v&13ByKTOEDqzrqi)DxPFIFu~JhLSC*5uBht82hIFJ7G-=7B?R0hZW; zqlp>c{Wx1Nt;BVVpUJvAH)q1)?jpjb{(3Nqz8$3fOL5+Kb6#@2Y(n9wse8q(p8;KC z8`;e-VKX{oAVgXlt5es@|yO(4i=(q)-Z#_QCOd>Qwct6-m-jT5RvJpmg6Y&p?cm;g|jC9aQBm(wVehVWnGJ>^{MLY=m z*0S%zh^I;C0ie3pQb+lc2b(4u73FPtTWd*bTl7nMX}CE0Q1;U^qY`f>5sxMR6l zm-)eE~1hffIOAfV+ue5>mit3z1E28DA;7&a-i%~axwY`2P=dec|9E}yu z|4TTJH(VYK4CNmxyi`YfMcEWzgUTWvqZ^IepF`>x!pTHnenN2}bhZokZ18kB#_X7Y g3=jvXy`&3F;t$brYPdn*B{xYY|1yvqePi^jvZk~bEdT%j literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch b/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch new file mode 100644 index 0000000..d8548c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch @@ -0,0 +1,3869 @@ +diff --git a/drivers/input/input.c b/drivers/input/input.c +--- a/drivers/input/input.c ++++ b/drivers/input/input.c +@@ -318,7 +318,7 @@ static int input_get_disposition(struct input_dev *dev, + static void input_event_dispose(struct input_dev *dev, int disposition, + unsigned int type, unsigned int code, int value) + { +- if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) ++ if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event && dev->ready) + dev->event(dev, type, code, value); + + if (disposition & INPUT_PASS_TO_HANDLERS) { +@@ -568,6 +568,75 @@ void input_release_device(struct input_handle *handle) + } + EXPORT_SYMBOL(input_release_device); + ++#define INPUT_DO_TOGGLE(dev, type, bits, on) \ ++ do { \ ++ int i; \ ++ bool active; \ ++ \ ++ if (!test_bit(EV_##type, dev->evbit)) \ ++ break; \ ++ \ ++ for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ ++ active = test_bit(i, dev->bits); \ ++ if (!active && !on) \ ++ continue; \ ++ \ ++ dev->event(dev, EV_##type, i, on ? active : 0); \ ++ } \ ++ } while (0) ++ ++/* ++ * Iterate through the logical state of the input device (LEDs, sounds, ++ * auto-repeat) and explicitly push that state down to the hardware ++ * via dev->event() to match the current logical state (if activate is true), ++ * or forcibly turn off all feedback like LEDs and sounds during teardown ++ * or suspend (if activate is false). ++ * ++ * Primarily used as a state-replay mechanism after a device is opened ++ * or uninhibited, as events might have been dropped by the core while the ++ * hardware was not marked as ready. ++ */ ++static void input_dev_toggle(struct input_dev *dev, bool activate) ++{ ++ if (!dev->event || !dev->ready) ++ return; ++ ++ INPUT_DO_TOGGLE(dev, LED, led, activate); ++ INPUT_DO_TOGGLE(dev, SND, snd, activate); ++ ++ if (activate && test_bit(EV_REP, dev->evbit)) { ++ dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); ++ dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); ++ } ++} ++ ++static int input_start_device(struct input_dev *dev) ++{ ++ int error; ++ ++ lockdep_assert_held(&dev->mutex); ++ ++ if (dev->users++ == 0 && !dev->inhibited) { ++ if (dev->open) { ++ error = dev->open(dev); ++ if (error) { ++ dev->users--; ++ return error; ++ } ++ } ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ dev->ready = true; ++ input_dev_toggle(dev, true); ++ } ++ ++ if (dev->poller) ++ input_dev_poller_start(dev->poller); ++ } ++ ++ return 0; ++} ++ + /** + * input_open_device - open input device + * @handle: handle through which device is being accessed +@@ -586,21 +655,9 @@ int input_open_device(struct input_handle *handle) + + handle->open++; + +- if (handle->handler->passive_observer) +- return 0; +- +- if (dev->users++ || dev->inhibited) { +- /* +- * Device is already opened and/or inhibited, +- * so we can exit immediately and report success. +- */ +- return 0; +- } +- +- if (dev->open) { +- error = dev->open(dev); ++ if (!handle->handler->passive_observer) { ++ error = input_start_device(dev); + if (error) { +- dev->users--; + handle->open--; + /* + * Make sure we are not delivering any more +@@ -611,8 +668,8 @@ int input_open_device(struct input_handle *handle) + } + } + +- if (dev->poller) +- input_dev_poller_start(dev->poller); ++ if (handle->open == 1 && handle->handler->start) ++ handle->handler->start(handle); + } + + return 0; +@@ -651,6 +708,12 @@ void input_close_device(struct input_handle *handle) + if (!--dev->users && !dev->inhibited) { + if (dev->poller) + input_dev_poller_stop(dev->poller); ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ input_dev_toggle(dev, false); ++ dev->ready = false; ++ } ++ + if (dev->close) + dev->close(dev); + } +@@ -1702,37 +1765,6 @@ static int input_dev_uevent(const struct device *device, struct kobj_uevent_env + return 0; + } + +-#define INPUT_DO_TOGGLE(dev, type, bits, on) \ +- do { \ +- int i; \ +- bool active; \ +- \ +- if (!test_bit(EV_##type, dev->evbit)) \ +- break; \ +- \ +- for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ +- active = test_bit(i, dev->bits); \ +- if (!active && !on) \ +- continue; \ +- \ +- dev->event(dev, EV_##type, i, on ? active : 0); \ +- } \ +- } while (0) +- +-static void input_dev_toggle(struct input_dev *dev, bool activate) +-{ +- if (!dev->event) +- return; +- +- INPUT_DO_TOGGLE(dev, LED, led, activate); +- INPUT_DO_TOGGLE(dev, SND, snd, activate); +- +- if (activate && test_bit(EV_REP, dev->evbit)) { +- dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); +- dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); +- } +-} +- + /** + * input_reset_device() - reset/restore the state of input device + * @dev: input device whose state needs to be reset +@@ -1756,52 +1788,73 @@ static int input_inhibit_device(struct input_dev *dev) + { + guard(mutex)(&dev->mutex); + ++ if (dev->going_away) ++ return -ENODEV; ++ + if (dev->inhibited) + return 0; + + if (dev->users) { +- if (dev->close) +- dev->close(dev); + if (dev->poller) + input_dev_poller_stop(dev->poller); ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ input_dev_toggle(dev, false); ++ dev->ready = false; ++ } ++ ++ if (dev->close) ++ dev->close(dev); + } + + scoped_guard(spinlock_irq, &dev->event_lock) { + input_mt_release_slots(dev); + input_dev_release_keys(dev); + input_handle_event(dev, EV_SYN, SYN_REPORT, 1); +- input_dev_toggle(dev, false); ++ dev->inhibited = true; + } + +- dev->inhibited = true; +- + return 0; + } + + static int input_uninhibit_device(struct input_dev *dev) + { ++ struct input_handle *handle; + int error; + + guard(mutex)(&dev->mutex); + ++ if (dev->going_away) ++ return -ENODEV; ++ + if (!dev->inhibited) + return 0; + ++ dev->inhibited = false; ++ + if (dev->users) { + if (dev->open) { + error = dev->open(dev); +- if (error) ++ if (error) { ++ dev->inhibited = true; + return error; ++ } + } +- if (dev->poller) +- input_dev_poller_start(dev->poller); ++ scoped_guard(spinlock_irq, &dev->event_lock) ++ dev->ready = true; + } + +- dev->inhibited = false; +- + scoped_guard(spinlock_irq, &dev->event_lock) + input_dev_toggle(dev, true); + ++ if (dev->users && dev->poller) ++ input_dev_poller_start(dev->poller); ++ ++ list_for_each_entry(handle, &dev->h_list, d_node) { ++ if (handle->open && handle->handler->start) ++ handle->handler->start(handle); ++ } ++ + return 0; + } + +@@ -2655,9 +2708,6 @@ int input_register_handle(struct input_handle *handle) + */ + list_add_tail_rcu(&handle->h_node, &handler->h_list); + +- if (handler->start) +- handler->start(handle); +- + return 0; + } + EXPORT_SYMBOL(input_register_handle); +diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig +--- a/drivers/input/keyboard/Kconfig ++++ b/drivers/input/keyboard/Kconfig +@@ -119,44 +119,6 @@ config KEYBOARD_ATKBD + To compile this driver as a module, choose M here: the + module will be called atkbd. + +-config KEYBOARD_ATKBD_HP_KEYCODES +- bool "Use HP keyboard scancodes" +- depends on PARISC && KEYBOARD_ATKBD +- default y +- help +- Say Y here if you have a PA-RISC machine and want to use an AT or +- PS/2 keyboard, and your keyboard uses keycodes that are specific to +- PA-RISC keyboards. +- +- Say N if you use a standard keyboard. +- +-config KEYBOARD_ATKBD_RDI_KEYCODES +- bool "Use PrecisionBook keyboard scancodes" +- depends on KEYBOARD_ATKBD_HP_KEYCODES +- default n +- help +- If you have an RDI PrecisionBook, say Y here if you want to use its +- built-in keyboard (as opposed to an external keyboard). +- +- The PrecisionBook has five keys that conflict with those used by most +- AT and PS/2 keyboards. These are as follows: +- +- PrecisionBook Standard AT or PS/2 +- +- F1 F12 +- Left Ctrl Left Alt +- Caps Lock Left Ctrl +- Right Ctrl Caps Lock +- Left 102nd key (the key to the right of Left Shift) +- +- If you say N here, and use the PrecisionBook keyboard, then each key +- in the left-hand column will be interpreted as the corresponding key +- in the right-hand column. +- +- If you say Y here, and use an external keyboard, then each key in the +- right-hand column will be interpreted as the key shown in the +- left-hand column. +- + config KEYBOARD_QT1050 + tristate "Microchip AT42QT1050 Touch Sensor Chip" + depends on I2C +diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c +--- a/drivers/input/keyboard/atkbd.c ++++ b/drivers/input/keyboard/atkbd.c +@@ -73,13 +73,6 @@ MODULE_PARM_DESC(terminal, "Enable break codes on an IBM Terminal keyboard conne + #define ATKBD_KEYMAP_SIZE 512 + + static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { +-#ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES +- +-/* XXX: need a more general approach */ +- +-#include "hpps2atkbd.h" /* include the keyboard scancodes */ +- +-#else + 0, 67, 65, 63, 61, 59, 60, 88,183, 68, 66, 64, 62, 15, 41,117, + 184, 56, 42, 93, 29, 16, 2, 0,185, 0, 44, 31, 30, 17, 3, 0, + 186, 46, 45, 32, 18, 5, 4, 95,187, 57, 47, 33, 20, 19, 6,183, +@@ -99,7 +92,6 @@ static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { + 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, + + 0, 0, 0, 65, 99, +-#endif + }; + + static const unsigned short atkbd_set3_keycode[ATKBD_KEYMAP_SIZE] = { +diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c +--- a/drivers/input/keyboard/cap11xx.c ++++ b/drivers/input/keyboard/cap11xx.c +@@ -5,6 +5,8 @@ + * (c) 2014 Daniel Mack + */ + ++#include ++#include + #include + #include + #include +@@ -16,63 +18,43 @@ + #include + #include + ++#define CAP1114_REG_BUTTON_STATUS1 0x03 ++#define CAP1114_REG_BUTTON_STATUS2 0x04 ++#define CAP1114_REG_CONFIG2 0x40 ++#define CAP1114_REG_CONFIG2_VOL_UP_DOWN BIT(1) ++#define CAP1114_REG_LED_OUTPUT_CONTROL1 0x73 ++ + #define CAP11XX_REG_MAIN_CONTROL 0x00 + #define CAP11XX_REG_MAIN_CONTROL_GAIN_SHIFT (6) + #define CAP11XX_REG_MAIN_CONTROL_GAIN_MASK (0xc0) + #define CAP11XX_REG_MAIN_CONTROL_DLSEEP BIT(4) +-#define CAP11XX_REG_GENERAL_STATUS 0x02 + #define CAP11XX_REG_SENSOR_INPUT 0x03 +-#define CAP11XX_REG_NOISE_FLAG_STATUS 0x0a ++#define CAP1114_REG_BUTTON_STATUS2 0x04 + #define CAP11XX_REG_SENOR_DELTA(X) (0x10 + (X)) + #define CAP11XX_REG_SENSITIVITY_CONTROL 0x1f + #define CAP11XX_REG_SENSITIVITY_CONTROL_DELTA_SENSE_MASK 0x70 +-#define CAP11XX_REG_CONFIG 0x20 +-#define CAP11XX_REG_SENSOR_ENABLE 0x21 +-#define CAP11XX_REG_SENSOR_CONFIG 0x22 +-#define CAP11XX_REG_SENSOR_CONFIG2 0x23 +-#define CAP11XX_REG_SAMPLING_CONFIG 0x24 +-#define CAP11XX_REG_CALIBRATION 0x26 +-#define CAP11XX_REG_INT_ENABLE 0x27 + #define CAP11XX_REG_REPEAT_RATE 0x28 + #define CAP11XX_REG_SIGNAL_GUARD_ENABLE 0x29 +-#define CAP11XX_REG_MT_CONFIG 0x2a +-#define CAP11XX_REG_MT_PATTERN_CONFIG 0x2b +-#define CAP11XX_REG_MT_PATTERN 0x2d +-#define CAP11XX_REG_RECALIB_CONFIG 0x2f + #define CAP11XX_REG_SENSOR_THRESH(X) (0x30 + (X)) +-#define CAP11XX_REG_SENSOR_NOISE_THRESH 0x38 +-#define CAP11XX_REG_STANDBY_CHANNEL 0x40 +-#define CAP11XX_REG_STANDBY_CONFIG 0x41 +-#define CAP11XX_REG_STANDBY_SENSITIVITY 0x42 +-#define CAP11XX_REG_STANDBY_THRESH 0x43 + #define CAP11XX_REG_CONFIG2 0x44 + #define CAP11XX_REG_CONFIG2_ALT_POL BIT(6) +-#define CAP11XX_REG_SENSOR_BASE_CNT(X) (0x50 + (X)) +-#define CAP11XX_REG_LED_POLARITY 0x73 + #define CAP11XX_REG_LED_OUTPUT_CONTROL 0x74 + #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG 0x80 + #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG2 0x81 +- +-#define CAP11XX_REG_LED_DUTY_CYCLE_1 0x90 +-#define CAP11XX_REG_LED_DUTY_CYCLE_2 0x91 +-#define CAP11XX_REG_LED_DUTY_CYCLE_3 0x92 + #define CAP11XX_REG_LED_DUTY_CYCLE_4 0x93 + +-#define CAP11XX_REG_LED_DUTY_MIN_MASK (0x0f) +-#define CAP11XX_REG_LED_DUTY_MIN_MASK_SHIFT (0) + #define CAP11XX_REG_LED_DUTY_MAX_MASK (0xf0) +-#define CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT (4) + #define CAP11XX_REG_LED_DUTY_MAX_VALUE (15) + +-#define CAP11XX_REG_SENSOR_CALIB (0xb1 + (X)) +-#define CAP11XX_REG_SENSOR_CALIB_LSB1 0xb9 +-#define CAP11XX_REG_SENSOR_CALIB_LSB2 0xba + #define CAP11XX_REG_PRODUCT_ID 0xfd + #define CAP11XX_REG_MANUFACTURER_ID 0xfe + #define CAP11XX_REG_REVISION 0xff + + #define CAP11XX_MANUFACTURER_ID 0x5d + ++#define CAP11XX_T_RST_FILT_MIN_US 10000 ++#define CAP11XX_T_RST_ON_MIN_MS 400 ++ + #ifdef CONFIG_LEDS_CLASS + struct cap11xx_led { + struct cap11xx_priv *priv; +@@ -85,6 +67,7 @@ struct cap11xx_priv { + struct regmap *regmap; + struct device *dev; + struct input_dev *idev; ++ struct gpio_desc *reset_gpio; + const struct cap11xx_hw_model *model; + + struct cap11xx_led *leds; +@@ -101,47 +84,32 @@ struct cap11xx_priv { + + struct cap11xx_hw_model { + u8 product_id; ++ u8 led_output_control_reg_base; ++ u8 sensor_input_reg_base; + unsigned int num_channels; + unsigned int num_leds; ++ unsigned int num_sensor_thresholds; + bool has_gain; ++ bool has_grouped_sensors; + bool has_irq_config; ++ bool has_repeat_en; + bool has_sensitivity_control; + bool has_signal_guard; + }; + + static const struct reg_default cap11xx_reg_defaults[] = { + { CAP11XX_REG_MAIN_CONTROL, 0x00 }, +- { CAP11XX_REG_GENERAL_STATUS, 0x00 }, +- { CAP11XX_REG_SENSOR_INPUT, 0x00 }, +- { CAP11XX_REG_NOISE_FLAG_STATUS, 0x00 }, + { CAP11XX_REG_SENSITIVITY_CONTROL, 0x2f }, +- { CAP11XX_REG_CONFIG, 0x20 }, +- { CAP11XX_REG_SENSOR_ENABLE, 0x3f }, +- { CAP11XX_REG_SENSOR_CONFIG, 0xa4 }, +- { CAP11XX_REG_SENSOR_CONFIG2, 0x07 }, +- { CAP11XX_REG_SAMPLING_CONFIG, 0x39 }, +- { CAP11XX_REG_CALIBRATION, 0x00 }, +- { CAP11XX_REG_INT_ENABLE, 0x3f }, + { CAP11XX_REG_REPEAT_RATE, 0x3f }, +- { CAP11XX_REG_MT_CONFIG, 0x80 }, +- { CAP11XX_REG_MT_PATTERN_CONFIG, 0x00 }, +- { CAP11XX_REG_MT_PATTERN, 0x3f }, +- { CAP11XX_REG_RECALIB_CONFIG, 0x8a }, + { CAP11XX_REG_SENSOR_THRESH(0), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(1), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(2), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(3), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(4), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(5), 0x40 }, +- { CAP11XX_REG_SENSOR_NOISE_THRESH, 0x01 }, +- { CAP11XX_REG_STANDBY_CHANNEL, 0x00 }, +- { CAP11XX_REG_STANDBY_CONFIG, 0x39 }, +- { CAP11XX_REG_STANDBY_SENSITIVITY, 0x02 }, +- { CAP11XX_REG_STANDBY_THRESH, 0x40 }, ++ { CAP11XX_REG_SENSOR_THRESH(6), 0x40 }, ++ { CAP11XX_REG_SENSOR_THRESH(7), 0x40 }, + { CAP11XX_REG_CONFIG2, 0x40 }, +- { CAP11XX_REG_LED_POLARITY, 0x00 }, +- { CAP11XX_REG_SENSOR_CALIB_LSB1, 0x00 }, +- { CAP11XX_REG_SENSOR_CALIB_LSB2, 0x00 }, + }; + + static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) +@@ -149,6 +117,12 @@ static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) + switch (reg) { + case CAP11XX_REG_MAIN_CONTROL: + case CAP11XX_REG_SENSOR_INPUT: ++ /* ++ * CAP1114_REG_BUTTON_STATUS1 (CAP11XX_REG_SENSOR_INPUT) and ++ * CAP1114_REG_BUTTON_STATUS2 is volatile for the CAP1114, ++ * which supports more than 8 touch channels. ++ */ ++ case CAP1114_REG_BUTTON_STATUS2: + case CAP11XX_REG_SENOR_DELTA(0): + case CAP11XX_REG_SENOR_DELTA(1): + case CAP11XX_REG_SENOR_DELTA(2): +@@ -257,8 +231,8 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + } + + if (!of_property_read_u32_array(node, "microchip,input-threshold", +- priv->thresholds, priv->model->num_channels)) { +- for (i = 0; i < priv->model->num_channels; i++) { ++ priv->thresholds, priv->model->num_sensor_thresholds)) { ++ for (i = 0; i < priv->model->num_sensor_thresholds; i++) { + if (priv->thresholds[i] > 127) { + dev_err(dev, "Invalid input-threshold value %u\n", + priv->thresholds[i]); +@@ -273,10 +247,13 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + } + } + +- if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", +- priv->calib_sensitivities, +- priv->model->num_channels)) { +- if (priv->model->has_sensitivity_control) { ++ if (of_property_present(node, "microchip,calib-sensitivity")) { ++ if (!priv->model->has_sensitivity_control) { ++ dev_warn(dev, ++ "This model doesn't support 'calib-sensitivity'\n"); ++ } else if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", ++ priv->calib_sensitivities, ++ priv->model->num_channels)) { + for (i = 0; i < priv->model->num_channels; i++) { + if (!is_power_of_2(priv->calib_sensitivities[i]) || + priv->calib_sensitivities[i] > 4) { +@@ -296,32 +273,31 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + if (error) + return error; + } +- } else { +- dev_warn(dev, +- "This model doesn't support 'calib-sensitivity'\n"); + } + } + +- for (i = 0; i < priv->model->num_channels; i++) { +- if (!of_property_read_u32_index(node, "microchip,signal-guard", +- i, &u32_val)) { +- if (u32_val > 1) +- return -EINVAL; +- if (u32_val) +- priv->signal_guard_inputs_mask |= 0x01 << i; +- } +- } +- +- if (priv->signal_guard_inputs_mask) { +- if (priv->model->has_signal_guard) { +- error = regmap_write(priv->regmap, +- CAP11XX_REG_SIGNAL_GUARD_ENABLE, +- priv->signal_guard_inputs_mask); +- if (error) +- return error; +- } else { ++ if (of_property_present(node, "microchip,signal-guard")) { ++ if (!priv->model->has_signal_guard) { + dev_warn(dev, + "This model doesn't support 'signal-guard'\n"); ++ } else { ++ for (i = 0; i < priv->model->num_channels; i++) { ++ if (!of_property_read_u32_index(node, "microchip,signal-guard", ++ i, &u32_val)) { ++ if (u32_val > 1) ++ return -EINVAL; ++ if (u32_val) ++ priv->signal_guard_inputs_mask |= 0x01 << i; ++ } ++ } ++ ++ if (priv->signal_guard_inputs_mask) { ++ error = regmap_write(priv->regmap, ++ CAP11XX_REG_SIGNAL_GUARD_ENABLE, ++ priv->signal_guard_inputs_mask); ++ if (error) ++ return error; ++ } + } + } + +@@ -332,10 +308,23 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + of_property_read_u32_array(node, "linux,keycodes", + priv->keycodes, priv->model->num_channels); + +- /* Disable autorepeat. The Linux input system has its own handling. */ +- error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); +- if (error) +- return error; ++ /* ++ * CAP1114 needs dedicated configuration to split ++ * grouped sensors into independent inputs. ++ */ ++ if (priv->model->has_grouped_sensors) { ++ error = regmap_set_bits(priv->regmap, CAP1114_REG_CONFIG2, ++ CAP1114_REG_CONFIG2_VOL_UP_DOWN); ++ if (error) ++ return error; ++ } ++ ++ if (priv->model->has_repeat_en) { ++ /* Disable autorepeat. The Linux input system has its own handling. */ ++ error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); ++ if (error) ++ return error; ++ } + + return 0; + } +@@ -354,10 +343,25 @@ static irqreturn_t cap11xx_thread_func(int irq_num, void *data) + if (ret < 0) + goto out; + +- ret = regmap_read(priv->regmap, CAP11XX_REG_SENSOR_INPUT, &status); ++ ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base, &status); + if (ret < 0) + goto out; + ++ if (priv->model->num_channels > 8) { ++ unsigned int status2; ++ ++ ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base + 1, &status2); ++ if (ret < 0) ++ goto out; ++ ++ /* ++ * CAP1114 STATUS1 register only contains data for the first 6 channels. ++ * the remaining channels is stored in STATUS2. ++ */ ++ status &= GENMASK(5, 0); ++ status |= FIELD_PREP(GENMASK(13, 6), status2); ++ } ++ + for (i = 0; i < priv->idev->keycodemax; i++) + input_report_key(priv->idev, priv->keycodes[i], + status & (1 << i)); +@@ -407,10 +411,16 @@ static int cap11xx_led_set(struct led_classdev *cdev, + * limitation. Brightness levels per LED are either + * 0 (OFF) and 1 (ON). + */ +- return regmap_update_bits(priv->regmap, +- CAP11XX_REG_LED_OUTPUT_CONTROL, +- BIT(led->reg), +- value ? BIT(led->reg) : 0); ++ if (led->reg >= 8) ++ return regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base + 1, ++ BIT(led->reg - 8), ++ value ? BIT(led->reg - 8) : 0); ++ else ++ return regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base, ++ BIT(led->reg), ++ value ? BIT(led->reg) : 0); + } + + static int cap11xx_init_leds(struct device *dev, +@@ -420,6 +430,7 @@ static int cap11xx_init_leds(struct device *dev, + struct cap11xx_led *led; + int cnt = of_get_child_count(node); + int error; ++ u32 duty_val; + + if (!num_leds || !cnt) + return 0; +@@ -433,15 +444,26 @@ static int cap11xx_init_leds(struct device *dev, + + priv->leds = led; + ++ /* Set all LEDs to off */ + error = regmap_update_bits(priv->regmap, +- CAP11XX_REG_LED_OUTPUT_CONTROL, 0xff, 0); ++ priv->model->led_output_control_reg_base, ++ GENMASK(min(num_leds, 8) - 1, 0), 0); + if (error) + return error; + ++ if (num_leds > 8) { ++ error = regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base + 1, ++ GENMASK(num_leds - 8 - 1, 0), 0); ++ if (error) ++ return error; ++ } ++ ++ duty_val = FIELD_PREP(CAP11XX_REG_LED_DUTY_MAX_MASK, ++ CAP11XX_REG_LED_DUTY_MAX_VALUE); ++ + error = regmap_update_bits(priv->regmap, CAP11XX_REG_LED_DUTY_CYCLE_4, +- CAP11XX_REG_LED_DUTY_MAX_MASK, +- CAP11XX_REG_LED_DUTY_MAX_VALUE << +- CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT); ++ CAP11XX_REG_LED_DUTY_MAX_MASK, duty_val); + if (error) + return error; + +@@ -510,9 +532,20 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + ++ priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); ++ if (IS_ERR(priv->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), ++ "Failed to get 'reset' GPIO\n"); ++ ++ if (priv->reset_gpio) { ++ usleep_range(CAP11XX_T_RST_FILT_MIN_US, CAP11XX_T_RST_FILT_MIN_US * 2); ++ gpiod_set_value_cansleep(priv->reset_gpio, 0); ++ msleep(CAP11XX_T_RST_ON_MIN_MS); ++ } ++ + error = regmap_read(priv->regmap, CAP11XX_REG_PRODUCT_ID, &val); + if (error) +- return error; ++ return dev_err_probe(dev, error, "Failed to read product ID\n"); + + if (val != cap->product_id) { + dev_err(dev, "Product ID: Got 0x%02x, expected 0x%02x\n", +@@ -522,7 +555,7 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + + error = regmap_read(priv->regmap, CAP11XX_REG_MANUFACTURER_ID, &val); + if (error) +- return error; ++ return dev_err_probe(dev, error, "Failed to read manufacturer ID\n"); + + if (val != CAP11XX_MANUFACTURER_ID) { + dev_err(dev, "Manufacturer ID: Got 0x%02x, expected 0x%02x\n", +@@ -531,11 +564,8 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + } + + error = regmap_read(priv->regmap, CAP11XX_REG_REVISION, &rev); +- if (error < 0) +- return error; +- +- dev_info(dev, "CAP11XX detected, model %s, revision 0x%02x\n", +- id->name, rev); ++ if (error) ++ return dev_err_probe(dev, error, "Failed to read revision\n"); + + priv->model = cap; + +@@ -599,47 +629,79 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + } + + static const struct cap11xx_hw_model cap1106_model = { +- .product_id = 0x55, .num_channels = 6, .num_leds = 0, ++ .product_id = 0x55, ++ .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, ++}; ++ ++static const struct cap11xx_hw_model cap1114_model = { ++ .product_id = 0x3a, ++ .num_channels = 14, .num_leds = 11, .num_sensor_thresholds = 8, ++ .led_output_control_reg_base = CAP1114_REG_LED_OUTPUT_CONTROL1, ++ .sensor_input_reg_base = CAP1114_REG_BUTTON_STATUS1, ++ .has_grouped_sensors = true, + }; + + static const struct cap11xx_hw_model cap1126_model = { +- .product_id = 0x53, .num_channels = 6, .num_leds = 2, ++ .product_id = 0x53, ++ .num_channels = 6, .num_leds = 2, .num_sensor_thresholds = 6, ++ .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1188_model = { +- .product_id = 0x50, .num_channels = 8, .num_leds = 8, ++ .product_id = 0x50, ++ .num_channels = 8, .num_leds = 8, .num_sensor_thresholds = 8, ++ .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1203_model = { +- .product_id = 0x6d, .num_channels = 3, .num_leds = 0, ++ .product_id = 0x6d, ++ .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1206_model = { +- .product_id = 0x67, .num_channels = 6, .num_leds = 0, ++ .product_id = 0x67, ++ .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1293_model = { +- .product_id = 0x6f, .num_channels = 3, .num_leds = 0, ++ .product_id = 0x6f, ++ .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, ++ .has_repeat_en = true, + .has_sensitivity_control = true, + .has_signal_guard = true, + }; + + static const struct cap11xx_hw_model cap1298_model = { +- .product_id = 0x71, .num_channels = 8, .num_leds = 0, ++ .product_id = 0x71, ++ .num_channels = 8, .num_leds = 0, .num_sensor_thresholds = 8, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, ++ .has_repeat_en = true, + .has_sensitivity_control = true, + .has_signal_guard = true, + }; + + static const struct of_device_id cap11xx_dt_ids[] = { + { .compatible = "microchip,cap1106", .data = &cap1106_model }, ++ { .compatible = "microchip,cap1114", .data = &cap1114_model }, + { .compatible = "microchip,cap1126", .data = &cap1126_model }, + { .compatible = "microchip,cap1188", .data = &cap1188_model }, + { .compatible = "microchip,cap1203", .data = &cap1203_model }, +@@ -652,6 +714,7 @@ MODULE_DEVICE_TABLE(of, cap11xx_dt_ids); + + static const struct i2c_device_id cap11xx_i2c_ids[] = { + { .name = "cap1106", .driver_data = (kernel_ulong_t)&cap1106_model }, ++ { .name = "cap1114", .driver_data = (kernel_ulong_t)&cap1114_model }, + { .name = "cap1126", .driver_data = (kernel_ulong_t)&cap1126_model }, + { .name = "cap1188", .driver_data = (kernel_ulong_t)&cap1188_model }, + { .name = "cap1203", .driver_data = (kernel_ulong_t)&cap1203_model }, +diff --git a/drivers/input/keyboard/charlieplex_keypad.c b/drivers/input/keyboard/charlieplex_keypad.c +--- a/drivers/input/keyboard/charlieplex_keypad.c ++++ b/drivers/input/keyboard/charlieplex_keypad.c +@@ -77,7 +77,9 @@ static int charlieplex_keypad_scan_line(struct charlieplex_keypad *keypad, + int err; + + /* Activate only one line as output at a time. */ +- gpiod_direction_output(line_gpios->desc[oline], 1); ++ err = gpiod_direction_output(line_gpios->desc[oline], 1); ++ if (err) ++ return err; + + if (keypad->settling_time_us) + fsleep(keypad->settling_time_us); +diff --git a/drivers/input/keyboard/hpps2atkbd.h b/drivers/input/keyboard/hpps2atkbd.h +deleted file mode 100644 +--- a/drivers/input/keyboard/hpps2atkbd.h ++++ /dev/null +@@ -1,110 +0,0 @@ +-/* +- * drivers/input/keyboard/hpps2atkbd.h +- * +- * Copyright (c) 2004 Helge Deller +- * Copyright (c) 2002 Laurent Canet +- * Copyright (c) 2002 Thibaut Varene +- * Copyright (c) 2000 Xavier Debacker +- * +- * HP PS/2 AT-compatible Keyboard, found in PA/RISC Workstations & Laptops +- * +- * This file is subject to the terms and conditions of the GNU General Public +- * License. See the file "COPYING" in the main directory of this archive +- * for more details. +- */ +- +- +-/* Is the keyboard an RDI PrecisionBook? */ +-#ifndef CONFIG_KEYBOARD_ATKBD_RDI_KEYCODES +-# define CONFLICT(x,y) x +-#else +-# define CONFLICT(x,y) y +-#endif +- +-/* sadly RDI (Tadpole) decided to ship a different keyboard layout +- than HP for their PS/2 laptop keyboard which leads to conflicting +- keycodes between a normal HP PS/2 keyboard and a RDI Precisionbook. +- HP: RDI: */ +-#define C_07 CONFLICT( KEY_F12, KEY_F1 ) +-#define C_11 CONFLICT( KEY_LEFTALT, KEY_LEFTCTRL ) +-#define C_14 CONFLICT( KEY_LEFTCTRL, KEY_CAPSLOCK ) +-#define C_58 CONFLICT( KEY_CAPSLOCK, KEY_RIGHTCTRL ) +-#define C_61 CONFLICT( KEY_102ND, KEY_LEFT ) +- +-/* Raw SET 2 scancode table */ +- +-/* 00 */ KEY_RESERVED, KEY_F9, KEY_RESERVED, KEY_F5, KEY_F3, KEY_F1, KEY_F2, C_07, +-/* 08 */ KEY_ESC, KEY_F10, KEY_F8, KEY_F6, KEY_F4, KEY_TAB, KEY_GRAVE, KEY_F2, +-/* 10 */ KEY_RESERVED, C_11, KEY_LEFTSHIFT, KEY_RESERVED, C_14, KEY_Q, KEY_1, KEY_F3, +-/* 18 */ KEY_RESERVED, KEY_LEFTALT, KEY_Z, KEY_S, KEY_A, KEY_W, KEY_2, KEY_F4, +-/* 20 */ KEY_RESERVED, KEY_C, KEY_X, KEY_D, KEY_E, KEY_4, KEY_3, KEY_F5, +-/* 28 */ KEY_RESERVED, KEY_SPACE, KEY_V, KEY_F, KEY_T, KEY_R, KEY_5, KEY_F6, +-/* 30 */ KEY_RESERVED, KEY_N, KEY_B, KEY_H, KEY_G, KEY_Y, KEY_6, KEY_F7, +-/* 38 */ KEY_RESERVED, KEY_RIGHTALT, KEY_M, KEY_J, KEY_U, KEY_7, KEY_8, KEY_F8, +-/* 40 */ KEY_RESERVED, KEY_COMMA, KEY_K, KEY_I, KEY_O, KEY_0, KEY_9, KEY_F9, +-/* 48 */ KEY_RESERVED, KEY_DOT, KEY_SLASH, KEY_L, KEY_SEMICOLON, KEY_P, KEY_MINUS, KEY_F10, +-/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_APOSTROPHE,KEY_RESERVED, KEY_LEFTBRACE, KEY_EQUAL, KEY_F11, KEY_SYSRQ, +-/* 58 */ C_58, KEY_RIGHTSHIFT,KEY_ENTER, KEY_RIGHTBRACE,KEY_BACKSLASH, KEY_BACKSLASH,KEY_F12, KEY_SCROLLLOCK, +-/* 60 */ KEY_DOWN, C_61, KEY_PAUSE, KEY_UP, KEY_DELETE, KEY_END, KEY_BACKSPACE, KEY_INSERT, +-/* 68 */ KEY_RESERVED, KEY_KP1, KEY_RIGHT, KEY_KP4, KEY_KP7, KEY_PAGEDOWN, KEY_HOME, KEY_PAGEUP, +-/* 70 */ KEY_KP0, KEY_KPDOT, KEY_KP2, KEY_KP5, KEY_KP6, KEY_KP8, KEY_ESC, KEY_NUMLOCK, +-/* 78 */ KEY_F11, KEY_KPPLUS, KEY_KP3, KEY_KPMINUS, KEY_KPASTERISK,KEY_KP9, KEY_SCROLLLOCK,KEY_102ND, +-/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 90 */ KEY_RESERVED, KEY_RIGHTALT, 255, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_CAPSLOCK, KEY_RESERVED, KEY_LEFTMETA, +-/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTMETA, +-/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_COMPOSE, +-/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPSLASH, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPENTER, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e8 */ KEY_RESERVED, KEY_END, KEY_RESERVED, KEY_LEFT, KEY_HOME, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f0 */ KEY_INSERT, KEY_DELETE, KEY_DOWN, KEY_RESERVED, KEY_RIGHT, KEY_UP, KEY_RESERVED, KEY_PAUSE, +-/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED, KEY_SYSRQ, KEY_PAGEUP, KEY_RESERVED, KEY_RESERVED, +- +-/* These are offset for escaped keycodes: */ +- +-/* 00 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_F7, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 08 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 10 */ KEY_RESERVED, KEY_RIGHTALT, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 18 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 20 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 28 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 30 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 38 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 40 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 48 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 58 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 60 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 68 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 70 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 78 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 90 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED +- +-#undef CONFLICT +-#undef C_07 +-#undef C_11 +-#undef C_14 +-#undef C_58 +-#undef C_61 +- +diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c +--- a/drivers/input/keyboard/matrix_keypad.c ++++ b/drivers/input/keyboard/matrix_keypad.c +@@ -16,7 +16,6 @@ + #include + #include + #include +-#include + #include + #include + #include +diff --git a/drivers/input/keyboard/pmic8xxx-keypad.c b/drivers/input/keyboard/pmic8xxx-keypad.c +--- a/drivers/input/keyboard/pmic8xxx-keypad.c ++++ b/drivers/input/keyboard/pmic8xxx-keypad.c +@@ -462,15 +462,9 @@ static int pmic8xxx_kp_enable(struct pmic8xxx_kp *kp) + + static int pmic8xxx_kp_disable(struct pmic8xxx_kp *kp) + { +- int rc; +- + kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN; + +- rc = regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); +- if (rc < 0) +- return rc; +- +- return rc; ++ return regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); + } + + static int pmic8xxx_kp_open(struct input_dev *dev) +diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c +--- a/drivers/input/keyboard/samsung-keypad.c ++++ b/drivers/input/keyboard/samsung-keypad.c +@@ -142,7 +142,7 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) + unsigned int row_state[SAMSUNG_MAX_COLS]; + bool key_down; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + do { + readl(keypad->base + SAMSUNG_KEYIFSTSCLR); +@@ -158,8 +158,6 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) + + } while (key_down && !keypad->stopped); + +- pm_runtime_put(&keypad->pdev->dev); +- + return IRQ_HANDLED; + } + +@@ -167,7 +165,7 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) + { + unsigned int val; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + /* Tell IRQ thread that it may poll the device. */ + keypad->stopped = false; +@@ -182,14 +180,14 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) + /* KEYIFCOL reg clear. */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); + +- pm_runtime_put(&keypad->pdev->dev); ++ enable_irq(keypad->irq); + } + + static void samsung_keypad_stop(struct samsung_keypad *keypad) + { + unsigned int val; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + /* Signal IRQ thread to stop polling and disable the handler. */ + keypad->stopped = true; +@@ -205,14 +203,6 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad) + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); +- +- /* +- * Now that chip should not generate interrupts we can safely +- * re-enable the handler. +- */ +- enable_irq(keypad->irq); +- +- pm_runtime_put(&keypad->pdev->dev); + } + + static int samsung_keypad_open(struct input_dev *input_dev) +@@ -412,7 +402,8 @@ static int samsung_keypad_probe(struct platform_device *pdev) + } + + error = devm_request_threaded_irq(&pdev->dev, keypad->irq, NULL, +- samsung_keypad_irq, IRQF_ONESHOT, ++ samsung_keypad_irq, ++ IRQF_ONESHOT | IRQF_NO_AUTOEN, + dev_name(&pdev->dev), keypad); + if (error) { + dev_err(&pdev->dev, "failed to register keypad interrupt\n"); +@@ -492,15 +483,17 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, + + val = readl(keypad->base + SAMSUNG_KEYIFCON); + if (enable) { ++ enable_irq_wake(keypad->irq); + val |= SAMSUNG_KEYIFCON_WAKEUPEN; +- if (device_may_wakeup(&keypad->pdev->dev)) +- enable_irq_wake(keypad->irq); ++ writel(val, keypad->base + SAMSUNG_KEYIFCON); + } else { + val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; +- if (device_may_wakeup(&keypad->pdev->dev)) +- disable_irq_wake(keypad->irq); ++ writel(val, keypad->base + SAMSUNG_KEYIFCON); ++ disable_irq_wake(keypad->irq); ++ ++ if (!input_device_enabled(keypad->input_dev)) ++ writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); + } +- writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); + } +@@ -516,7 +509,8 @@ static int samsung_keypad_suspend(struct device *dev) + if (input_device_enabled(input_dev)) + samsung_keypad_stop(keypad); + +- samsung_keypad_toggle_wakeup(keypad, true); ++ if (device_may_wakeup(dev)) ++ samsung_keypad_toggle_wakeup(keypad, true); + + return 0; + } +@@ -529,7 +523,8 @@ static int samsung_keypad_resume(struct device *dev) + + guard(mutex)(&input_dev->mutex); + +- samsung_keypad_toggle_wakeup(keypad, false); ++ if (device_may_wakeup(dev)) ++ samsung_keypad_toggle_wakeup(keypad, false); + + if (input_device_enabled(input_dev)) + samsung_keypad_start(keypad); +diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c +--- a/drivers/input/keyboard/snvs_pwrkey.c ++++ b/drivers/input/keyboard/snvs_pwrkey.c +@@ -39,6 +39,9 @@ struct pwrkey_drv_data { + int keycode; + int keystate; /* 1:pressed */ + int wakeup; ++ bool suspended; /* Track suspend state */ ++ bool pending_press; /* Key pressed during suspend, report from timer callback */ ++ spinlock_t lock; /* Protects keystate, suspended and pending_press */ + struct timer_list check_timer; + struct input_dev *input; + u8 minor_rev; +@@ -49,14 +52,38 @@ static void imx_imx_snvs_check_for_events(struct timer_list *t) + struct pwrkey_drv_data *pdata = timer_container_of(pdata, t, + check_timer); + struct input_dev *input = pdata->input; ++ bool state_changed = false; ++ bool pending_press; + u32 state; + + regmap_read(pdata->snvs, SNVS_HPSR_REG, &state); + state = state & SNVS_HPSR_BTN ? 1 : 0; + +- /* only report new event if status changed */ +- if (state ^ pdata->keystate) { +- pdata->keystate = state; ++ scoped_guard(spinlock_irqsave, &pdata->lock) { ++ pending_press = pdata->pending_press; ++ if (pending_press) { ++ pdata->pending_press = false; ++ pdata->keystate = 1; ++ } ++ /* only report new event if status changed */ ++ if (state ^ pdata->keystate) { ++ pdata->keystate = state; ++ state_changed = true; ++ } ++ } ++ ++ /* ++ * Report a press event latched during suspend. If the key is still ++ * held, state_changed will be 0 (keystate already set to 1 above), ++ * so no duplicate press is reported. If already released, ++ * state_changed will fire next to report the release. ++ */ ++ if (pending_press) { ++ input_report_key(input, pdata->keycode, 1); ++ input_sync(input); ++ } ++ ++ if (state_changed) { + input_event(input, EV_KEY, pdata->keycode, state); + input_sync(input); + pm_relax(pdata->input->dev.parent); +@@ -92,8 +119,17 @@ static irqreturn_t imx_snvs_pwrkey_interrupt(int irq, void *dev_id) + input_sync(input); + pm_relax(input->dev.parent); + } else { ++ /* ++ * If the key is pressed during suspend, latch it so ++ * the timer callback can report the press event in ++ * softirq context, avoiding out-of-order events. ++ */ ++ scoped_guard(spinlock_irqsave, &pdata->lock) { ++ if (pdata->suspended) ++ pdata->pending_press = true; ++ } + mod_timer(&pdata->check_timer, +- jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); ++ jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); + } + } + +@@ -112,6 +148,7 @@ static void imx_snvs_pwrkey_act(void *pdata) + + static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + { ++ struct device *dev = &pdev->dev; + struct pwrkey_drv_data *pdata; + struct input_dev *input; + struct device_node *np; +@@ -122,37 +159,35 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + u32 vid; + + /* Get SNVS register Page */ +- np = pdev->dev.of_node; ++ np = dev->of_node; + if (!np) +- return -ENODEV; ++ return dev_err_probe(dev, -ENODEV, "Device tree node not found\n"); + +- pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->snvs = syscon_regmap_lookup_by_phandle(np, "regmap"); +- if (IS_ERR(pdata->snvs)) { +- dev_err(&pdev->dev, "Can't get snvs syscon\n"); +- return PTR_ERR(pdata->snvs); +- } ++ if (IS_ERR(pdata->snvs)) ++ return dev_err_probe(dev, PTR_ERR(pdata->snvs), "Can't get snvs syscon\n"); + + if (of_property_read_u32(np, "linux,keycode", &pdata->keycode)) { + pdata->keycode = KEY_POWER; +- dev_warn(&pdev->dev, "KEY_POWER without setting in dts\n"); ++ dev_warn(dev, "KEY_POWER without setting in dts\n"); + } + +- clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); +- if (IS_ERR(clk)) { +- dev_err(&pdev->dev, "Failed to get snvs clock (%pe)\n", clk); +- return PTR_ERR(clk); +- } ++ clk = devm_clk_get_optional_enabled(dev, NULL); ++ if (IS_ERR(clk)) ++ return dev_err_probe(dev, PTR_ERR(clk), ++ "Failed to get snvs clock (%pe)\n", clk); + + pdata->wakeup = of_property_read_bool(np, "wakeup-source"); + + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq < 0) +- return -EINVAL; ++ return pdata->irq; + ++ spin_lock_init(&pdata->lock); + error = of_property_read_u32(np, "power-off-time-sec", &val); + if (!error) { + switch (val) { +@@ -165,9 +200,8 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + bpt = (val / 5) - 1; + break; + default: +- dev_err(&pdev->dev, +- "power-off-time-sec %d out of range\n", val); +- return -EINVAL; ++ return dev_err_probe(dev, -EINVAL, ++ "power-off-time-sec %d out of range\n", val); + } + + regmap_update_bits(pdata->snvs, SNVS_LPCR_REG, SNVS_LPCR_BPT_MASK, +@@ -184,11 +218,9 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + + timer_setup(&pdata->check_timer, imx_imx_snvs_check_for_events, 0); + +- input = devm_input_allocate_device(&pdev->dev); +- if (!input) { +- dev_err(&pdev->dev, "failed to allocate the input device\n"); +- return -ENOMEM; +- } ++ input = devm_input_allocate_device(dev); ++ if (!input) ++ return dev_err_probe(dev, -ENOMEM, "failed to allocate the input device\n"); + + input->name = pdev->name; + input->phys = "snvs-pwrkey/input0"; +@@ -197,37 +229,57 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + input_set_capability(input, EV_KEY, pdata->keycode); + + /* input customer action to cancel release timer */ +- error = devm_add_action(&pdev->dev, imx_snvs_pwrkey_act, pdata); +- if (error) { +- dev_err(&pdev->dev, "failed to register remove action\n"); +- return error; +- } ++ error = devm_add_action(dev, imx_snvs_pwrkey_act, pdata); ++ if (error) ++ return dev_err_probe(dev, error, "failed to register remove action\n"); + + pdata->input = input; + platform_set_drvdata(pdev, pdata); + +- error = devm_request_irq(&pdev->dev, pdata->irq, +- imx_snvs_pwrkey_interrupt, +- 0, pdev->name, pdev); +- if (error) { +- dev_err(&pdev->dev, "interrupt not available.\n"); +- return error; +- } ++ error = devm_request_irq(dev, pdata->irq, ++ imx_snvs_pwrkey_interrupt, ++ 0, pdev->name, pdev); ++ if (error) ++ return dev_err_probe(dev, error, "interrupt not available.\n"); + + error = input_register_device(input); +- if (error < 0) { +- dev_err(&pdev->dev, "failed to register input device\n"); +- return error; +- } ++ if (error < 0) ++ return dev_err_probe(dev, error, "failed to register input device\n"); + +- device_init_wakeup(&pdev->dev, pdata->wakeup); +- error = dev_pm_set_wake_irq(&pdev->dev, pdata->irq); ++ device_init_wakeup(dev, pdata->wakeup); ++ error = dev_pm_set_wake_irq(dev, pdata->irq); + if (error) +- dev_err(&pdev->dev, "irq wake enable failed.\n"); ++ dev_err(dev, "irq wake enable failed.\n"); + + return 0; + } + ++static int imx_snvs_pwrkey_suspend(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); ++ ++ guard(spinlock_irq)(&pdata->lock); ++ pdata->suspended = true; ++ ++ return 0; ++} ++ ++static int imx_snvs_pwrkey_resume(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); ++ ++ guard(spinlock_irq)(&pdata->lock); ++ pdata->suspended = false; ++ ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(imx_snvs_pwrkey_pm_ops, ++ imx_snvs_pwrkey_suspend, ++ imx_snvs_pwrkey_resume); ++ + static const struct of_device_id imx_snvs_pwrkey_ids[] = { + { .compatible = "fsl,sec-v4.0-pwrkey" }, + { /* sentinel */ } +@@ -238,6 +290,7 @@ static struct platform_driver imx_snvs_pwrkey_driver = { + .driver = { + .name = "snvs_pwrkey", + .of_match_table = imx_snvs_pwrkey_ids, ++ .pm = pm_ptr(&imx_snvs_pwrkey_pm_ops), + }, + .probe = imx_snvs_pwrkey_probe, + }; +diff --git a/drivers/input/keyboard/tca8418_keypad.c b/drivers/input/keyboard/tca8418_keypad.c +--- a/drivers/input/keyboard/tca8418_keypad.c ++++ b/drivers/input/keyboard/tca8418_keypad.c +@@ -254,7 +254,8 @@ static int tca8418_configure(struct tca8418_keypad *keypad_data, + return error; + + error = tca8418_write_byte(keypad_data, REG_CFG, +- CFG_INT_CFG | CFG_OVR_FLOW_IEN | CFG_KE_IEN); ++ CFG_INT_CFG | CFG_OVR_FLOW_IEN | ++ CFG_OVR_FLOW_M | CFG_KE_IEN); + + return error; + } +diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig +--- a/drivers/input/misc/Kconfig ++++ b/drivers/input/misc/Kconfig +@@ -842,6 +842,18 @@ config INPUT_IQS7222 + To compile this driver as a module, choose M here: the + module will be called iqs7222. + ++config INPUT_ISA1200_HAPTIC ++ tristate "Imagis ISA1200 haptic feedback unit" ++ depends on I2C ++ select INPUT_FF_MEMLESS ++ select REGMAP_I2C ++ help ++ Say Y to enable support for the Imagis ISA1200 haptic ++ feedback unit. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called isa1200. ++ + config INPUT_CMA3000 + tristate "VTI CMA3000 Tri-axis accelerometer" + help +diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile +--- a/drivers/input/misc/Makefile ++++ b/drivers/input/misc/Makefile +@@ -49,6 +49,7 @@ obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o + obj-$(CONFIG_INPUT_IQS269A) += iqs269a.o + obj-$(CONFIG_INPUT_IQS626A) += iqs626a.o + obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o ++obj-$(CONFIG_INPUT_ISA1200_HAPTIC) += isa1200.o + obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o + obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o + obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -2234,6 +2234,7 @@ static const struct usb_device_id ims_pcu_id_table[] = { + }, + { } + }; ++MODULE_DEVICE_TABLE(usb, ims_pcu_id_table); + + static const struct attribute_group *ims_pcu_sysfs_groups[] = { + &ims_pcu_attr_group, +diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c +new file mode 100644 +--- /dev/null ++++ b/drivers/input/misc/isa1200.c +@@ -0,0 +1,533 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * System control (LDO regulator) ++ * ++ * LDO voltage to register mapping is linear, but it is split in two parts: ++ * 2.3V - 3.0V map to 0x08 - 0x0f; 3.1V - 3.8V map to 0x00 - 0x7 ++ */ ++ ++#define ISA1200_SCTRL 0x00 ++#define ISA1200_LDO_VOLTAGE_BASE 0x08 ++#define ISA1200_LDO_VOLTAGE_STEP 100000 ++#define ISA1200_LDO_VOLTAGE_2V3 23 ++#define ISA1200_LDO_VOLTAGE_3V1 31 ++#define ISA1200_LDO_VOLTAGE_MIN 2300000 ++#define ISA1200_LDO_VOLTAGE_MAX 3800000 ++ ++/* ++ * The output frequency is calculated with this formula: ++ * ++ * base clock frequency ++ * fout = ----------------------------------------- ++ * (128 - PWM_FREQ) * 2 * PLLDIV * PWM_PERIOD ++ * ++ * The base clock frequency is the clock frequency provided on the ++ * clock input to the chip, divided by the value in HCTRL0 ++ * ++ * PWM_FREQ is configured in register HCTRL4, it is common to set this ++ * to 0 to get only two variables to calculate. ++ * ++ * PLLDIV is configured in register HCTRL3 (bits 7..4, so 0..15) ++ * PWM_PERIOD is configured in register HCTRL6 ++ * Further the duty cycle can be configured in HCTRL5 ++ */ ++ ++/* ++ * HCTRL0 configures clock or PWM input and selects the divider for ++ * the clock input. ++ */ ++#define ISA1200_HCTRL0 0x30 ++#define ISA1200_HCTRL0_HAP_ENABLE BIT(7) ++#define ISA1200_HCTRL0_PWM_GEN_MODE BIT(4) ++#define ISA1200_HCTRL0_PWM_INPUT_MODE BIT(3) ++#define ISA1200_HCTRL0_CLKDIV_128 128 ++ ++/* ++ * HCTRL1 configures the motor type and clock sourse ++ */ ++#define ISA1200_HCTRL1 0x31 ++#define ISA1200_HCTRL1_EXT_CLOCK BIT(7) ++#define ISA1200_HCTRL1_DAC_INVERT BIT(6) ++#define ISA1200_HCTRL1_MODE(n) (((n) & 1) << 5) ++ ++/* HCTRL2 controls software reset of the chip */ ++#define ISA1200_HCTRL2 0x32 ++#define ISA1200_HCTRL2_SW_RESET BIT(0) ++ ++/* ++ * HCTRL3 controls the PLL divisor ++ * ++ * Bits [0,1] are always set to 1 (we don't know what they are ++ * used for) and bit 4 and upward control the PLL divisor. ++ */ ++#define ISA1200_HCTRL3 0x33 ++#define ISA1200_HCTRL3_DEFAULT 0x03 ++#define ISA1200_HCTRL3_PLLDIV(n) (((n) & 0xf) << 4) ++ ++/* HCTRL4 controls the PWM frequency of external channel */ ++#define ISA1200_HCTRL4 0x34 ++ ++/* HCTRL5 controls the PWM high duty cycle of internal channel */ ++#define ISA1200_HCTRL5 0x35 ++ ++/* HCTRL6 controls the PWM period of internal channel */ ++#define ISA1200_HCTRL6 0x36 ++#define ISA1200_HCTRL6_PERIOD_SCALE 100 ++ ++/* The use for these registers is unknown but they exist */ ++#define ISA1200_HCTRL7 0x37 ++#define ISA1200_HCTRL8 0x38 ++#define ISA1200_HCTRL9 0x39 ++#define ISA1200_HCTRLA 0x3a ++#define ISA1200_HCTRLB 0x3b ++#define ISA1200_HCTRLC 0x3c ++#define ISA1200_HCTRLD 0x3d ++ ++#define ISA1200_EN_PINS_MAX 2 ++ ++static const struct regulator_bulk_data isa1200_supplies[] = { ++ { .supply = "vdd" }, { .supply = "vddp" }, ++}; ++ ++struct isa1200_config { ++ u32 ldo_voltage; ++ u32 mode; ++ u32 clkdiv; ++ u32 plldiv; ++ u32 freq; ++ u32 period; ++ u32 duty; ++}; ++ ++struct isa1200 { ++ struct input_dev *input; ++ struct regmap *map; ++ ++ struct clk *clk; ++ struct pwm_device *pwm; ++ struct gpio_descs *enable_gpios; ++ struct regulator_bulk_data *supplies; ++ ++ struct work_struct play_work; ++ struct isa1200_config config; ++ ++ int level; ++ bool suspended; ++ bool active; ++}; ++ ++static const struct regmap_config isa1200_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = ISA1200_HCTRLD, ++}; ++ ++static void isa1200_start(struct isa1200 *isa) ++{ ++ struct isa1200_config *config = &isa->config; ++ struct device *dev = &isa->input->dev; ++ struct pwm_state state; ++ u8 hctrl0 = 0, hctrl1 = 0; ++ DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); ++ int err; ++ ++ if (!isa->active) { ++ err = regulator_bulk_enable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ if (err) { ++ dev_err(dev, "failed to enable supplies (%d)\n", err); ++ return; ++ } ++ ++ err = clk_prepare_enable(isa->clk); ++ if (err) { ++ dev_err(dev, "failed to enable clock (%d)\n", err); ++ regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ return; ++ } ++ ++ bitmap_fill(values, ISA1200_EN_PINS_MAX); ++ gpiod_multi_set_value_cansleep(isa->enable_gpios, values); ++ ++ usleep_range(200, 300); ++ } ++ ++ regmap_write(isa->map, ISA1200_SCTRL, config->ldo_voltage); ++ ++ if (isa->clk) { ++ hctrl0 = ISA1200_HCTRL0_PWM_GEN_MODE; ++ hctrl1 = ISA1200_HCTRL1_EXT_CLOCK; ++ } ++ ++ if (isa->pwm) { ++ hctrl0 = ISA1200_HCTRL0_PWM_INPUT_MODE; ++ hctrl1 = 0; ++ } ++ ++ hctrl0 |= __ffs(config->clkdiv / ISA1200_HCTRL0_CLKDIV_128); ++ hctrl1 |= ISA1200_HCTRL1_DAC_INVERT; ++ hctrl1 |= ISA1200_HCTRL1_MODE(config->mode); ++ ++ regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); ++ regmap_write(isa->map, ISA1200_HCTRL1, hctrl1); ++ ++ /* Make sure to de-assert software reset */ ++ regmap_write(isa->map, ISA1200_HCTRL2, 0x00); ++ ++ /* PLL divisor */ ++ regmap_write(isa->map, ISA1200_HCTRL3, ++ ISA1200_HCTRL3_PLLDIV(config->plldiv) | ++ ISA1200_HCTRL3_DEFAULT); ++ ++ /* Frequency */ ++ regmap_write(isa->map, ISA1200_HCTRL4, config->freq); ++ /* Duty cycle */ ++ regmap_write(isa->map, ISA1200_HCTRL5, config->period >> 1); ++ /* Period */ ++ regmap_write(isa->map, ISA1200_HCTRL6, config->period); ++ ++ hctrl0 |= ISA1200_HCTRL0_HAP_ENABLE; ++ regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); ++ ++ if (isa->clk) ++ regmap_write(isa->map, ISA1200_HCTRL5, config->duty); ++ ++ if (isa->pwm) { ++ pwm_get_state(isa->pwm, &state); ++ state.duty_cycle = config->duty; ++ state.enabled = true; ++ pwm_apply_might_sleep(isa->pwm, &state); ++ } ++ ++ isa->active = true; ++} ++ ++static void isa1200_stop(struct isa1200 *isa) ++{ ++ struct pwm_state state; ++ DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); ++ ++ if (!isa->active) ++ return; ++ ++ if (isa->pwm) { ++ pwm_get_state(isa->pwm, &state); ++ state.duty_cycle = 0; ++ state.enabled = false; ++ pwm_apply_might_sleep(isa->pwm, &state); ++ } ++ ++ regmap_write(isa->map, ISA1200_HCTRL0, 0x00); ++ ++ bitmap_zero(values, ISA1200_EN_PINS_MAX); ++ gpiod_multi_set_value_cansleep(isa->enable_gpios, values); ++ ++ clk_disable_unprepare(isa->clk); ++ regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ ++ isa->active = false; ++} ++ ++static void isa1200_play_work(struct work_struct *work) ++{ ++ struct isa1200 *isa = container_of(work, struct isa1200, play_work); ++ ++ if (!READ_ONCE(isa->suspended)) { ++ if (isa->level) ++ isa1200_start(isa); ++ else ++ isa1200_stop(isa); ++ } ++} ++ ++static int isa1200_vibrator_play_effect(struct input_dev *input, void *data, ++ struct ff_effect *effect) ++{ ++ struct isa1200 *isa = input_get_drvdata(input); ++ int level; ++ ++ /* ++ * TODO: we currently only support rumble. ++ * The ISA1200 can control two motors and some devices ++ * also have two motors mounted. ++ */ ++ level = effect->u.rumble.strong_magnitude; ++ if (!level) ++ level = effect->u.rumble.weak_magnitude; ++ ++ dev_dbg(&input->dev, "FF effect type %d level %d\n", ++ effect->type, level); ++ ++ if (isa->level != level) { ++ isa->level = level; ++ if (!READ_ONCE(isa->suspended)) ++ schedule_work(&isa->play_work); ++ } ++ ++ return 0; ++} ++ ++static void isa1200_vibrator_close(struct input_dev *input) ++{ ++ struct isa1200 *isa = input_get_drvdata(input); ++ ++ cancel_work_sync(&isa->play_work); ++ isa1200_stop(isa); ++ isa->level = 0; ++} ++ ++static int isa1200_of_probe(struct i2c_client *client) ++{ ++ struct isa1200 *isa = i2c_get_clientdata(client); ++ struct isa1200_config *config = &isa->config; ++ struct device *dev = &client->dev; ++ struct fwnode_handle *ldo_node; ++ int err; ++ ++ isa->clk = devm_clk_get_optional(dev, NULL); ++ if (IS_ERR(isa->clk)) ++ return dev_err_probe(dev, PTR_ERR(isa->clk), ++ "failed to get clock\n"); ++ ++ isa->pwm = devm_pwm_get(dev, NULL); ++ if (IS_ERR(isa->pwm)) { ++ err = PTR_ERR(isa->pwm); ++ if (err == -ENODEV || err == -EINVAL) ++ isa->pwm = NULL; ++ else ++ return dev_err_probe(dev, err, "getting PWM\n"); ++ } ++ ++ if (!isa->clk && !isa->pwm) ++ return dev_err_probe(dev, -EINVAL, ++ "clock or PWM are required, none were provided\n"); ++ ++ err = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(isa1200_supplies), ++ isa1200_supplies, &isa->supplies); ++ if (err) ++ return dev_err_probe(dev, err, "failed to get supplies\n"); ++ ++ isa->enable_gpios = devm_gpiod_get_array_optional(dev, "control", ++ GPIOD_OUT_LOW); ++ if (IS_ERR(isa->enable_gpios)) ++ return dev_err_probe(dev, PTR_ERR(isa->enable_gpios), ++ "failed to get enable gpios\n"); ++ ++ if (isa->enable_gpios && isa->enable_gpios->ndescs > ISA1200_EN_PINS_MAX) ++ return dev_err_probe(dev, -EINVAL, "too many enable gpios\n"); ++ ++ ldo_node = device_get_named_child_node(dev, "ldo"); ++ if (!ldo_node) ++ return dev_err_probe(dev, -ENODEV, ++ "failed to get embedded LDO node\n"); ++ ++ err = fwnode_property_read_u32(ldo_node, "regulator-min-microvolt", ++ &config->ldo_voltage); ++ fwnode_handle_put(ldo_node); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to get ldo voltage\n"); ++ ++ config->ldo_voltage = clamp(config->ldo_voltage, ++ ISA1200_LDO_VOLTAGE_MIN, ++ ISA1200_LDO_VOLTAGE_MAX); ++ ++ config->ldo_voltage /= ISA1200_LDO_VOLTAGE_STEP; ++ if (config->ldo_voltage < ISA1200_LDO_VOLTAGE_3V1) ++ config->ldo_voltage = config->ldo_voltage - ++ ISA1200_LDO_VOLTAGE_2V3 + ++ ISA1200_LDO_VOLTAGE_BASE; ++ else ++ config->ldo_voltage -= ISA1200_LDO_VOLTAGE_3V1; ++ ++ config->mode = 0; /* LRA_MODE */ ++ device_property_read_u32(dev, "imagis,mode", &config->mode); ++ ++ config->clkdiv = ISA1200_HCTRL0_CLKDIV_128; ++ device_property_read_u32(dev, "imagis,clk-div", &config->clkdiv); ++ if (!config->clkdiv) ++ return dev_err_probe(dev, -EINVAL, "clk-div cannot be zero\n"); ++ ++ config->clkdiv = clamp(config->clkdiv, ISA1200_HCTRL0_CLKDIV_128, ++ ISA1200_HCTRL0_CLKDIV_128 << 3); ++ ++ err = device_property_read_u32(dev, "imagis,pll-div", &config->plldiv); ++ if (err || !config->plldiv) ++ config->plldiv = 1; ++ ++ config->period = 0; ++ config->freq = 0; ++ config->duty = 0; ++ ++ if (isa->clk) { ++ err = device_property_read_u32(dev, "imagis,period-ns", ++ &config->period); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to get period\n"); ++ ++ /* ++ * TODO: The scale value is arbitrary, but it fits observations ++ * quite well, and the exact conversion method is unknown. ++ * The period property value returned above is the HCTRL6 ++ * register value set by the vendor code, multiplied by 100. ++ */ ++ config->period /= ISA1200_HCTRL6_PERIOD_SCALE; ++ config->duty = config->period >> 1; ++ } ++ ++ if (isa->pwm) { ++ struct pwm_state state; ++ ++ pwm_init_state(isa->pwm, &state); ++ ++ if (!state.period) ++ return dev_err_probe(dev, -EINVAL, ++ "PWM period cannot be zero\n"); ++ ++ config->freq = div64_u64(NANO, state.period * config->clkdiv); ++ config->duty = state.period >> 1; ++ ++ err = pwm_apply_might_sleep(isa->pwm, &state); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to apply initial PWM state\n"); ++ } ++ ++ /* ++ * TODO: If device is using a clock, this property should return the ++ * value written to the HCTRL5 register by downstrem code. It likely ++ * needs to be converted into a meaningful duty cycle value, though ++ * unfortunately the exact conversion mechanism is unknown. If the ++ * device uses PWM, this property will return the correct duty cycle ++ * in nanoseconds. ++ */ ++ device_property_read_u32(dev, "imagis,duty-cycle-ns", &config->duty); ++ ++ return 0; ++} ++ ++static int isa1200_probe(struct i2c_client *client) ++{ ++ struct isa1200 *isa; ++ struct device *dev = &client->dev; ++ int err; ++ ++ isa = devm_kzalloc(dev, sizeof(*isa), GFP_KERNEL); ++ if (!isa) ++ return -ENOMEM; ++ ++ isa->input = devm_input_allocate_device(dev); ++ if (!isa->input) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(client, isa); ++ ++ err = isa1200_of_probe(client); ++ if (err) ++ return err; ++ ++ isa->map = devm_regmap_init_i2c(client, &isa1200_regmap_config); ++ if (IS_ERR(isa->map)) ++ return dev_err_probe(dev, PTR_ERR(isa->map), ++ "failed to initialize register map\n"); ++ ++ INIT_WORK(&isa->play_work, isa1200_play_work); ++ ++ isa->input->name = "isa1200-haptic"; ++ isa->input->id.bustype = BUS_I2C; ++ isa->input->close = isa1200_vibrator_close; ++ ++ isa->active = false; ++ ++ input_set_drvdata(isa->input, isa); ++ ++ /* TODO: this hardware can likely support more than rumble */ ++ input_set_capability(isa->input, EV_FF, FF_RUMBLE); ++ ++ err = input_ff_create_memless(isa->input, NULL, ++ isa1200_vibrator_play_effect); ++ if (err) ++ return dev_err_probe(dev, err, "failed to create FF dev\n"); ++ ++ err = input_register_device(isa->input); ++ if (err) ++ return dev_err_probe(dev, err, "failed to register input dev\n"); ++ ++ return 0; ++} ++ ++static int isa1200_suspend(struct device *dev) ++{ ++ struct isa1200 *isa = dev_get_drvdata(dev); ++ ++ guard(mutex)(&isa->input->mutex); ++ ++ if (input_device_enabled(isa->input)) { ++ WRITE_ONCE(isa->suspended, true); ++ cancel_work_sync(&isa->play_work); ++ isa1200_stop(isa); ++ } ++ ++ return 0; ++} ++ ++static int isa1200_resume(struct device *dev) ++{ ++ struct isa1200 *isa = dev_get_drvdata(dev); ++ ++ guard(mutex)(&isa->input->mutex); ++ ++ if (input_device_enabled(isa->input)) { ++ WRITE_ONCE(isa->suspended, false); ++ if (isa->level) ++ schedule_work(&isa->play_work); ++ } ++ ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(isa1200_pm_ops, isa1200_suspend, isa1200_resume); ++ ++static const struct of_device_id isa1200_of_match[] = { ++ { .compatible = "imagis,isa1200" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, isa1200_of_match); ++ ++static struct i2c_driver isa1200_i2c_driver = { ++ .driver = { ++ .name = "isa1200", ++ .of_match_table = isa1200_of_match, ++ .pm = pm_sleep_ptr(&isa1200_pm_ops), ++ }, ++ .probe = isa1200_probe, ++}; ++module_i2c_driver(isa1200_i2c_driver); ++ ++MODULE_AUTHOR("Linus Walleij "); ++MODULE_AUTHOR("Svyatoslav Ryhel "); ++MODULE_DESCRIPTION("Imagis ISA1200 haptic feedback unit"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/mouse/elan_i2c.h b/drivers/input/mouse/elan_i2c.h +--- a/drivers/input/mouse/elan_i2c.h ++++ b/drivers/input/mouse/elan_i2c.h +@@ -102,7 +102,8 @@ struct elan_transport_ops { + int (*prepare_fw_update)(struct i2c_client *client, u16 ic_type, + u8 iap_version, u16 fw_page_size); + int (*write_fw_block)(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx); ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx); + int (*finish_fw_update)(struct i2c_client *client, + struct completion *reset_done); + +diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c +--- a/drivers/input/mouse/elan_i2c_core.c ++++ b/drivers/input/mouse/elan_i2c_core.c +@@ -16,27 +16,27 @@ + */ + + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +-#include +-#include +-#include +-#include +-#include +-#include +-#include + #include +-#include ++#include ++#include + #include + #include + #include + #include ++#include ++#include ++#include ++#include + #include + + #include "elan_i2c.h" +@@ -86,6 +86,7 @@ struct elan_tp_data { + u16 ic_type; + u16 fw_validpage_count; + u16 fw_page_size; ++ u16 fw_page_delay; + u32 fw_signature_address; + + u8 min_baseline; +@@ -127,8 +128,11 @@ static u32 elan_i2c_lookup_quirks(u16 ic_type, u16 product_id) + } + + static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, +- u32 *signature_address, u16 *page_size) ++ u32 *signature_address, u16 *page_size, ++ u16 *page_delay) + { ++ *page_delay = 30; ++ + switch (ic_type) { + case 0x00: + case 0x06: +@@ -164,6 +168,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, + break; + case 0x19: + *validpage_count = 2032; ++ *page_delay = 10; + break; + default: + /* unknown ic type clear value */ +@@ -179,6 +184,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, + if ((ic_type == 0x14 || ic_type == 0x15) && iap_version >= 2) { + *validpage_count /= 8; + *page_size = ETP_FW_PAGE_SIZE_512; ++ *page_delay = 50; + } else if (ic_type >= 0x0D && iap_version >= 1) { + *validpage_count /= 2; + *page_size = ETP_FW_PAGE_SIZE_128; +@@ -368,7 +374,8 @@ static int elan_query_device_info(struct elan_tp_data *data) + error = elan_get_fwinfo(data->ic_type, data->iap_version, + &data->fw_validpage_count, + &data->fw_signature_address, +- &data->fw_page_size); ++ &data->fw_page_size, ++ &data->fw_page_delay); + if (error) + dev_warn(&data->client->dev, + "unexpected iap version %#04x (ic type: %#04x), firmware update will not work\n", +@@ -479,14 +486,16 @@ static int elan_query_device_parameters(struct elan_tp_data *data) + ********************************************************** + */ + static int elan_write_fw_block(struct elan_tp_data *data, u16 page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 page_delay, const u8 *page, u16 checksum, ++ int idx) + { + int retry = ETP_RETRY_COUNT; + int error; + + do { + error = data->ops->write_fw_block(data->client, page_size, +- page, checksum, idx); ++ page_delay, page, checksum, ++ idx); + if (!error) + return 0; + +@@ -525,7 +534,8 @@ static int __elan_update_firmware(struct elan_tp_data *data, + checksum += ((page[j + 1] << 8) | page[j]); + + error = elan_write_fw_block(data, data->fw_page_size, +- page, checksum, i); ++ data->fw_page_delay, page, checksum, ++ i); + if (error) { + dev_err(dev, "write page %d fail: %d\n", i, error); + return error; +diff --git a/drivers/input/mouse/elan_i2c_i2c.c b/drivers/input/mouse/elan_i2c_i2c.c +--- a/drivers/input/mouse/elan_i2c_i2c.c ++++ b/drivers/input/mouse/elan_i2c_i2c.c +@@ -625,7 +625,8 @@ static int elan_i2c_prepare_fw_update(struct i2c_client *client, u16 ic_type, + } + + static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx) + { + struct device *dev = &client->dev; + u8 val[3]; +@@ -650,7 +651,7 @@ static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, + } + + /* Wait for F/W to update one page ROM data. */ +- msleep(fw_page_size == ETP_FW_PAGE_SIZE_512 ? 50 : 35); ++ msleep(fw_page_delay); + + error = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val); + if (error) { +diff --git a/drivers/input/mouse/elan_i2c_smbus.c b/drivers/input/mouse/elan_i2c_smbus.c +--- a/drivers/input/mouse/elan_i2c_smbus.c ++++ b/drivers/input/mouse/elan_i2c_smbus.c +@@ -416,7 +416,8 @@ static int elan_smbus_prepare_fw_update(struct i2c_client *client, u16 ic_type, + + + static int elan_smbus_write_fw_block(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx) + { + struct device *dev = &client->dev; + int error; +diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c +--- a/drivers/input/mouse/focaltech.c ++++ b/drivers/input/mouse/focaltech.c +@@ -78,8 +78,8 @@ struct focaltech_finger_state { + * Absolute position (from the bottom left corner) of the + * finger. + */ +- unsigned int x; +- unsigned int y; ++ int x; ++ int y; + }; + + /* +@@ -108,7 +108,7 @@ struct focaltech_hw_state { + }; + + struct focaltech_data { +- unsigned int x_max, y_max; ++ int x_max, y_max; + struct focaltech_hw_state state; + }; + +@@ -126,17 +126,16 @@ static void focaltech_report_state(struct psmouse *psmouse) + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); + if (active) { +- unsigned int clamped_x, clamped_y; + /* + * The touchpad might report invalid data, so we clamp + * the resulting values so that we do not confuse +- * userspace. ++ * userspace or accumulate coordinate wind-up. + */ +- clamped_x = clamp(finger->x, 0U, priv->x_max); +- clamped_y = clamp(finger->y, 0U, priv->y_max); +- input_report_abs(dev, ABS_MT_POSITION_X, clamped_x); ++ finger->x = clamp(finger->x, 0, priv->x_max); ++ finger->y = clamp(finger->y, 0, priv->y_max); ++ input_report_abs(dev, ABS_MT_POSITION_X, finger->x); + input_report_abs(dev, ABS_MT_POSITION_Y, +- priv->y_max - clamped_y); ++ priv->y_max - finger->y); + input_report_abs(dev, ABS_TOOL_WIDTH, state->width); + } + } +diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c +--- a/drivers/input/mouse/psmouse-base.c ++++ b/drivers/input/mouse/psmouse-base.c +@@ -256,9 +256,8 @@ static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_sta + */ + void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + { +- serio_pause_rx(psmouse->ps2dev.serio); ++ guard(serio_pause_rx)(psmouse->ps2dev.serio); + __psmouse_set_state(psmouse, new_state); +- serio_continue_rx(psmouse->ps2dev.serio); + } + + /* +@@ -267,7 +266,15 @@ void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + */ + static int psmouse_handle_byte(struct psmouse *psmouse) + { +- psmouse_ret_t rc = psmouse->protocol_handler(psmouse); ++ psmouse_ret_t rc; ++ ++ /* protocol_handler is NULL when device is being disconnected */ ++ if (unlikely(!psmouse->protocol_handler)) { ++ psmouse->pktcnt = 0; ++ return 0; ++ } ++ ++ rc = psmouse->protocol_handler(psmouse); + + switch (rc) { + case PSMOUSE_BAD_DATA: +@@ -484,13 +491,16 @@ static int psmouse_poll(struct psmouse *psmouse) + PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)); + } + +-static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) ++static bool psmouse_check_pnp_id(const char *p, const char * const ids[]) + { +- int i; ++ const char * const *id; ++ size_t len; + +- for (i = 0; ids[i]; i++) +- if (!strcasecmp(id, ids[i])) ++ for (id = ids; *id; id++) { ++ len = strlen(*id); ++ if (!strncasecmp(p, *id, len) && (p[len] == ' ' || p[len] == '\0')) + return true; ++ } + + return false; + } +@@ -501,28 +511,26 @@ static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) + bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]) + { + struct serio *serio = psmouse->ps2dev.serio; +- char *p, *fw_id_copy, *save_ptr; +- bool found = false; ++ const char *p = serio->firmware_id; + +- if (strncmp(serio->firmware_id, "PNP: ", 5)) ++ if (!strstarts(p, "PNP: ")) + return false; + +- fw_id_copy = kstrndup(&serio->firmware_id[5], +- sizeof(serio->firmware_id) - 5, +- GFP_KERNEL); +- if (!fw_id_copy) +- return false; ++ p += 5; ++ while (*p) { ++ p = skip_spaces(p); ++ if (!*p) ++ break; + +- save_ptr = fw_id_copy; +- while ((p = strsep(&fw_id_copy, " ")) != NULL) { +- if (psmouse_check_pnp_id(p, ids)) { +- found = true; ++ if (psmouse_check_pnp_id(p, ids)) ++ return true; ++ ++ p = strchr(p, ' '); ++ if (!p) + break; +- } + } + +- kfree(save_ptr); +- return found; ++ return false; + } + + /* +@@ -1311,10 +1319,10 @@ static void psmouse_resync(struct work_struct *work) + bool failed = false, enabled = false; + int i; + +- mutex_lock(&psmouse_mutex); ++ guard(mutex)(&psmouse_mutex); + + if (psmouse->state != PSMOUSE_RESYNCING) +- goto out; ++ return; + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); +@@ -1392,8 +1400,6 @@ static void psmouse_resync(struct work_struct *work) + + if (parent) + psmouse_activate(parent); +- out: +- mutex_unlock(&psmouse_mutex); + } + + /* +@@ -1404,7 +1410,7 @@ static void psmouse_cleanup(struct serio *serio) + struct psmouse *psmouse = psmouse_from_serio(serio); + struct psmouse *parent = NULL; + +- mutex_lock(&psmouse_mutex); ++ guard(mutex)(&psmouse_mutex); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); +@@ -1440,8 +1446,6 @@ static void psmouse_cleanup(struct serio *serio) + + psmouse_activate(parent); + } +- +- mutex_unlock(&psmouse_mutex); + } + + /* +@@ -1452,20 +1456,20 @@ static void psmouse_disconnect(struct serio *serio) + struct psmouse *psmouse = psmouse_from_serio(serio); + struct psmouse *parent = NULL; + +- mutex_lock(&psmouse_mutex); ++ disable_work_sync(&psmouse->resync_work); ++ ++ guard(mutex)(&psmouse_mutex); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + +- /* make sure we don't have a resync in progress */ +- mutex_unlock(&psmouse_mutex); +- disable_work_sync(&psmouse->resync_work); +- mutex_lock(&psmouse_mutex); +- + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); + psmouse_deactivate(parent); + } + ++ scoped_guard(serio_pause_rx, serio) ++ psmouse->protocol_handler = NULL; ++ + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + +@@ -1484,8 +1488,6 @@ static void psmouse_disconnect(struct serio *serio) + + if (parent) + psmouse_activate(parent); +- +- mutex_unlock(&psmouse_mutex); + } + + static int psmouse_switch_protocol(struct psmouse *psmouse, +@@ -1654,14 +1656,12 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) + enum psmouse_type type; + int rc = -1; + +- mutex_lock(&psmouse_mutex); ++ lockdep_assert_held(&psmouse_mutex); + + if (fast_reconnect) { + reconnect_handler = psmouse->fast_reconnect; +- if (!reconnect_handler) { +- rc = -ENOENT; +- goto out_unlock; +- } ++ if (!reconnect_handler) ++ return -ENOENT; + } else { + reconnect_handler = psmouse->reconnect; + } +@@ -1713,18 +1713,20 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) + if (parent) + psmouse_activate(parent); + +-out_unlock: +- mutex_unlock(&psmouse_mutex); + return rc; + } + + static int psmouse_reconnect(struct serio *serio) + { ++ guard(mutex)(&psmouse_mutex); ++ + return __psmouse_reconnect(serio, false); + } + + static int psmouse_fast_reconnect(struct serio *serio) + { ++ guard(mutex)(&psmouse_mutex); ++ + return __psmouse_reconnect(serio, true); + } + +diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c +--- a/drivers/input/mouse/synaptics.c ++++ b/drivers/input/mouse/synaptics.c +@@ -164,6 +164,7 @@ static const char * const topbuttonpad_pnp_ids[] = { + #ifdef CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS + static const char * const smbus_pnp_ids[] = { + /* all of the topbuttonpad_pnp_ids are valid, we just add some extras */ ++ "DLL0597", /* Dell Inspiron 3521 */ + "DLL060d", /* Dell Precision M3800 */ + "LEN0048", /* X1 Carbon 3 */ + "LEN0046", /* X250 */ +diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c +--- a/drivers/input/mouse/synaptics_i2c.c ++++ b/drivers/input/mouse/synaptics_i2c.c +@@ -261,7 +261,7 @@ static s32 synaptics_i2c_reg_set(struct i2c_client *client, u16 reg, u8 val) + if (error) + return error; + +- return error; ++ return 0; + } + + static s32 synaptics_i2c_word_get(struct i2c_client *client, u16 reg) +diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c +--- a/drivers/input/rmi4/rmi_f12.c ++++ b/drivers/input/rmi4/rmi_f12.c +@@ -155,6 +155,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + offset += 4; + } + ++ /* When platform data are provided, we're done */ ++ if (sensor->x_mm && sensor->y_mm) ++ return 0; ++ + /* + * Use the Query DPM feature when the resolution query register + * exists. +@@ -171,8 +175,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + } + dpm_resolution = buf[0]; + +- sensor->x_mm = sensor->max_x / dpm_resolution; +- sensor->y_mm = sensor->max_y / dpm_resolution; ++ if (!sensor->x_mm) ++ sensor->x_mm = sensor->max_x / dpm_resolution; ++ if (!sensor->y_mm) ++ sensor->y_mm = sensor->max_y / dpm_resolution; + } else { + if (rmi_register_desc_has_subpacket(item, 3)) { + rx_receivers = buf[offset]; +@@ -184,8 +190,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + if (rmi_register_desc_has_subpacket(item, 4)) + offset += 1; + +- sensor->x_mm = (pitch_x * rx_receivers) >> 12; +- sensor->y_mm = (pitch_y * tx_receivers) >> 12; ++ if (!sensor->x_mm) ++ sensor->x_mm = (pitch_x * rx_receivers) >> 12; ++ if (!sensor->y_mm) ++ sensor->y_mm = (pitch_y * tx_receivers) >> 12; + } + + rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: x_mm: %d y_mm: %d\n", __func__, +diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c +--- a/drivers/input/rmi4/rmi_smbus.c ++++ b/drivers/input/rmi4/rmi_smbus.c +@@ -177,13 +177,8 @@ static int smb_block_read(struct rmi_transport_dev *xport, + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + struct i2c_client *client = rmi_smb->client; +- int retval; + +- retval = i2c_smbus_read_block_data(client, commandcode, buf); +- if (retval < 0) +- return retval; +- +- return retval; ++ return i2c_smbus_read_block_data(client, commandcode, buf); + } + + static int rmi_smb_read_block(struct rmi_transport_dev *xport, u16 rmiaddr, +diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig +--- a/drivers/input/serio/Kconfig ++++ b/drivers/input/serio/Kconfig +@@ -106,6 +106,33 @@ config SERIO_GSCPS2 + To compile this driver as a module, choose M here: the + module will be called gscps2. + ++config SERIO_GSCPS2_RDI_KEYCODES ++ bool "Use PrecisionBook keyboard scancodes" ++ depends on SERIO_GSCPS2 ++ default n ++ help ++ If you have an RDI PrecisionBook, say Y here if you want to use its ++ built-in keyboard (as opposed to an external keyboard). ++ ++ The PrecisionBook has five keys that conflict with those used by most ++ AT and PS/2 keyboards. These are as follows: ++ ++ PrecisionBook Standard AT or PS/2 ++ ++ F1 F12 ++ Left Ctrl Left Alt ++ Caps Lock Left Ctrl ++ Right Ctrl Caps Lock ++ Left 102nd key (the key to the right of Left Shift) ++ ++ If you say N here, and use the PrecisionBook keyboard, then each key ++ in the left-hand column will be interpreted as the corresponding key ++ in the right-hand column. ++ ++ If you say Y here, and use an external keyboard, then each key in the ++ right-hand column will be interpreted as the key shown in the ++ left-hand column. ++ + config HP_SDC + tristate "HP System Device Controller i8042 Support" + depends on (GSC || HP300) && SERIO +diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c +--- a/drivers/input/serio/gscps2.c ++++ b/drivers/input/serio/gscps2.c +@@ -22,18 +22,15 @@ + * was usable/enabled ?) + */ + +-#include +-#include +-#include +-#include ++#include + #include + #include +-#include +-#include ++#include + #include ++#include ++#include + + #include +-#include + #include + + MODULE_AUTHOR("Laurent Canet , Thibaut Varene , Helge Deller "); +@@ -80,6 +77,116 @@ MODULE_LICENSE("GPL"); + #define GSC_ID_KEYBOARD 0 /* device ID values */ + #define GSC_ID_MOUSE 1 + ++#ifndef CONFIG_SERIO_GSCPS2_RDI_KEYCODES ++# define CONFLICT(x, y) x ++#else ++# define CONFLICT(x, y) y ++#endif ++ ++/* ++ * Sadly RDI (Tadpole) decided to ship a different keyboard layout ++ * than HP for their PS/2 laptop keyboard which leads to conflicting ++ * keycodes between a normal HP PS/2 keyboard and a RDI PrecisionBook. ++ * HP: RDI: ++ */ ++#define C_07 CONFLICT(KEY_F12, KEY_F1) ++#define C_11 CONFLICT(KEY_LEFTALT, KEY_LEFTCTRL) ++#define C_14 CONFLICT(KEY_LEFTCTRL, KEY_CAPSLOCK) ++#define C_58 CONFLICT(KEY_CAPSLOCK, KEY_RIGHTCTRL) ++#define C_61 CONFLICT(KEY_102ND, KEY_LEFT) ++ ++/* ++ * Special keycode value recognized by atkbd (ATKBD_KEY_NULL) to silently ++ * discard scancodes without generating input events or "unknown key" warnings. ++ */ ++#define KEY_NULL 255 ++ ++#define KEYMAP_ENTRY(scancode, keycode) (((scancode) << 16) | (keycode)) ++ ++static const u32 gscps2_keymap[] = { ++ KEYMAP_ENTRY(0x01, KEY_F9), KEYMAP_ENTRY(0x03, KEY_F5), ++ KEYMAP_ENTRY(0x04, KEY_F3), KEYMAP_ENTRY(0x05, KEY_F1), ++ KEYMAP_ENTRY(0x06, KEY_F2), KEYMAP_ENTRY(0x07, C_07), ++ KEYMAP_ENTRY(0x08, KEY_ESC), KEYMAP_ENTRY(0x09, KEY_F10), ++ KEYMAP_ENTRY(0x0a, KEY_F8), KEYMAP_ENTRY(0x0b, KEY_F6), ++ KEYMAP_ENTRY(0x0c, KEY_F4), KEYMAP_ENTRY(0x0d, KEY_TAB), ++ KEYMAP_ENTRY(0x0e, KEY_GRAVE), KEYMAP_ENTRY(0x0f, KEY_F2), ++ KEYMAP_ENTRY(0x11, C_11), KEYMAP_ENTRY(0x12, KEY_LEFTSHIFT), ++ KEYMAP_ENTRY(0x14, C_14), KEYMAP_ENTRY(0x15, KEY_Q), ++ KEYMAP_ENTRY(0x16, KEY_1), KEYMAP_ENTRY(0x17, KEY_F3), ++ KEYMAP_ENTRY(0x19, KEY_LEFTALT), KEYMAP_ENTRY(0x1a, KEY_Z), ++ KEYMAP_ENTRY(0x1b, KEY_S), KEYMAP_ENTRY(0x1c, KEY_A), ++ KEYMAP_ENTRY(0x1d, KEY_W), KEYMAP_ENTRY(0x1e, KEY_2), ++ KEYMAP_ENTRY(0x1f, KEY_F4), KEYMAP_ENTRY(0x21, KEY_C), ++ KEYMAP_ENTRY(0x22, KEY_X), KEYMAP_ENTRY(0x23, KEY_D), ++ KEYMAP_ENTRY(0x24, KEY_E), KEYMAP_ENTRY(0x25, KEY_4), ++ KEYMAP_ENTRY(0x26, KEY_3), KEYMAP_ENTRY(0x27, KEY_F5), ++ KEYMAP_ENTRY(0x29, KEY_SPACE), KEYMAP_ENTRY(0x2a, KEY_V), ++ KEYMAP_ENTRY(0x2b, KEY_F), KEYMAP_ENTRY(0x2c, KEY_T), ++ KEYMAP_ENTRY(0x2d, KEY_R), KEYMAP_ENTRY(0x2e, KEY_5), ++ KEYMAP_ENTRY(0x2f, KEY_F6), KEYMAP_ENTRY(0x31, KEY_N), ++ KEYMAP_ENTRY(0x32, KEY_B), KEYMAP_ENTRY(0x33, KEY_H), ++ KEYMAP_ENTRY(0x34, KEY_G), KEYMAP_ENTRY(0x35, KEY_Y), ++ KEYMAP_ENTRY(0x36, KEY_6), KEYMAP_ENTRY(0x37, KEY_F7), ++ KEYMAP_ENTRY(0x39, KEY_RIGHTALT), KEYMAP_ENTRY(0x3a, KEY_M), ++ KEYMAP_ENTRY(0x3b, KEY_J), KEYMAP_ENTRY(0x3c, KEY_U), ++ KEYMAP_ENTRY(0x3d, KEY_7), KEYMAP_ENTRY(0x3e, KEY_8), ++ KEYMAP_ENTRY(0x3f, KEY_F8), KEYMAP_ENTRY(0x41, KEY_COMMA), ++ KEYMAP_ENTRY(0x42, KEY_K), KEYMAP_ENTRY(0x43, KEY_I), ++ KEYMAP_ENTRY(0x44, KEY_O), KEYMAP_ENTRY(0x45, KEY_0), ++ KEYMAP_ENTRY(0x46, KEY_9), KEYMAP_ENTRY(0x47, KEY_F9), ++ KEYMAP_ENTRY(0x49, KEY_DOT), KEYMAP_ENTRY(0x4a, KEY_SLASH), ++ KEYMAP_ENTRY(0x4b, KEY_L), KEYMAP_ENTRY(0x4c, KEY_SEMICOLON), ++ KEYMAP_ENTRY(0x4d, KEY_P), KEYMAP_ENTRY(0x4e, KEY_MINUS), ++ KEYMAP_ENTRY(0x4f, KEY_F10), KEYMAP_ENTRY(0x52, KEY_APOSTROPHE), ++ KEYMAP_ENTRY(0x54, KEY_LEFTBRACE), KEYMAP_ENTRY(0x55, KEY_EQUAL), ++ KEYMAP_ENTRY(0x56, KEY_F11), KEYMAP_ENTRY(0x57, KEY_SYSRQ), ++ KEYMAP_ENTRY(0x58, C_58), KEYMAP_ENTRY(0x59, KEY_RIGHTSHIFT), ++ KEYMAP_ENTRY(0x5a, KEY_ENTER), KEYMAP_ENTRY(0x5b, KEY_RIGHTBRACE), ++ KEYMAP_ENTRY(0x5c, KEY_BACKSLASH), KEYMAP_ENTRY(0x5d, KEY_BACKSLASH), ++ KEYMAP_ENTRY(0x5e, KEY_F12), KEYMAP_ENTRY(0x5f, KEY_SCROLLLOCK), ++ KEYMAP_ENTRY(0x60, KEY_DOWN), KEYMAP_ENTRY(0x61, C_61), ++ KEYMAP_ENTRY(0x62, KEY_PAUSE), KEYMAP_ENTRY(0x63, KEY_UP), ++ KEYMAP_ENTRY(0x64, KEY_DELETE), KEYMAP_ENTRY(0x65, KEY_END), ++ KEYMAP_ENTRY(0x66, KEY_BACKSPACE), KEYMAP_ENTRY(0x67, KEY_INSERT), ++ KEYMAP_ENTRY(0x69, KEY_KP1), KEYMAP_ENTRY(0x6a, KEY_RIGHT), ++ KEYMAP_ENTRY(0x6b, KEY_KP4), KEYMAP_ENTRY(0x6c, KEY_KP7), ++ KEYMAP_ENTRY(0x6d, KEY_PAGEDOWN), KEYMAP_ENTRY(0x6e, KEY_HOME), ++ KEYMAP_ENTRY(0x6f, KEY_PAGEUP), KEYMAP_ENTRY(0x70, KEY_KP0), ++ KEYMAP_ENTRY(0x71, KEY_KPDOT), KEYMAP_ENTRY(0x72, KEY_KP2), ++ KEYMAP_ENTRY(0x73, KEY_KP5), KEYMAP_ENTRY(0x74, KEY_KP6), ++ KEYMAP_ENTRY(0x75, KEY_KP8), KEYMAP_ENTRY(0x76, KEY_ESC), ++ KEYMAP_ENTRY(0x77, KEY_NUMLOCK), KEYMAP_ENTRY(0x78, KEY_F11), ++ KEYMAP_ENTRY(0x79, KEY_KPPLUS), KEYMAP_ENTRY(0x7a, KEY_KP3), ++ KEYMAP_ENTRY(0x7b, KEY_KPMINUS), KEYMAP_ENTRY(0x7c, KEY_KPASTERISK), ++ KEYMAP_ENTRY(0x7d, KEY_KP9), KEYMAP_ENTRY(0x7e, KEY_SCROLLLOCK), ++ KEYMAP_ENTRY(0x7f, KEY_102ND), KEYMAP_ENTRY(0x91, KEY_RIGHTALT), ++ KEYMAP_ENTRY(0x92, KEY_NULL), KEYMAP_ENTRY(0x94, KEY_RIGHTCTRL), ++ KEYMAP_ENTRY(0x9d, KEY_CAPSLOCK), KEYMAP_ENTRY(0x9f, KEY_LEFTMETA), ++ KEYMAP_ENTRY(0xa7, KEY_RIGHTMETA), KEYMAP_ENTRY(0xaf, KEY_COMPOSE), ++ KEYMAP_ENTRY(0xca, KEY_KPSLASH), KEYMAP_ENTRY(0xda, KEY_KPENTER), ++ KEYMAP_ENTRY(0xe9, KEY_END), KEYMAP_ENTRY(0xeb, KEY_LEFT), ++ KEYMAP_ENTRY(0xec, KEY_HOME), KEYMAP_ENTRY(0xf0, KEY_INSERT), ++ KEYMAP_ENTRY(0xf1, KEY_DELETE), KEYMAP_ENTRY(0xf2, KEY_DOWN), ++ KEYMAP_ENTRY(0xf4, KEY_RIGHT), KEYMAP_ENTRY(0xf5, KEY_UP), ++ KEYMAP_ENTRY(0xf7, KEY_PAUSE), KEYMAP_ENTRY(0xfa, KEY_PAGEDOWN), ++ KEYMAP_ENTRY(0xfc, KEY_SYSRQ), KEYMAP_ENTRY(0xfd, KEY_PAGEUP), ++ ++ /* Escaped keycodes */ ++ KEYMAP_ENTRY(0x103, KEY_F7), KEYMAP_ENTRY(0x10b, KEY_LEFTMETA), ++ KEYMAP_ENTRY(0x10c, KEY_RIGHTMETA), KEYMAP_ENTRY(0x111, KEY_RIGHTALT), ++ KEYMAP_ENTRY(0x114, KEY_RIGHTCTRL), ++}; ++ ++static const struct property_entry gscps2_props[] = { ++ PROPERTY_ENTRY_U32_ARRAY("linux,keymap", gscps2_keymap), ++ { } ++}; ++ ++static const struct software_node gscps2_keyboard_node = { ++ .name = "gscps2-keyboard", ++ .properties = gscps2_props, ++}; + + static irqreturn_t gscps2_interrupt(int irq, void *dev); + +@@ -398,6 +505,17 @@ static int __init gscps2_probe(struct parisc_device *dev) + goto fail; + #endif + ++ if (ps2port->id == GSC_ID_KEYBOARD) { ++ ret = device_add_software_node(&serio->dev, ++ &gscps2_keyboard_node); ++ if (ret) { ++ dev_err(&dev->dev, ++ "failed to add software node for keyboard: %d\n", ++ ret); ++ goto fail; ++ } ++ } ++ + pr_info("serio: %s port at 0x%08lx irq %d @ %s\n", + ps2port->port->name, + hpa, +@@ -411,11 +529,16 @@ static int __init gscps2_probe(struct parisc_device *dev) + return 0; + + fail: ++ if (ps2port->id == GSC_ID_KEYBOARD) ++ device_remove_software_node(&serio->dev); ++ + free_irq(dev->irq, ps2port); + + fail_miserably: + iounmap(ps2port->addr); ++#if 0 + release_mem_region(dev->hpa.start, GSC_STATUS + 4); ++#endif + + fail_nomem: + kfree(ps2port); +@@ -434,6 +557,9 @@ static void __exit gscps2_remove(struct parisc_device *dev) + { + struct gscps2port *ps2port = dev_get_drvdata(&dev->dev); + ++ if (ps2port->id == GSC_ID_KEYBOARD) ++ device_remove_software_node(&ps2port->port->dev); ++ + serio_unregister_port(ps2port->port); + free_irq(dev->irq, ps2port); + gscps2_flush(ps2port); +@@ -465,16 +591,25 @@ static struct parisc_driver parisc_ps2_driver __refdata = { + + static int __init gscps2_init(void) + { +- register_parisc_driver(&parisc_ps2_driver); +- return 0; ++ int error; ++ ++ error = software_node_register(&gscps2_keyboard_node); ++ if (error) ++ return error; ++ ++ error = register_parisc_driver(&parisc_ps2_driver); ++ if (error) ++ software_node_unregister(&gscps2_keyboard_node); ++ ++ return error; + } + + static void __exit gscps2_exit(void) + { + unregister_parisc_driver(&parisc_ps2_driver); ++ software_node_unregister(&gscps2_keyboard_node); + } + + + module_init(gscps2_init); + module_exit(gscps2_exit); +- +diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h +--- a/drivers/input/serio/i8042-acpipnpio.h ++++ b/drivers/input/serio/i8042-acpipnpio.h +@@ -3,6 +3,7 @@ + #define _I8042_ACPIPNPIO_H + + #include ++#include + + #ifdef CONFIG_X86 + #include +@@ -1486,17 +1487,21 @@ static char i8042_pnp_aux_name[32]; + + static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size) + { +- strscpy(dst, "PNP:", dst_size); ++ struct seq_buf sb; ++ ++ seq_buf_init(&sb, dst, dst_size); ++ seq_buf_printf(&sb, "PNP:"); + + while (id) { +- strlcat(dst, " ", dst_size); +- strlcat(dst, id->id, dst_size); ++ seq_buf_printf(&sb, " %s", id->id); + id = id->next; + } + } + + static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did) + { ++ const char *name = pnp_dev_name(dev); ++ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + +@@ -1506,11 +1511,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * + if (pnp_irq_valid(dev,0)) + i8042_pnp_kbd_irq = pnp_irq(dev, 0); + +- strscpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); +- if (strlen(pnp_dev_name(dev))) { +- strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); +- strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name)); +- } ++ scnprintf(i8042_pnp_kbd_name, sizeof(i8042_pnp_kbd_name), "%s%s%s", ++ did->id, strlen(name) ? ":" : "", name); + i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id, + sizeof(i8042_kbd_firmware_id)); + i8042_kbd_fwnode = dev_fwnode(&dev->dev); +@@ -1524,6 +1526,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * + + static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did) + { ++ const char *name = pnp_dev_name(dev); ++ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + +@@ -1533,11 +1537,8 @@ static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id * + if (pnp_irq_valid(dev, 0)) + i8042_pnp_aux_irq = pnp_irq(dev, 0); + +- strscpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); +- if (strlen(pnp_dev_name(dev))) { +- strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); +- strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name)); +- } ++ scnprintf(i8042_pnp_aux_name, sizeof(i8042_pnp_aux_name), "%s%s%s", ++ did->id, strlen(name) ? ":" : "", name); + i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id, + sizeof(i8042_aux_firmware_id)); + +diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c +--- a/drivers/input/touchscreen/elo.c ++++ b/drivers/input/touchscreen/elo.c +@@ -257,7 +257,7 @@ static int elo_command_10(struct elo *elo, unsigned char *packet) + + static int elo_setup_10(struct elo *elo) + { +- static const char *elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; ++ static const char * const elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; + struct input_dev *dev = elo->dev; + unsigned char packet[ELO10_PACKET_LEN] = { ELO10_ID_CMD }; + +@@ -273,7 +273,7 @@ static int elo_setup_10(struct elo *elo) + + dev_info(&elo->serio->dev, + "%sTouch touchscreen, fw: %02x.%02x, features: 0x%02x, controller: 0x%02x\n", +- elo_types[(packet[1] -'0') & 0x03], ++ elo_types[(packet[1] - '0') & 0x03], + packet[5], packet[4], packet[3], packet[7]); + + return 0; +diff --git a/drivers/input/touchscreen/inexio.c b/drivers/input/touchscreen/inexio.c +--- a/drivers/input/touchscreen/inexio.c ++++ b/drivers/input/touchscreen/inexio.c +@@ -81,7 +81,9 @@ static irqreturn_t inexio_interrupt(struct serio *serio, + if (INEXIO_RESPONSE_BEGIN_BYTE&pinexio->data[0]) + inexio_process_data(pinexio); + else +- printk(KERN_DEBUG "inexio.c: unknown/unsynchronized data from device, byte %x\n",pinexio->data[0]); ++ dev_dbg(&serio->dev, ++ "unknown/unsynchronized data from device, byte %x\n", ++ pinexio->data[0]); + + return IRQ_HANDLED; + } +diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c +--- a/drivers/input/touchscreen/iqs5xx.c ++++ b/drivers/input/touchscreen/iqs5xx.c +@@ -785,7 +785,8 @@ static int iqs5xx_fw_file_parse(struct i2c_client *client, + switch (rec_type) { + case IQS5XX_REC_TYPE_DATA: + if (rec_addr < IQS5XX_CHKSM || +- rec_addr > IQS5XX_PMAP_END) { ++ rec_addr > IQS5XX_PMAP_END || ++ rec_len > IQS5XX_PMAP_END + 1 - rec_addr) { + dev_err(&client->dev, + "Invalid address at record %u\n", + rec_num); +diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c +--- a/drivers/input/touchscreen/mms114.c ++++ b/drivers/input/touchscreen/mms114.c +@@ -4,6 +4,8 @@ + // Copyright (c) 2012 Samsung Electronics Co., Ltd. + // Author: Joonyoung Shim + ++#include ++#include + #include + #include + #include +@@ -52,21 +54,13 @@ + #define MMS114_TYPE_TOUCHSCREEN 1 + #define MMS114_TYPE_TOUCHKEY 2 + +-enum mms_type { +- TYPE_MMS114 = 114, +- TYPE_MMS134S = 134, +- TYPE_MMS136 = 136, +- TYPE_MMS152 = 152, +- TYPE_MMS345L = 345, +-}; +- + struct mms114_data { ++ const struct mms_chip *chip; + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *core_reg; + struct regulator *io_reg; + struct touchscreen_properties props; +- enum mms_type type; + unsigned int contact_threshold; + unsigned int moving_threshold; + +@@ -77,9 +71,23 @@ struct mms114_data { + u8 cache_mode_control; + }; + ++struct mms_chip { ++ const char *name; ++ int event_size; ++ bool has_config_regs; ++ int (*get_version)(struct mms114_data *data); ++}; ++ ++#define MMS114_FLAGS_ID_MASK GENMASK(3, 0) ++#define MMS114_FLAGS_TYPE_MASK GENMASK(6, 5) ++#define MMS114_FLAGS_PRESSED_MASK BIT(7) ++ ++#define MMS114_XY_HI_X_MASK GENMASK(3, 0) ++#define MMS114_XY_HI_Y_MASK GENMASK(7, 4) ++ + struct mms114_touch { +- u8 id:4, reserved_bit4:1, type:2, pressed:1; +- u8 x_hi:4, y_hi:4; ++ u8 flags; ++ u8 xy_hi; + u8 x_lo; + u8 y_lo; + u8 width; +@@ -87,16 +95,16 @@ struct mms114_touch { + u8 reserved[2]; + } __packed; + +-static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, +- unsigned int len, u8 *val) ++static int __mms114_read_reg(struct mms114_data *data, u8 reg, ++ unsigned int len, void *val) + { + struct i2c_client *client = data->client; + struct i2c_msg xfer[2]; +- u8 buf = reg & 0xff; ++ u8 buf = reg; + int error; + +- if (reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL) +- BUG(); ++ if (WARN_ON(reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL)) ++ return -EINVAL; + + /* Write register */ + xfer[0].addr = client->addr; +@@ -116,12 +124,12 @@ static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, + "%s: i2c transfer failed (%d)\n", __func__, error); + return error < 0 ? error : -EIO; + } +- udelay(MMS114_I2C_DELAY); ++ usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); + + return 0; + } + +-static int mms114_read_reg(struct mms114_data *data, unsigned int reg) ++static int mms114_read_reg(struct mms114_data *data, u8 reg) + { + u8 val; + int error; +@@ -133,15 +141,14 @@ static int mms114_read_reg(struct mms114_data *data, unsigned int reg) + return error < 0 ? error : val; + } + +-static int mms114_write_reg(struct mms114_data *data, unsigned int reg, +- unsigned int val) ++static int mms114_write_reg(struct mms114_data *data, u8 reg, u8 val) + { + struct i2c_client *client = data->client; + u8 buf[2]; + int error; + +- buf[0] = reg & 0xff; +- buf[1] = val & 0xff; ++ buf[0] = reg; ++ buf[1] = val; + + error = i2c_master_send(client, buf, 2); + if (error != 2) { +@@ -149,7 +156,7 @@ static int mms114_write_reg(struct mms114_data *data, unsigned int reg, + "%s: i2c send failed (%d)\n", __func__, error); + return error < 0 ? error : -EIO; + } +- udelay(MMS114_I2C_DELAY); ++ usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); + + if (reg == MMS114_MODE_CONTROL) + data->cache_mode_control = val; +@@ -157,32 +164,119 @@ static int mms114_write_reg(struct mms114_data *data, unsigned int reg, + return 0; + } + ++static int mms114_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[6]; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); ++ if (error) ++ return error; ++ ++ dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", ++ buf[0], buf[1], buf[3]); ++ return 0; ++} ++ ++static int mms152_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[3]; ++ int group; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); ++ if (error) ++ return error; ++ ++ group = i2c_smbus_read_byte_data(data->client, MMS152_COMPAT_GROUP); ++ if (group < 0) ++ return group; ++ ++ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", ++ buf[0], buf[1], buf[2], group); ++ return 0; ++} ++ ++static int mms345l_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[3]; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); ++ if (error) ++ return error; ++ ++ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", ++ buf[0], buf[1], buf[2]); ++ return 0; ++} ++ ++static const struct mms_chip mms114_descriptor = { ++ .name = "MMS114", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms134s_descriptor = { ++ .name = "MMS134S", ++ .event_size = MMS136_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms136_descriptor = { ++ .name = "MMS136", ++ .event_size = MMS136_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms152_descriptor = { ++ .name = "MMS152", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = false, ++ .get_version = mms152_get_version, ++}; ++ ++static const struct mms_chip mms345l_descriptor = { ++ .name = "MMS345L", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = false, ++ .get_version = mms345l_get_version, ++}; ++ + static void mms114_process_mt(struct mms114_data *data, struct mms114_touch *touch) + { + struct i2c_client *client = data->client; + struct input_dev *input_dev = data->input_dev; +- unsigned int id; ++ unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); ++ unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, touch->flags); ++ bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); + unsigned int x; + unsigned int y; + +- if (touch->id == 0 || touch->id > MMS114_MAX_TOUCH) { +- dev_err(&client->dev, "Wrong touch id (%d)\n", touch->id); ++ if (id == 0 || id > MMS114_MAX_TOUCH) { ++ dev_err(&client->dev, "Wrong touch id (%d)\n", id); + return; + } + +- id = touch->id - 1; +- x = touch->x_lo | touch->x_hi << 8; +- y = touch->y_lo | touch->y_hi << 8; ++ id--; ++ x = touch->x_lo | FIELD_GET(MMS114_XY_HI_X_MASK, touch->xy_hi) << 8; ++ y = touch->y_lo | FIELD_GET(MMS114_XY_HI_Y_MASK, touch->xy_hi) << 8; + + dev_dbg(&client->dev, + "id: %d, type: %d, pressed: %d, x: %d, y: %d, width: %d, strength: %d\n", +- id, touch->type, touch->pressed, ++ id, type, pressed, + x, y, touch->width, touch->strength); + + input_mt_slot(input_dev, id); +- input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, touch->pressed); ++ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, pressed); + +- if (touch->pressed) { ++ if (pressed) { + touchscreen_report_pos(input_dev, &data->props, x, y, true); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, touch->width); + input_report_abs(input_dev, ABS_MT_PRESSURE, touch->strength); +@@ -195,21 +289,23 @@ static void mms114_process_touchkey(struct mms114_data *data, + struct i2c_client *client = data->client; + struct input_dev *input_dev = data->input_dev; + unsigned int keycode_id; ++ unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); ++ bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); + +- if (touch->id == 0) ++ if (id == 0) + return; + +- if (touch->id > data->num_keycodes) { ++ if (id > data->num_keycodes) { + dev_err(&client->dev, "Wrong touch id for touchkey (%d)\n", +- touch->id); ++ id); + return; + } + +- keycode_id = touch->id - 1; ++ keycode_id = id - 1; + dev_dbg(&client->dev, "keycode id: %d, pressed: %d\n", keycode_id, +- touch->pressed); ++ pressed); + +- input_report_key(input_dev, data->keycodes[keycode_id], touch->pressed); ++ input_report_key(input_dev, data->keycodes[keycode_id], pressed); + } + + static irqreturn_t mms114_interrupt(int irq, void *dev_id) +@@ -218,8 +314,8 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + struct i2c_client *client = data->client; + struct mms114_touch touch[MMS114_MAX_TOUCH]; + struct mms114_touch *t; ++ int event_size = data->chip->event_size; + int packet_size; +- int event_size; + int touch_size; + int index; + int error; +@@ -234,23 +330,17 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + goto out; + } + +- /* MMS136 has slightly different event size */ +- if (data->type == TYPE_MMS134S || data->type == TYPE_MMS136) +- event_size = MMS136_EVENT_SIZE; +- else +- event_size = MMS114_EVENT_SIZE; +- + touch_size = packet_size / event_size; + +- error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, +- (u8 *)touch); +- if (error < 0) ++ error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, touch); ++ if (error) + goto out; + + for (index = 0; index < touch_size; index++) { + t = (struct mms114_touch *)((u8 *)touch + index * event_size); ++ unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, t->flags); + +- switch (t->type) { ++ switch (type) { + case MMS114_TYPE_TOUCHSCREEN: + mms114_process_mt(data, t); + break; +@@ -261,7 +351,7 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + + default: + dev_err(&client->dev, "Wrong touch type (%d)\n", +- t->type); ++ type); + break; + } + } +@@ -290,65 +380,17 @@ static int mms114_set_active(struct mms114_data *data, bool active) + return mms114_write_reg(data, MMS114_MODE_CONTROL, val); + } + +-static int mms114_get_version(struct mms114_data *data) +-{ +- struct device *dev = &data->client->dev; +- u8 buf[6]; +- int group; +- int error; +- +- switch (data->type) { +- case TYPE_MMS345L: +- error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); +- if (error) +- return error; +- +- dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", +- buf[0], buf[1], buf[2]); +- break; +- +- case TYPE_MMS152: +- error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); +- if (error) +- return error; +- +- group = i2c_smbus_read_byte_data(data->client, +- MMS152_COMPAT_GROUP); +- if (group < 0) +- return group; +- +- dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", +- buf[0], buf[1], buf[2], group); +- break; +- +- case TYPE_MMS114: +- case TYPE_MMS134S: +- case TYPE_MMS136: +- error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); +- if (error) +- return error; +- +- dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", +- buf[0], buf[1], buf[3]); +- break; +- } +- +- return 0; +-} +- + static int mms114_setup_regs(struct mms114_data *data) + { + const struct touchscreen_properties *props = &data->props; + int val; + int error; + +- error = mms114_get_version(data); +- if (error < 0) ++ error = data->chip->get_version(data); ++ if (error) + return error; + +- /* MMS114, MMS134S and MMS136 have configuration and power on registers */ +- if (data->type != TYPE_MMS114 && data->type != TYPE_MMS134S && +- data->type != TYPE_MMS136) ++ if (!data->chip->has_config_regs) + return 0; + + error = mms114_set_active(data, true); +@@ -366,21 +408,21 @@ static int mms114_setup_regs(struct mms114_data *data) + if (error < 0) + return error; + +- val = props->max_x & 0xff; ++ val = props->max_y & 0xff; + error = mms114_write_reg(data, MMS114_Y_RESOLUTION, val); + if (error < 0) + return error; + + if (data->contact_threshold) { + error = mms114_write_reg(data, MMS114_CONTACT_THRESHOLD, +- data->contact_threshold); ++ data->contact_threshold); + if (error < 0) + return error; + } + + if (data->moving_threshold) { + error = mms114_write_reg(data, MMS114_MOVING_THRESHOLD, +- data->moving_threshold); ++ data->moving_threshold); + if (error < 0) + return error; + } +@@ -466,9 +508,9 @@ static int mms114_parse_legacy_bindings(struct mms114_data *data) + } + + device_property_read_u32(dev, "contact-threshold", +- &data->contact_threshold); ++ &data->contact_threshold); + device_property_read_u32(dev, "moving-threshold", +- &data->moving_threshold); ++ &data->moving_threshold); + + if (device_property_read_bool(dev, "x-invert")) + props->invert_x = true; +@@ -484,7 +526,6 @@ static int mms114_probe(struct i2c_client *client) + { + struct mms114_data *data; + struct input_dev *input_dev; +- const void *match_data; + int error; + int i; + +@@ -504,12 +545,10 @@ static int mms114_probe(struct i2c_client *client) + data->client = client; + data->input_dev = input_dev; + +- match_data = device_get_match_data(&client->dev); +- if (!match_data) ++ data->chip = i2c_get_match_data(client); ++ if (!data->chip) + return -EINVAL; + +- data->type = (enum mms_type)match_data; +- + data->num_keycodes = device_property_count_u32(&client->dev, + "linux,keycodes"); + if (data->num_keycodes == -EINVAL) { +@@ -521,7 +560,7 @@ static int mms114_probe(struct i2c_client *client) + return data->num_keycodes; + } else if (data->num_keycodes > MMS114_MAX_TOUCHKEYS) { + dev_warn(&client->dev, +- "Found %d linux,keycodes but max is %d, ignoring the rest\n", ++ "Found %d linux,keycodes but max is %d, ignoring the rest\n", + data->num_keycodes, MMS114_MAX_TOUCHKEYS); + data->num_keycodes = MMS114_MAX_TOUCHKEYS; + } +@@ -566,8 +605,7 @@ static int mms114_probe(struct i2c_client *client) + 0, data->props.max_y, 0, 0); + } + +- if (data->type == TYPE_MMS114 || data->type == TYPE_MMS134S || +- data->type == TYPE_MMS136) { ++ if (data->chip->has_config_regs) { + /* + * The firmware handles movement and pressure fuzz, so + * don't duplicate that in software. +@@ -582,8 +620,8 @@ static int mms114_probe(struct i2c_client *client) + } + + input_dev->name = devm_kasprintf(&client->dev, GFP_KERNEL, +- "MELFAS MMS%d Touchscreen", +- data->type); ++ "MELFAS %s Touchscreen", ++ data->chip->name); + if (!input_dev->name) + return -ENOMEM; + +@@ -679,29 +717,22 @@ static int mms114_resume(struct device *dev) + static DEFINE_SIMPLE_DEV_PM_OPS(mms114_pm_ops, mms114_suspend, mms114_resume); + + static const struct i2c_device_id mms114_id[] = { +- { .name = "mms114" }, ++ { .name = "mms114", .driver_data = (kernel_ulong_t)&mms114_descriptor }, ++ { .name = "mms134s", .driver_data = (kernel_ulong_t)&mms134s_descriptor }, ++ { .name = "mms136", .driver_data = (kernel_ulong_t)&mms136_descriptor }, ++ { .name = "mms152", .driver_data = (kernel_ulong_t)&mms152_descriptor }, ++ { .name = "mms345l", .driver_data = (kernel_ulong_t)&mms345l_descriptor }, + { } + }; + MODULE_DEVICE_TABLE(i2c, mms114_id); + + #ifdef CONFIG_OF + static const struct of_device_id mms114_dt_match[] = { +- { +- .compatible = "melfas,mms114", +- .data = (void *)TYPE_MMS114, +- }, { +- .compatible = "melfas,mms134s", +- .data = (void *)TYPE_MMS134S, +- }, { +- .compatible = "melfas,mms136", +- .data = (void *)TYPE_MMS136, +- }, { +- .compatible = "melfas,mms152", +- .data = (void *)TYPE_MMS152, +- }, { +- .compatible = "melfas,mms345l", +- .data = (void *)TYPE_MMS345L, +- }, ++ { .compatible = "melfas,mms114", .data = &mms114_descriptor }, ++ { .compatible = "melfas,mms134s", .data = &mms134s_descriptor }, ++ { .compatible = "melfas,mms136", .data = &mms136_descriptor }, ++ { .compatible = "melfas,mms152", .data = &mms152_descriptor }, ++ { .compatible = "melfas,mms345l", .data = &mms345l_descriptor }, + { } + }; + MODULE_DEVICE_TABLE(of, mms114_dt_match); +@@ -722,4 +753,4 @@ module_i2c_driver(mms114_driver); + /* Module information */ + MODULE_AUTHOR("Joonyoung Shim "); + MODULE_DESCRIPTION("MELFAS mms114 Touchscreen driver"); +-MODULE_LICENSE("GPL v2"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/touchscreen/sur40.c b/drivers/input/touchscreen/sur40.c +--- a/drivers/input/touchscreen/sur40.c ++++ b/drivers/input/touchscreen/sur40.c +@@ -128,8 +128,8 @@ struct sur40_image_header { + /* polling interval (ms) */ + #define POLL_INTERVAL 1 + +-/* maximum number of contacts FIXME: this is a guess? */ +-#define MAX_CONTACTS 64 ++/* maximum number of contacts */ ++#define MAX_CONTACTS 52 + + /* control commands */ + #define SUR40_GET_VERSION 0xb0 /* 12 bytes string */ +diff --git a/drivers/input/touchscreen/wacom_w8001.c b/drivers/input/touchscreen/wacom_w8001.c +--- a/drivers/input/touchscreen/wacom_w8001.c ++++ b/drivers/input/touchscreen/wacom_w8001.c +@@ -417,6 +417,13 @@ static int w8001_detect(struct w8001 *w8001) + return 0; + } + ++static void w8001_append_suffix(char *dest, const char *suffix, size_t dest_sz) ++{ ++ size_t used = strnlen(dest, dest_sz); ++ ++ strscpy(dest + used, suffix, dest_sz - used); ++} ++ + static int w8001_setup_pen(struct w8001 *w8001, char *basename, + size_t basename_sz) + { +@@ -453,7 +460,7 @@ static int w8001_setup_pen(struct w8001 *w8001, char *basename, + } + + w8001->id = 0x90; +- strlcat(basename, " Penabled", basename_sz); ++ w8001_append_suffix(basename, " Penabled", basename_sz); + + return 0; + } +@@ -503,14 +510,14 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + case 2: + w8001->pktlen = W8001_PKTLEN_TOUCH93; + w8001->id = 0x93; +- strlcat(basename, " 1FG", basename_sz); ++ w8001_append_suffix(basename, " 1FG", basename_sz); + break; + + case 1: + case 3: + case 4: + w8001->pktlen = W8001_PKTLEN_TOUCH9A; +- strlcat(basename, " 1FG", basename_sz); ++ w8001_append_suffix(basename, " 1FG", basename_sz); + w8001->id = 0x9a; + break; + +@@ -534,7 +541,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + input_abs_set_res(dev, ABS_MT_POSITION_X, touch.panel_res); + input_abs_set_res(dev, ABS_MT_POSITION_Y, touch.panel_res); + +- strlcat(basename, " 2FG", basename_sz); ++ w8001_append_suffix(basename, " 2FG", basename_sz); + if (w8001->max_pen_x && w8001->max_pen_y) + w8001->id = 0xE3; + else +@@ -542,7 +549,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + break; + } + +- strlcat(basename, " Touchscreen", basename_sz); ++ w8001_append_suffix(basename, " Touchscreen", basename_sz); + + return 0; + } +diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c +--- a/drivers/mfd/rohm-bd71828.c ++++ b/drivers/mfd/rohm-bd71828.c +@@ -5,7 +5,8 @@ + * ROHM BD718[15/28/79] and BD72720 PMIC driver + */ + +-#include ++#include ++#include + #include + #include + #include +@@ -18,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -37,19 +39,6 @@ + }, \ + } + +-static struct gpio_keys_button button = { +- .code = KEY_POWER, +- .gpio = -1, +- .type = EV_KEY, +- .wakeup = 1, +-}; +- +-static const struct gpio_keys_platform_data bd71828_powerkey_data = { +- .buttons = &button, +- .nbuttons = 1, +- .name = "bd71828-pwrkey", +-}; +- + static const struct resource bd71815_rtc_irqs[] = { + DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC0, "bd70528-rtc-alm-0"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC1, "bd70528-rtc-alm-1"), +@@ -174,10 +163,6 @@ static struct mfd_cell bd71828_mfd_cells[] = { + .name = "bd71828-rtc", + .resources = bd71828_rtc_irqs, + .num_resources = ARRAY_SIZE(bd71828_rtc_irqs), +- }, { +- .name = "gpio-keys", +- .platform_data = &bd71828_powerkey_data, +- .pdata_size = sizeof(bd71828_powerkey_data), + }, + }; + +@@ -242,11 +227,8 @@ static const struct mfd_cell bd72720_mfd_cells[] = { + .name = "bd72720-rtc", + .resources = bd72720_rtc_irqs, + .num_resources = ARRAY_SIZE(bd72720_rtc_irqs), +- }, { +- .name = "gpio-keys", +- .platform_data = &bd71828_powerkey_data, +- .pdata_size = sizeof(bd71828_powerkey_data), + }, ++ /* Power button is registered separately */ + }; + + static const struct regmap_range bd71815_volatile_ranges[] = { +@@ -877,6 +859,84 @@ static int set_clk_mode(struct device *dev, struct regmap *regmap, + OUT32K_MODE_CMOS); + } + ++static const struct property_entry bd71828_powerkey_parent_props[] = { ++ PROPERTY_ENTRY_STRING("label", "bd71828-pwrkey"), ++ { } ++}; ++ ++static const struct property_entry bd71828_powerkey_props[] = { ++ PROPERTY_ENTRY_U32("linux,code", KEY_POWER), ++ PROPERTY_ENTRY_BOOL("wakeup-source"), ++ { } ++}; ++ ++#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ ++#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ ++ ++static int bd71828_i2c_register_swnodes(const struct software_node *nodes) ++{ ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ return software_node_register_node_group(node_group); ++} ++ ++static void bd71828_i2c_unregister_swnodes(void *data) ++{ ++ const struct software_node *nodes = data; ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ software_node_unregister_node_group(node_group); ++} ++ ++static int bd71828_i2c_register_pwrbutton(struct device *dev, int button_irq, ++ struct irq_domain *irq_domain) ++{ ++ const struct resource res[] = { ++ DEFINE_RES_IRQ_NAMED(button_irq, "bd71828-pwrkey"), ++ }; ++ struct mfd_cell gpio_keys_cell = { ++ .name = "gpio-keys", ++ .resources = res, ++ .num_resources = ARRAY_SIZE(res), ++ }; ++ struct software_node *nodes; ++ int ret; ++ ++ nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); ++ if (!nodes) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); ++ if (!nodes[GPIO_KEYS].name) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].properties = bd71828_powerkey_parent_props; ++ ++ nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; ++ nodes[PWRON_KEY].properties = bd71828_powerkey_props; ++ ++ ret = bd71828_i2c_register_swnodes(nodes); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, bd71828_i2c_unregister_swnodes, nodes); ++ if (ret) ++ return ret; ++ ++ gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, ++ NULL, 0, irq_domain); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register power-button"); ++ ++ return 0; ++} ++ + static struct i2c_client *bd71828_dev; + static void bd71828_power_off(void) + { +@@ -929,6 +989,7 @@ static struct regmap *bd72720_do_regmaps(struct i2c_client *i2c) + static int bd71828_i2c_probe(struct i2c_client *i2c) + { + struct regmap_irq_chip_data *irq_data; ++ struct irq_domain *irq_domain; + int ret; + struct regmap *regmap = NULL; + const struct regmap_config *regmap_config; +@@ -1022,23 +1083,23 @@ static int bd71828_i2c_probe(struct i2c_client *i2c) + "Failed to enable main level IRQs\n"); + } + } +- if (button_irq) { +- ret = regmap_irq_get_virq(irq_data, button_irq); +- if (ret < 0) +- return dev_err_probe(&i2c->dev, ret, +- "Failed to get the power-key IRQ\n"); +- +- button.irq = ret; +- } + + ret = set_clk_mode(&i2c->dev, regmap, clkmode_reg); + if (ret) + return ret; + ++ irq_domain = regmap_irq_get_domain(irq_data); ++ + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, mfd, cells, +- NULL, 0, regmap_irq_get_domain(irq_data)); ++ NULL, 0, irq_domain); + if (ret) +- return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ ++ if (button_irq) { ++ ret = bd71828_i2c_register_pwrbutton(&i2c->dev, button_irq, irq_domain); ++ if (ret) ++ return ret; ++ } + + if (of_device_is_system_power_controller(i2c->dev.of_node) && + chip_type == ROHM_CHIP_TYPE_BD71828) { +diff --git a/drivers/mfd/rohm-bd718x7.c b/drivers/mfd/rohm-bd718x7.c +--- a/drivers/mfd/rohm-bd718x7.c ++++ b/drivers/mfd/rohm-bd718x7.c +@@ -7,7 +7,8 @@ + // Datasheet for BD71837MWV available from + // https://www.rohm.com/datasheet/BD71837MWV/bd71837mwv-e + +-#include ++#include ++#include + #include + #include + #include +@@ -15,37 +16,16 @@ + #include + #include + #include ++#include + #include + #include + +-static struct gpio_keys_button button = { +- .code = KEY_POWER, +- .gpio = -1, +- .type = EV_KEY, +-}; +- +-static struct gpio_keys_platform_data bd718xx_powerkey_data = { +- .buttons = &button, +- .nbuttons = 1, +- .name = "bd718xx-pwrkey", +-}; +- + static struct mfd_cell bd71837_mfd_cells[] = { +- { +- .name = "gpio-keys", +- .platform_data = &bd718xx_powerkey_data, +- .pdata_size = sizeof(bd718xx_powerkey_data), +- }, + { .name = "bd71837-clk", }, + { .name = "bd71837-pmic", }, + }; + + static struct mfd_cell bd71847_mfd_cells[] = { +- { +- .name = "gpio-keys", +- .platform_data = &bd718xx_powerkey_data, +- .pdata_size = sizeof(bd718xx_powerkey_data), +- }, + { .name = "bd71847-clk", }, + { .name = "bd71847-pmic", }, + }; +@@ -125,10 +105,89 @@ static int bd718xx_init_press_duration(struct regmap *regmap, + return 0; + } + ++static const struct property_entry bd718xx_powerkey_parent_props[] = { ++ PROPERTY_ENTRY_STRING("label", "bd718xx-pwrkey"), ++ { } ++}; ++ ++static const struct property_entry bd718xx_powerkey_props[] = { ++ PROPERTY_ENTRY_U32("linux,code", KEY_POWER), ++ { } ++}; ++ ++static const struct resource bd718xx_powerkey_resources[] = { ++ DEFINE_RES_IRQ_NAMED(BD718XX_INT_PWRBTN_S, "bd718xx-pwrkey"), ++}; ++ ++#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ ++#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ ++ ++static int bd718xx_i2c_register_swnodes(const struct software_node *nodes) ++{ ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ return software_node_register_node_group(node_group); ++} ++ ++static void bd718xx_i2c_unregister_swnodes(void *data) ++{ ++ const struct software_node *nodes = data; ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ software_node_unregister_node_group(node_group); ++} ++ ++static int bd718xx_i2c_register_pwrbutton(struct device *dev, ++ struct irq_domain *irq_domain) ++{ ++ struct mfd_cell gpio_keys_cell = { ++ .name = "gpio-keys", ++ .resources = bd718xx_powerkey_resources, ++ .num_resources = ARRAY_SIZE(bd718xx_powerkey_resources), ++ }; ++ struct software_node *nodes; ++ int ret; ++ ++ nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); ++ if (!nodes) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); ++ if (!nodes[GPIO_KEYS].name) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].properties = bd718xx_powerkey_parent_props; ++ ++ nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; ++ nodes[PWRON_KEY].properties = bd718xx_powerkey_props; ++ ++ ret = bd718xx_i2c_register_swnodes(nodes); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, bd718xx_i2c_unregister_swnodes, nodes); ++ if (ret) ++ return ret; ++ ++ gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, ++ NULL, 0, irq_domain); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register power-button"); ++ ++ return 0; ++} ++ + static int bd718xx_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; + struct regmap_irq_chip_data *irq_data; ++ struct irq_domain *irq_domain; + int ret; + unsigned int chip_type; + struct mfd_cell *mfd; +@@ -169,20 +228,18 @@ static int bd718xx_i2c_probe(struct i2c_client *i2c) + if (ret) + return ret; + +- ret = regmap_irq_get_virq(irq_data, BD718XX_INT_PWRBTN_S); +- +- if (ret < 0) +- return dev_err_probe(&i2c->dev, ret, "Failed to get the IRQ\n"); +- +- button.irq = ret; ++ irq_domain = regmap_irq_get_domain(irq_data); + + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, +- mfd, cells, NULL, 0, +- regmap_irq_get_domain(irq_data)); ++ mfd, cells, NULL, 0, irq_domain); + if (ret) +- dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); + +- return ret; ++ ret = bd718xx_i2c_register_pwrbutton(&i2c->dev, irq_domain); ++ if (ret) ++ return ret; ++ ++ return 0; + } + + static const struct of_device_id bd718xx_of_match[] = { +diff --git a/include/linux/input.h b/include/linux/input.h +--- a/include/linux/input.h ++++ b/include/linux/input.h +@@ -117,7 +117,8 @@ enum input_clock_type { + * user opens device and dev->close() is called when the very + * last user closes the device + * @going_away: marks devices that are in a middle of unregistering and +- * causes input_open_device*() fail with -ENODEV. ++ * causes input_open_device() and input_inhibit/uninhibit_device() ++ * to fail with -ENODEV. + * @dev: driver model's view of this device + * @h_list: list of input handles associated with the device. When + * accessing the list dev->mutex must be held +@@ -128,11 +129,14 @@ enum input_clock_type { + * @devres_managed: indicates that devices is managed with devres framework + * and needs not be explicitly unregistered or freed. + * @timestamp: storage for a timestamp set by input_set_timestamp called +- * by a driver ++ * by a driver + * @inhibited: indicates that the input device is inhibited. If that is +- * the case then input core ignores any events generated by the device. +- * Device's close() is called when it is being inhibited and its open() +- * is called when it is being uninhibited. ++ * the case then input core ignores any events generated by the device. ++ * Device's close() is called when it is being inhibited and its open() ++ * is called when it is being uninhibited. ++ * @ready: indicates that the device has been successfully opened and is ++ * prepared to process events (like LEDs or sounds) sent from the ++ * input core. + */ + struct input_dev { + const char *name; +@@ -209,6 +213,7 @@ struct input_dev { + ktime_t timestamp[INPUT_CLK_MAX]; + + bool inhibited; ++ bool ready; + }; + #define to_input_dev(d) container_of(d, struct input_dev, dev) + +@@ -284,8 +289,9 @@ struct input_handle; + * @connect: called when attaching a handler to an input device + * @disconnect: disconnects a handler from input device + * @start: starts handler for given handle. This function is called by +- * input core right after connect() method and also when a process +- * that "grabbed" a device releases it ++ * input core when device is open and ready to process events, ++ * and also when device is uninhibited or when a process that "grabbed" ++ * a device releases it + * @passive_observer: set to %true by drivers only interested in observing + * data stream from devices if there are other users present. Such + * drivers will not result in starting underlying hardware device +diff --git a/include/linux/input/elan-i2c-ids.h b/include/linux/input/elan-i2c-ids.h +--- a/include/linux/input/elan-i2c-ids.h ++++ b/include/linux/input/elan-i2c-ids.h +@@ -18,7 +18,7 @@ + #ifndef __ELAN_I2C_IDS_H + #define __ELAN_I2C_IDS_H + +-#include ++#include + + static const struct acpi_device_id elan_acpi_id[] = { + { "ELAN0000", 0 }, diff --git a/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..9e9a2210663e9e44651668c993798a7f326966bc GIT binary patch literal 594 zcmV-Y0s=j00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=fuko|6E5_p=nWp3DlY7mNSZ| ze*2Czh!W~K!QHLX-Ir$+tR0F$(KvmV+Gj2~>wfHB$(oVI28K)(+_SL= zIF^fez|&axb*S1?j*m&AmFu{=pa~XsQcfXu8upnMG|b$BPSwJO%nCSd;|@g&EBkiM zvA93SN2t$Z_xAhZUL!s}eaYeno8G}D$B#4SQ-$3!aw}U?;GahYd%xSE^MoN`l-N&e zoGJeXSgm1XPkpc`H^$UPEiqxA0@TbJqmOwir)diD>C}=6ka!BR+nYB&g_?(Xdp(Z# zh)73Mn_9?0O!~P+>>U>qX^u(Mj7KqaJKr& zNL+4CyO+}g99a4SYMx>7Bwvu@=ko_UULk#vo03X=)F6Bm^U3DnJ5j@N1SvqEXejTT z-z}h(f6ti82FC(cA76NIXhl;;s<0`nGj(-qyt7>labH9`6J#vfqev>r^>x<&u4~Q@ zeB2C?@|qkz-T@#?*0s>&iQtuv{vSJh1v;Q|nq@aUG_xcWkKWqO?}XQ<7&_`l4b%e4 garpZwoD4wRnF@>{V1@`ExoKTaZn;JN50)a`bomAr3jhEB literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch new file mode 100644 index 0000000..b067aaa --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c +--- a/drivers/input/touchscreen/tsc2007_core.c ++++ b/drivers/input/touchscreen/tsc2007_core.c +@@ -221,7 +221,6 @@ static int tsc2007_get_pendown_state_gpio(struct device *dev) + static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) + { + u32 val32; +- u64 val64; + + if (!device_property_read_u32(dev, "ti,max-rt", &val32)) + ts->max_rt = val32; +@@ -237,8 +236,8 @@ static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) + if (!device_property_read_u32(dev, "ti,fuzzz", &val32)) + ts->fuzzz = val32; + +- if (!device_property_read_u64(dev, "ti,poll-period", &val64)) +- ts->poll_period = msecs_to_jiffies(val64); ++ if (!device_property_read_u32(dev, "ti,poll-period", &val32)) ++ ts->poll_period = msecs_to_jiffies(val32); + else + ts->poll_period = msecs_to_jiffies(1); + diff --git a/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..27751f1443b9bdb591f9dd079ff4d18cbd62df80 GIT binary patch literal 594 zcmV-Y0s=j00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=j#s-{TDiZt61Pk4wi=gZ z>x;IMbhrp!kvR4LXS57jU3h=tcF@s(Kc0Pf_gqU8wD{LKG~-u>b?5 zs*1)r%kfQNg&ps0E21aV6kyAPeOMZ_b!vRHuks0kwiuVl=M40Ot!((d6A>~c6rHk~ zt`u=G7ZP#9V{e*pOjA;{gEC|B^gxb<&2#O%I!T2 z2Bj)lJLsdarZBb`o;a*>`;fvp&IB$<)%=;&;BNW8$M|s-i~x8EZ-#gg^4GEbS;aBM zU<_?q_x!ISe4wJncbRlIuLg|3&F4@NZFXbZTUQJm3jM!nk8PBjPsitqzj23Hk$$;Y zQ4D-nSbO5Q&=}ZRP_IM}%lOd0t-r|(k8YA)qt3iNKs@vH0uCr^P`f2(2tBqWIT<{h zJQ!~#P5H|vYbm9@oeO{7#BXufB~6h?$dti=0av2H + #include ++#include + #include "amd_sfh_hid.h" + + #define PCI_DEVICE_ID_AMD_MP2 0x15E4 + #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A + ++/* The BAR 2 size must cover the highest register offset (0x10958) */ ++#define AMD_SFH_MIN_BAR_SIZE SZ_128K ++ + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) + #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) + +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +@@ -451,6 +451,16 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i + if (rc) + return rc; + ++ if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { ++ dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); ++ return -ENODEV; ++ } ++ ++ if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { ++ dev_err(&pdev->dev, "BAR 2 is too small\n"); ++ return -EINVAL; ++ } ++ + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); + if (rc) + return rc; +diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c +--- a/drivers/hid/bpf/hid_bpf_dispatch.c ++++ b/drivers/hid/bpf/hid_bpf_dispatch.c +@@ -359,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) + + static int + __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, +- enum hid_report_type rtype) ++ enum hid_report_type rtype, bool hw_request) + { + struct hid_report_enum *report_enum; + struct hid_report *report; +@@ -388,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, + + report_len = hid_report_len(report); + ++ /* unnumbered reports need to have a report ID reserved in the first byte */ ++ if (hw_request && report_enum->numbered == 0) ++ report_len += 1; ++ + if (*buf__sz > report_len) + *buf__sz = report_len; + +@@ -420,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); + if (ret) + return ret; + +@@ -480,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); + if (ret) + return ret; + +@@ -506,7 +510,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); + if (ret) + return ret; + +diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c +--- a/drivers/hid/hid-alps.c ++++ b/drivers/hid/hid-alps.c +@@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) + return 1; + + case U1_SP_ABSOLUTE_REPORT_ID: ++ if (!hdata->input2) ++ return 0; + sp_x = get_unaligned_le16(data+2); + sp_y = get_unaligned_le16(data+4); + +@@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) + goto exit; + } + +- data->input2 = input2; + input2->phys = input->phys; + input2->name = "DualPoint Stick"; + input2->id.bustype = BUS_I2C; +@@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) + __set_bit(INPUT_PROP_POINTER, input2->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); + +- if (input_register_device(data->input2)) { ++ if (input_register_device(input2)) { + input_free_device(input2); + ret = -ENOENT; + goto exit; + } ++ data->input2 = input2; + } + + exit: +@@ -823,6 +825,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) + return 0; + } + ++static void alps_remove(struct hid_device *hdev) ++{ ++ struct alps_dev *data = hid_get_drvdata(hdev); ++ ++ /* ++ * input2 ("DualPoint Stick") is allocated separately and is not ++ * tracked in hdev->inputs, so the default remove path ++ * (hid_hw_stop -> hidinput_disconnect) does not unregister it. ++ * ++ * Stop the device first so that no URB callback can touch input2 ++ * while it is being unregistered, then drop it explicitly. ++ */ ++ hid_hw_stop(hdev); ++ ++ if (data->input2) ++ input_unregister_device(data->input2); ++} ++ + static const struct hid_device_id alps_id[] = { + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, +@@ -845,6 +865,7 @@ static struct hid_driver alps_driver = { + .input_configured = alps_input_configured, + .resume = pm_ptr(alps_post_resume), + .reset_resume = pm_ptr(alps_post_reset), ++ .remove = alps_remove, + }; + + module_hid_driver(alps_driver); +diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c +--- a/drivers/hid/hid-corsair-void.c ++++ b/drivers/hid/hid-corsair-void.c +@@ -51,20 +51,23 @@ + /* Receiver report information: (ID 102) */ + /* -------------------------------------------------------------------------- */ + /* +- * When queried, the recevier responds with 4 bytes to describe the firmware +- * The first 2 bytes are for the receiver, the second 2 are the headset ++ * When queried, the receiver responds with 5 bytes to describe the firmware ++ * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset + * The headset firmware version will be 0 if no headset is connected + * +- * 0: Recevier firmware major version ++ * 0: Report ID ++ * 102 for the firmware packet ++ * ++ * 1: Receiver firmware major version + * Major version of the receiver's firmware + * +- * 1: Recevier firmware minor version ++ * 2: Receiver firmware minor version + * Minor version of the receiver's firmware + * +- * 2: Headset firmware major version ++ * 3: Headset firmware major version + * Major version of the headset's firmware + * +- * 3: Headset firmware minor version ++ * 4: Headset firmware minor version + * Minor version of the headset's firmware + */ + /* -------------------------------------------------------------------------- */ +diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c +--- a/drivers/hid/hid-elecom.c ++++ b/drivers/hid/hid-elecom.c +@@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, + case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: + case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: +- case USB_DEVICE_ID_ELECOM_M_XT4DRBK: ++ case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: + /* + * Report descriptor format: + * 12: button bit count +@@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, + mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); + break; + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: ++ case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: + /* + * Report descriptor format: + * 22: button bit count +@@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -472,7 +472,8 @@ + #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f + #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc + #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c +-#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd ++#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd ++#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e + #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe + #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff + #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d +@@ -759,6 +760,7 @@ + #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a + #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 + #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 ++#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db + #define USB_DEVICE_ID_ITE8595 0x8595 + #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 + +@@ -1283,6 +1285,9 @@ + #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 + #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 + ++#define USB_VENDOR_ID_SDINNOVATION 0x36ae ++#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab ++ + #define USB_VENDOR_ID_SEMICO 0x1a2c + #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 + #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 +diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c +--- a/drivers/hid/hid-logitech-hidpp.c ++++ b/drivers/hid/hid-logitech-hidpp.c +@@ -4656,6 +4656,8 @@ static const struct hid_device_id hidpp_devices[] = { + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) }, + { /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, ++ { /* Logitech G502 X Lightspeed Wireless Gaming Mouse over USB */ ++ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, + { /* Logitech G703 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, + { /* Logitech G703 Hero Gaming Mouse over USB */ +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_YOGABOOK9I BIT(24) + #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) ++#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -235,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_APPLE_TOUCHBAR 0x0114 + #define MT_CLS_YOGABOOK9I 0x0115 + #define MT_CLS_EGALAX_P80H84 0x0116 ++#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -405,6 +407,16 @@ static const struct mt_class mt_classes[] = { + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_ASUS_CUSTOM_UP }, ++ { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, ++ .quirks = MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS | ++ MT_QUIRK_CONFIDENCE | ++ MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, ++ .export_all_inputs = true }, + { .name = MT_CLS_VTL, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | +@@ -507,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { + + static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) + { ++ struct mt_device *td = hid_get_drvdata(hdev); + int ret; + u32 size = hid_report_len(report); + u8 *buf; +@@ -528,8 +541,14 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) + dev_warn(&hdev->dev, "failed to fetch feature %d\n", + report->id); + } else { +- /* The report ID in the request and the response should match */ +- if (report->id != buf[0]) { ++ /* ++ * The report ID in the request and the response should match. ++ * Some firmware (e.g. the ASUS ROG Z13 Folio ++ * touchpad) returns a mismatched ID on this specific fetch; ++ * tolerate it only for devices explicitly flagged as such. ++ */ ++ if (report->id != buf[0] && ++ !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { + hid_err(hdev, "Returned feature report did not match the request\n"); + goto free; + } +@@ -2726,6 +2745,12 @@ static const struct hid_device_id mt_devices[] = { + HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, + I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, + ++ /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ ++ { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, ++ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, ++ USB_VENDOR_ID_ASUSTEK, ++ USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, ++ + /* Generic MT device */ + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, + +diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c +--- a/drivers/hid/hid-oxp.c ++++ b/drivers/hid/hid-oxp.c +@@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev, + + static void oxp_hid_remove(struct hid_device *hdev) + { +- cancel_delayed_work(&drvdata.oxp_rgb_queue); +- cancel_delayed_work(&drvdata.oxp_btn_queue); +- cancel_delayed_work(&drvdata.oxp_mcu_init); ++ cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); ++ cancel_delayed_work_sync(&drvdata.oxp_btn_queue); ++ cancel_delayed_work_sync(&drvdata.oxp_mcu_init); + hid_hw_close(hdev); + hid_hw_stop(hdev); + } +diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c +--- a/drivers/hid/hid-quirks.c ++++ b/drivers/hid/hid-quirks.c +@@ -183,6 +183,7 @@ static const struct hid_device_id hid_quirks[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, + { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, +@@ -422,13 +423,14 @@ static const struct hid_device_id hid_have_special_driver[] = { + #endif + #if IS_ENABLED(CONFIG_HID_ELECOM) + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, +- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, +diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c +--- a/drivers/hid/hid-roccat.c ++++ b/drivers/hid/hid-roccat.c +@@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report + return temp; + } + +- mutex_unlock(&devices_lock); +- + init_waitqueue_head(&device->wait); + INIT_LIST_HEAD(&device->readers); + mutex_init(&device->readers_lock); +@@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report + device->cbuf_end = 0; + device->report_size = report_size; + ++ mutex_unlock(&devices_lock); + return minor; + } + EXPORT_SYMBOL_GPL(roccat_connect); +@@ -369,15 +368,12 @@ void roccat_disconnect(int minor) + + mutex_lock(&devices_lock); + device = devices[minor]; +- mutex_unlock(&devices_lock); + + device->exist = 0; /* TODO exist maybe not needed */ + + device_destroy(device->dev->class, MKDEV(roccat_major, minor)); + +- mutex_lock(&devices_lock); + devices[minor] = NULL; +- mutex_unlock(&devices_lock); + + if (device->open) { + hid_hw_close(device->hid); +@@ -385,6 +381,8 @@ void roccat_disconnect(int minor) + } else { + roccat_free_device(device); + } ++ ++ mutex_unlock(&devices_lock); + } + EXPORT_SYMBOL_GPL(roccat_disconnect); + +diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c +--- a/drivers/hid/hid-winwing.c ++++ b/drivers/hid/hid-winwing.c +@@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) + static int winwing_play_effect(struct input_dev *dev, void *context, + struct ff_effect *effect) + { +- struct winwing_drv_data *data = (struct winwing_drv_data *) context; ++ struct hid_device *hdev = input_get_drvdata(dev); ++ struct winwing_drv_data *data = hid_get_drvdata(hdev); + + if (effect->type != FF_RUMBLE) + return 0; +@@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) + + input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); + +- return input_ff_create_memless(hidinput->input, data, ++ /* ++ * input_ff_create_memless() takes ownership of the context pointer ++ * and frees it on teardown; do not hand it the devm-managed drvdata. ++ * winwing_play_effect() fetches it from the input device instead. ++ */ ++ return input_ff_create_memless(hidinput->input, NULL, + winwing_play_effect); + } + +diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c +--- a/drivers/hid/i2c-hid/i2c-hid-core.c ++++ b/drivers/hid/i2c-hid/i2c-hid-core.c +@@ -123,9 +123,11 @@ static const struct i2c_hid_quirks { + __u32 quirks; + } i2c_hid_quirks[] = { + { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, +- I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, ++ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, + { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, ++ { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, ++ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, +diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c +--- a/drivers/hid/wacom_sys.c ++++ b/drivers/hid/wacom_sys.c +@@ -113,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, + + /* Queue events which have invalid tool type or serial number */ + for (i = 0; i < report->maxfield; i++) { +- for (j = 0; j < report->field[i]->maxusage; j++) { +- struct hid_field *field = report->field[i]; ++ struct hid_field *field = report->field[i]; ++ ++ for (j = 0; j < field->report_count; j++) { + struct hid_usage *usage = &field->usage[j]; + unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); + unsigned int offset; diff --git a/pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..03c4b82652fddad8cb7c20bce5a3b0a57185fca4 GIT binary patch literal 594 zcmV-Y0#r0Chb=mB1aO7Wi3s{{%2pw|d$DZGo?k`CTM?%ZPYOX?`UqgedVHoJ!+YmKS6n$_ z9aLn6Xuf_2W>$lPkky;~r7GM>AVynx0SnUGKARk4v;LCFP|6xZNW-HdQV~v=g^D@TmS$7 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch new file mode 100644 index 0000000..1682da2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch @@ -0,0 +1,88 @@ +diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c +--- a/drivers/input/keyboard/atkbd.c ++++ b/drivers/input/keyboard/atkbd.c +@@ -1938,6 +1938,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { + }, + .callback = atkbd_deactivate_fixup, + }, ++ { ++ /* Lenovo IdeaPad Slim 3 15IWC11 (83RR) */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83RR"), ++ }, ++ .callback = atkbd_deactivate_fixup, ++ }, + { + /* Xiaomi Book Pro 14 (TM2424) */ + .matches = { +diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c +--- a/drivers/input/mouse/synaptics.c ++++ b/drivers/input/mouse/synaptics.c +@@ -192,6 +192,7 @@ static const char * const smbus_pnp_ids[] = { + "LEN2054", /* E480 */ + "LEN2055", /* E580 */ + "LEN2058", /* E490 */ ++ "LEN205b", /* T490 */ + "LEN2068", /* T14 Gen 1 */ + "SYN1221", /* TUXEDO InfinityBook Pro 14 v5 */ + "SYN3003", /* HP EliteBook 850 G1 */ +@@ -1839,8 +1840,10 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, + return -ENXIO; + } + +- /* Disable intertouch on known-broken board revisions */ +- if (info->board_id == 2722) { ++ /* Disable intertouch on known-broken T440p board revisions */ ++ if (info->board_id == 2722 && ++ psmouse_matches_pnp_id(psmouse, ++ (const char * const []){"LEN0036", NULL})) { + psmouse_info(psmouse, + "Disabling intertouch for board id %u\n", + info->board_id); +diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h +--- a/drivers/input/serio/i8042-acpipnpio.h ++++ b/drivers/input/serio/i8042-acpipnpio.h +@@ -671,6 +671,15 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { + }, + .driver_data = (void *)(SERIO_QUIRK_NOAUX) + }, ++ { ++ /* Fujitsu LIFEBOOK U7410 */ ++ /* Mux mode also disturbs the EC keyboard, causing spurious ACKs */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU CLIENT COMPUTING LIMITED"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U7410"), ++ }, ++ .driver_data = (void *)(SERIO_QUIRK_NOMUX) ++ }, + { + /* Gigabyte M912 */ + .matches = { +diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c +--- a/drivers/input/touchscreen/s6sy761.c ++++ b/drivers/input/touchscreen/s6sy761.c +@@ -500,10 +500,22 @@ static int s6sy761_suspend(struct device *dev) + static int s6sy761_resume(struct device *dev) + { + struct s6sy761_data *sdata = dev_get_drvdata(dev); ++ int err; ++ ++ err = s6sy761_power_on(sdata); ++ if (err) ++ return err; + + enable_irq(sdata->client->irq); + +- return s6sy761_power_on(sdata); ++ guard(mutex)(&sdata->input->mutex); ++ ++ if (!input_device_enabled(sdata->input)) ++ return 0; ++ ++ err = i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_ON); ++ ++ return err; + } + + static const struct dev_pm_ops s6sy761_pm_ops = { diff --git a/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..50592003460fa89c1af26332c04c22de3ea3b2a3 GIT binary patch literal 594 zcmV-Y0kR+ zLI-QbAA&+byW?KTCSK*Ta8SlSJL`ZN8kE*7KV68bO7ynG0)<5o(c%bejRTTgc4rH7 z@Yuxxgcs>PS8Axjc7bR?UuaMTC|AF22960=a*K8QexLRXqr-2&1^Mqdpnr7Va(njy ze`Tb~rTJ0>>yJ5CE!b3fr!Fk3HWTmtR<>)?MYc;xona>6Xbo+}rg1A92^RAbRPNyt zC||a}z8rRZf8{QsRFX`mjMEZUpf07-Zf8bUY3f(if_V>|IaJVk=^um#7d}Q*7H|O< zPwLHGFu3@D+Mt-~g@P(EQwcjqXoT$E0aAjONtg9G zV_S9i(`MZltapzOTb{Uz29dQu5qo;twR2!W6+vNg2w^KUmEWZ*-9$MBi-IReL=Y)| z!ucOTD?obGfTp3x4lBSzqI&(z^u(gKdeW)^RCU}Hs;pOnQEI; z9=h}a9tQG;c2N5G$m^PT*;j{S6tUzXd%bGi=kp2OH>Y)t@8sJN z1gQgA6MNKO;8h(GbOR|jGpETh?f&_k_3?)i!hPeO%$K2wO(ak*BZ%?%@<;Se0!p{| giLaVET}A#B;Z0oyDnLRh7c!KAc)NUkn|pe6MJs|2P5=M^ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch b/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch new file mode 100644 index 0000000..01748b9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch @@ -0,0 +1,11 @@ +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -373,6 +373,7 @@ static const struct acpi_device_id i2c_acpi_force_100khz_device_ids[] = { + * the device works without issues on Windows at what is expected to be + * a 400KHz frequency. The root cause of the issue is not known. + */ ++ { "ASUE140D", 0 }, + { "DLL0945", 0 }, + { "ELAN0678", 0 }, + { "ELAN06FA", 0 }, diff --git a/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch.sig b/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..d5acafd04e22cc6ad6e754e55ed0edebad70e883 GIT binary patch literal 594 zcmV-Y0-M-HLBBlIa$lF(LuG4Q6YN)MtZw=hT6&{PvF4l`D7U|GadO@UAxiiyTHw(${;*QMkT3%Y=FYl)W zWRgJl9-$ADjbA;J5H)*rOmE4~_jeWg%P!Bl!U3P`6Z2S%*P%vz#!bUw9;hi{-;YU` z?czu4(5nqx0!mA7@ut*x?brlTTPwt$jqlraKlM48^qpi5B^#XLjL*<#?P1lV!848q!K*hcZ|t1pcB{pRdz+a28;q1 gke>XD=iXPr+14l})TU;DhiOwBpey+%FLsg6@j@aO@&Et; literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch b/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch new file mode 100644 index 0000000..d7870a2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c +--- a/drivers/hid/hid-asus.c ++++ b/drivers/hid/hid-asus.c +@@ -1480,12 +1480,14 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) + return ret; + } + +- for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { +- if (asus_has_report_id(hdev, asus_report_id_init[r])) { +- ret = asus_kbd_init(hdev, asus_report_id_init[r]); +- if (ret < 0) +- hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", +- asus_report_id_init[r], ret); ++ if (!drvdata->tp) { ++ for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { ++ if (asus_has_report_id(hdev, asus_report_id_init[r])) { ++ ret = asus_kbd_init(hdev, asus_report_id_init[r]); ++ if (ret < 0) ++ hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", ++ asus_report_id_init[r], ret); ++ } + } + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig b/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..0d7e5e44a14b9052a817ec400f2795d6a2b529ee GIT binary patch literal 594 zcmV-Y0ZXHo9+a zj?AP}eVB-j#&Nj20H*dYyLu5?IJiYxaQy@BTIF)0oJSBfOjm4?B(N@z*t1^x*ap$N zNJ9)2ehx8JXDWLo(+xG~5Qp+Y!a^R?aHpdHIG^?!l-v021ZRL(J(@DgRAMY&o{u3~ z*hK{i=a1~|`8uc{i4%C|`RxS;E-`X`o5%(%^huP9dDe2*N@Ow1G8@e9vRHYz0T8cn zRwHTSE3?l`6j`j)5%K93)nf&rM_4Uy zSOJyqW-KQ@X@|hpDVXI`Cq67S2O~m8O zc{+M=6Tna%@_OQ2*37Ds8Z-f$e^aWm89heqp2QzLT48K_Ak^mUklbZv@5|ow#smrE z*I?Yp{=6i7iG&@<&etWzIUQG*WDec4u%sl|2=M8~F?PfTbnH1`6_RA`Xm$ezXl(EF gjY$bpzD&zQRF}54^%*pRBPb9P3;+NC literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch b/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch new file mode 100644 index 0000000..7a0d9ca --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch @@ -0,0 +1,1131 @@ +diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c +--- a/drivers/net/thunderbolt/main.c ++++ b/drivers/net/thunderbolt/main.c +@@ -1362,7 +1362,7 @@ static void tbnet_generate_mac(struct net_device *dev) + dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + } + +-static int tbnet_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int tbnet_probe(struct tb_service *svc) + { + struct tb_xdomain *xd = tb_service_parent(svc); + struct net_device *dev; +@@ -1482,7 +1482,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tbnet_pm_ops, tbnet_suspend, tbnet_resume); + + static const struct tb_service_id tbnet_ids[] = { + { TB_SERVICE("network", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, tbnet_ids); + +diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c +--- a/drivers/thunderbolt/debugfs.c ++++ b/drivers/thunderbolt/debugfs.c +@@ -136,13 +136,13 @@ static void *validate_and_copy_from_user(const void __user *user_buf, + if (!access_ok(user_buf, *count)) + return ERR_PTR(-EFAULT); + +- buf = (void *)get_zeroed_page(GFP_KERNEL); ++ buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + nbytes = min_t(size_t, *count, PAGE_SIZE); + if (copy_from_user(buf, user_buf, nbytes)) { +- free_page((unsigned long)buf); ++ kfree(buf); + return ERR_PTR(-EFAULT); + } + +@@ -265,7 +265,7 @@ static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -406,7 +406,7 @@ static ssize_t port_sb_regs_write(struct file *file, const char __user *user_buf + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -439,7 +439,7 @@ static ssize_t retimer_sb_regs_write(struct file *file, + out: + pm_runtime_mark_last_busy(&rt->dev); + pm_runtime_put_autosuspend(&rt->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -652,7 +652,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf, + margining->ber_level = val; + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + out_unlock: + mutex_unlock(&tb->lock); + +@@ -829,7 +829,7 @@ margining_lanes_write(struct file *file, const char __user *user_buf, + } + } + +- free_page((unsigned long)buf); ++ kfree(buf); + + if (lane == -1) + return -EINVAL; +@@ -958,7 +958,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, + else + goto err_free; + +- free_page((unsigned long)buf); ++ kfree(buf); + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) { + if (!margining->software) +@@ -970,7 +970,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, + return count; + + err_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return -EINVAL; + } + +@@ -1116,7 +1116,7 @@ static ssize_t margining_mode_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1503,7 +1503,7 @@ static ssize_t margining_test_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1569,7 +1569,7 @@ static ssize_t margining_margin_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1624,7 +1624,7 @@ static ssize_t margining_eye_write(struct file *file, + ret = -EINVAL; + } + +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1934,7 +1934,7 @@ static ssize_t counters_write(struct file *file, const char __user *user_buf, + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c +--- a/drivers/thunderbolt/dma_test.c ++++ b/drivers/thunderbolt/dma_test.c +@@ -636,7 +636,7 @@ static void dma_test_debugfs_init(struct tb_service *svc) + debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops); + } + +-static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int dma_test_probe(struct tb_service *svc) + { + struct tb_xdomain *xd = tb_service_parent(svc); + struct dma_test *dt; +@@ -689,7 +689,7 @@ static const struct dev_pm_ops dma_test_pm_ops = { + + static const struct tb_service_id dma_test_ids[] = { + { TB_SERVICE("dma_test", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, dma_test_ids); + +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -77,12 +77,10 @@ static int tb_service_probe(struct device *dev) + { + struct tb_service *svc = tb_to_service(dev); + struct tb_service_driver *driver; +- const struct tb_service_id *id; + + driver = container_of(dev->driver, struct tb_service_driver, driver); +- id = __tb_service_match(dev, &driver->driver); + +- return driver->probe(svc, id); ++ return driver->probe(svc); + } + + static void tb_service_remove(struct device *dev) +@@ -790,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + transmit_ring, receive_path, receive_ring); + } + ++static void tb_domain_reset_interface(struct tb *tb) ++{ ++ struct tb_nhi *nhi = tb->nhi; ++ ++ if (!nhi->ops->reset_interface) ++ return; ++ ++ guard(mutex)(&tb->lock); ++ ++ /* The reset clears the ring state so stop the control channel */ ++ tb_ctl_stop(tb->ctl); ++ nhi->ops->reset_interface(nhi); ++ tb_ctl_start(tb->ctl); ++} ++ + /** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths +@@ -812,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + int transmit_path, int transmit_ring, + int receive_path, int receive_ring) + { ++ int ret; ++ + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + +- return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, ++ ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + transmit_ring, receive_path, receive_ring); ++ if (ret) ++ return ret; ++ ++ if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) ++ tb_domain_reset_interface(tb); ++ ++ return 0; + } + + static int disconnect_xdomain(struct device *dev, void *data) +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring) + { + struct ring_frame *frame, *n; + struct ring_desc *descriptor; ++ u32 flags; ++ ++ flags = RING_DESC_POSTED; ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ flags |= RING_DESC_INTERRUPT; ++ + list_for_each_entry_safe(frame, n, &ring->queue, list) { + if (ring_full(ring)) + break; +@@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring) + descriptor = &ring->descriptors[ring->head]; + descriptor->phys = frame->buffer_phy; + descriptor->time = 0; +- descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; ++ descriptor->flags = flags; + if (ring->is_tx) { + descriptor->length = frame->size; + descriptor->eof = frame->eof; +@@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); + * @ring: Ring to poll + * + * This function can be called when @start_poll callback of the @ring +- * has been called. It will read one completed frame from the ring and +- * return it to the caller. ++ * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT. ++ * It will read one completed frame from the ring and return it to the ++ * caller. + * + * Return: Pointer to &struct ring_frame, %NULL if there is no more + * completed frames. +@@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", + transmit ? "TX" : "RX", hop, size); + ++ if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) { ++ dev_WARN(nhi->dev, ++ "start_poll() and NO_INTERRUPT cannot be used at the same time\n"); ++ return NULL; ++ } ++ + ring = kzalloc_obj(*ring); + if (!ring) + return NULL; +@@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + if (!ring->descriptors) + goto err_free_ring; + +- if (nhi->ops->request_ring_irq) { ++ if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_descs; + } +@@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring) + ring_iowrite32options(ring, flags, 0); + } + +- ring_interrupt_active(ring, true); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, true); + ring->running = true; + err: + spin_unlock(&ring->lock); +@@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring) + RING_TYPE(ring), ring->hop); + goto err; + } +- ring_interrupt_active(ring, false); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, false); + + ring_iowrite32options(ring, 0, 0); + ring_iowrite64desc(ring, 0, 0); +@@ -1160,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi) + dev_warn(nhi->dev, "timeout resetting host router\n"); + } + ++/** ++ * nhi_reset_interface() - Reset the host interface ++ * @nhi: Host interface to reset ++ * ++ * Brings the registers in the memory BAR back to their default state and ++ * clears the End-to-End Flow Control state. The caller is responsible for ++ * stopping the control channel over the reset because it clears the ring ++ * state as well. ++ */ ++void nhi_reset_interface(struct tb_nhi *nhi) ++{ ++ u32 val; ++ ++ val = ioread32(nhi->iobase + REG_CAPS); ++ /* Only v1 host interfaces implement the reset */ ++ if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) ++ return; ++ ++ dev_dbg(nhi->dev, "issuing host interface reset\n"); ++ ++ iowrite32(REG_HOST_INTERFACE_RESET_RST, ++ nhi->iobase + REG_HOST_INTERFACE_RESET); ++ /* Wait for tHIReset (10 ms) to complete */ ++ usleep_range(10000, 20000); ++} ++ + static struct tb *nhi_select_cm(struct tb_nhi *nhi) + { + struct tb *tb; +@@ -1235,6 +1276,8 @@ int nhi_probe(struct tb_nhi *nhi) + + dev_dbg(dev, "NHI initialized, starting thunderbolt\n"); + ++ nhi->host_reset = host_reset; ++ + res = tb_domain_add(tb, host_reset); + if (res) { + /* +diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h +--- a/drivers/thunderbolt/nhi.h ++++ b/drivers/thunderbolt/nhi.h +@@ -36,6 +36,8 @@ irqreturn_t nhi_msi(int irq, void *data); + irqreturn_t ring_msix(int irq, void *data); + int nhi_probe(struct tb_nhi *nhi); + void nhi_shutdown(struct tb_nhi *nhi); ++void nhi_reset_interface(struct tb_nhi *nhi); ++ + extern const struct dev_pm_ops nhi_pm_ops; + + /** +@@ -52,6 +54,7 @@ extern const struct dev_pm_ops nhi_pm_ops; + * @release_ring_irq: NHI specific interrupt release hook + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook ++ * @reset_interface: Resets the host interface + */ + struct tb_nhi_ops { + int (*init)(struct tb_nhi *nhi); +@@ -66,6 +69,7 @@ struct tb_nhi_ops { + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); ++ void (*reset_interface)(struct tb_nhi *nhi); + }; + + /* +@@ -116,11 +120,24 @@ struct tb_nhi_ops { + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 + ++#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 ++#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 ++#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b ++#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 ++#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c ++#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d ++#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d ++#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e ++ + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + + /* Host interface quirks */ +-#define QUIRK_AUTO_CLEAR_INT BIT(0) +-#define QUIRK_E2E BIT(1) ++#define QUIRK_AUTO_CLEAR_INT BIT(0) ++#define QUIRK_E2E BIT(1) ++#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) + + /* + * Minimal number of vectors when we use MSI-X. Two for control channel +diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h +--- a/drivers/thunderbolt/nhi_regs.h ++++ b/drivers/thunderbolt/nhi_regs.h +@@ -115,6 +115,10 @@ struct ring_desc { + #define REG_CAPS_VERSION_MASK GENMASK(23, 16) + #define REG_CAPS_VERSION_2 0x40 + ++/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ ++#define REG_HOST_INTERFACE_RESET 0x39858 ++#define REG_HOST_INTERFACE_RESET_RST BIT(0) ++ + #define REG_DMA_MISC 0x39864 + #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) + #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c +--- a/drivers/thunderbolt/pci.c ++++ b/drivers/thunderbolt/pci.c +@@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) + nhi->quirks |= QUIRK_E2E; + break; + } ++ } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { ++ switch (pdev->device) { ++ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: ++ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: ++ /* ++ * These AMD hosts may hang the Tx ring when the ++ * DMA paths are torn down so they need the host ++ * interface reset after each teardown. ++ */ ++ nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; ++ break; ++ } + } + } + +@@ -112,7 +133,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) + { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); +- struct device *dev = &pdev->dev; + int res, irq, nvec; + + ida_init(&nhi_pci->msix_ida); +@@ -139,7 +159,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) + res = devm_request_irq(&pdev->dev, irq, nhi_msi, + IRQF_NO_SUSPEND, "thunderbolt", nhi); + if (res) +- return dev_err_probe(dev, res, "request_irq failed, aborting\n"); ++ return res; + } + + return 0; +@@ -230,7 +250,7 @@ static void nhi_pci_ring_release_msix(struct tb_ring *ring) + ring->irq = 0; + } + +-static void nhi_pci_shutdown(struct tb_nhi *nhi) ++static void nhi_pci_release_irq(struct tb_nhi *nhi) + { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); +@@ -256,9 +276,10 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, +- .shutdown = nhi_pci_shutdown, ++ .shutdown = nhi_pci_release_irq, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, ++ .reset_interface = nhi_reset_interface, + }; + + /* Ice Lake specific NHI operations */ +@@ -424,7 +445,7 @@ static int icl_nhi_resume(struct tb_nhi *nhi) + + static void icl_nhi_shutdown(struct tb_nhi *nhi) + { +- nhi_pci_shutdown(nhi); ++ nhi_pci_release_irq(nhi); + + icl_nhi_force_power(nhi, false); + } +@@ -442,6 +463,7 @@ static const struct tb_nhi_ops icl_nhi_ops = { + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, ++ .reset_interface = nhi_reset_interface, + }; + + static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +@@ -479,11 +501,19 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) + return nhi_probe(&nhi_pci->nhi); + } + +-static void nhi_pci_remove(struct pci_dev *pdev) ++static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset) + { + struct tb *tb = pci_get_drvdata(pdev); + struct tb_nhi *nhi = tb->nhi; + ++ /* ++ * On system shutdown/reboot force a host router reset so the ++ * connection manager asserts DPR on connected Thunderbolt 3 devices ++ * before the router tree is removed (see tb_stop()). ++ */ ++ if (reset) ++ nhi->host_reset = true; ++ + pm_runtime_get_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_forbid(&pdev->dev); +@@ -493,6 +523,16 @@ static void nhi_pci_remove(struct pci_dev *pdev) + nhi_shutdown(nhi); + } + ++static void nhi_pci_remove(struct pci_dev *pdev) ++{ ++ nhi_pci_do_remove(pdev, false); ++} ++ ++static void nhi_pci_shutdown(struct pci_dev *pdev) ++{ ++ nhi_pci_do_remove(pdev, true); ++} ++ + static struct pci_device_id nhi_ids[] = { + /* + * We have to specify class, the TB bridges use the same device and +@@ -593,7 +633,7 @@ static struct pci_driver nhi_driver = { + .id_table = nhi_ids, + .probe = nhi_pci_probe, + .remove = nhi_pci_remove, +- .shutdown = nhi_pci_remove, ++ .shutdown = nhi_pci_shutdown, + .driver.pm = &nhi_pm_ops, + }; + +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -9,10 +9,12 @@ + + #define pr_fmt(fmt) "tbstream: " fmt + ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -128,6 +130,7 @@ struct tbstream_ring { + * @out_hopid: Out HopID + * @ring_size: Size of the rings + * @throttling: Interrupt throttling rate in ns ++ * @busy_poll: Instead of interrupts, busy poll the rings + * @users: Number of times @cdev has been opened + * @closed: CLOSE packet was received + * @removed: Userspace removed the ConfigFS group underneath. +@@ -147,6 +150,7 @@ struct tbstream_dev { + int out_hopid; + unsigned int ring_size; + unsigned int throttling; ++ bool busy_poll; + int users; + bool closed; + bool removed; +@@ -536,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); + } + ++static void ++tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring) ++{ ++ struct ring_frame *frame; ++ ++ if (!sdev->busy_poll) ++ return; ++ ++ while ((frame = tb_ring_poll(ring->ring))) ++ frame->callback(ring->ring, frame, false); ++} ++ + static int tbstream_dev_send_close(struct tbstream_dev *sdev) + { + struct tbstream_frame *sf; + ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling it's the write(2) path that ++ * advances the completions so it is possible that the ++ * ring is full at this point. Advance the ring here so ++ * that there is room for the CLOSE packet to be sent. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ } ++ + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); + if (IS_ERR(sf)) + return PTR_ERR(sf); +@@ -549,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) + static int tbstream_dev_start(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); ++ unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E; + u16 sof_mask, eof_mask; + struct tb_ring *ring; + int ret, e2e_tx_hop; + +- ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E); ++ if (sdev->busy_poll) ++ flags |= RING_FLAG_NO_INTERRUPT; ++ ++ ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); + if (!ring) + return -ENOMEM; + sdev->tx_ring.ring = ring; +@@ -567,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + sof_mask = BIT(TBSTREAM_FRAME_START); + eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); + +- ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, +- sof_mask, eof_mask, NULL, NULL); ++ ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, ++ e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); + if (!ring) { + ret = -ENOMEM; + goto err_free_tx_buffers; +@@ -607,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + return ret; + } + ++static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev) ++{ ++ const struct tbstream_ring *ring = &sdev->tx_ring; ++ ++ /* ++ * Everything is completed when number of free TX slots is back ++ * to the maximum. ++ */ ++ return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1; ++} ++ + static void tbstream_dev_stop(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd; + +- /* Wait for the ring to complete any outstanding frames */ +- tb_ring_flush(sdev->tx_ring.ring, 500); +- tb_ring_stop(sdev->tx_ring.ring); +- tb_ring_flush(sdev->rx_ring.ring, 500); +- tb_ring_stop(sdev->rx_ring.ring); ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling we must advance the ring ourselves ++ * to push all outstanding frames on the wire. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_dev_tx_drained(sdev)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } else { ++ tb_ring_flush(sdev->tx_ring.ring, 500); ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_flush(sdev->rx_ring.ring, 500); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } + + xd = tbstream_dev_xdomain(sdev); + if (xd) { +@@ -633,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) + sdev->tx_ring.ring = NULL; + } + ++/* Use only with read_iter/write_iter() to handle nowait */ ++static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) ++{ ++ if (nowait) { ++ if (!mutex_trylock(&sdev->lock)) ++ return -EAGAIN; ++ } else { ++ if (mutex_lock_interruptible(&sdev->lock)) ++ return -ERESTARTSYS; ++ } ++ return 0; ++} ++ + static ssize_t + tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -645,31 +722,50 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->rx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->rx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ /* When busy polling, advance any completions manually */ ++ tbstream_dev_poll_ring(sdev, &sdev->rx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return 0; ++ } ++ ++ if (tbstream_ring_available(&sdev->rx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->rx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return 0; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -729,6 +825,7 @@ static ssize_t + tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -737,31 +834,49 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->tx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->tx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return -ENXIO; ++ } ++ ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->tx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return -ENXIO; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -795,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + __poll_t mask = 0; + ++ /* ++ * Without interrupts there is nothing that can wake us up so ++ * return failure instead. ++ */ ++ if (sdev->busy_poll) ++ return EPOLLERR; ++ + poll_wait(file, &sdev->wait, wait); + guard(mutex)(&sdev->lock); + if (tbstream_dev_valid(sdev) != 0) { +@@ -904,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group) + return container_of(group, struct tbstream_dev, group); + } + ++static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ ++ return sysfs_emit(buf, "%u\n", sdev->busy_poll); ++} ++ ++static ssize_t ++tbstream_dev_busy_poll_store(struct config_item *item, const char *buf, ++ size_t count) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ bool busy_poll; ++ int ret; ++ ++ ret = kstrtobool(buf, &busy_poll); ++ if (ret) ++ return ret; ++ ++ guard(mutex)(&sdev->lock); ++ if (sdev->users) ++ return -EBUSY; ++ sdev->busy_poll = busy_poll; ++ return count; ++} ++CONFIGFS_ATTR(tbstream_dev_, busy_poll); ++ + static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) + { + struct config_group *group = to_config_group(item); +@@ -1210,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf, + CONFIGFS_ATTR(tbstream_dev_, throttling); + + static struct configfs_attribute *tbstream_dev_attrs[] = { ++ &tbstream_dev_attr_busy_poll, + &tbstream_dev_attr_index, + &tbstream_dev_attr_in_hopid, + &tbstream_dev_attr_out_hopid, +@@ -1542,7 +1694,7 @@ static void tbstream_group_detach_stream(struct tbstream *stream) + config_group_put(&sg->group); + } + +-static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int tbstream_probe(struct tb_service *svc) + { + struct tbstream *stream; + +@@ -1632,7 +1784,7 @@ static const struct dev_pm_ops tbstream_pm_ops = { + + static const struct tb_service_id tbstream_ids[] = { + { TB_SERVICE("stream", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, tbstream_ids); + +diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -682,7 +682,16 @@ int tb_port_disable(struct tb_port *port) + return __tb_port_enable(port, false); + } + +-static int tb_port_reset(struct tb_port *port) ++/** ++ * tb_port_reset() - Reset the port ++ * @port: Port to reset ++ * ++ * Resets @port. For USB4 ports this issues a USB4 port reset and for ++ * legacy ports the link controller port is reset. ++ * ++ * Return: %0 on success, negative errno otherwise. ++ */ ++int tb_port_reset(struct tb_port *port) + { + if (tb_switch_is_usb4(port->sw)) + return port->cap_usb4 ? usb4_port_reset(port) : 0; +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -2941,7 +2941,9 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, + static void tb_stop(struct tb *tb) + { + struct tb_cm *tcm = tb_priv(tb); ++ struct tb_nhi *nhi = tb->nhi; + struct tb_tunnel *tunnel; ++ struct tb_port *port; + struct tb_tunnel *n; + + cancel_delayed_work(&tcm->remove_work); +@@ -2956,6 +2958,25 @@ static void tb_stop(struct tb *tb) + tb_tunnel_deactivate(tunnel); + tb_tunnel_put(tunnel); + } ++ /* ++ * Signal disconnect to connected devices before the router tree is ++ * removed below. A Thunderbolt 3 device directly connected to a USB4 ++ * host otherwise never receives a disconnect indication, leaving ++ * firmware to poll the dead link for up to ~60 s which on some ++ * platforms turns the shutdown into a warm reset. Asserting ++ * PORT_CS_19.DPR drives SBTX low (USB4 spec section 6.9) so the device ++ * detects SBRX low and goes to Uninitialized Unplugged immediately. ++ */ ++ if (nhi->host_reset) { ++ tb_switch_for_each_port(tb->root_switch, port) { ++ if (!tb_port_is_null(port) || !tb_port_has_remote(port)) ++ continue; ++ if (tb_switch_is_usb4(port->remote->sw)) ++ continue; ++ if (tb_port_reset(port)) ++ tb_port_dbg(port, "downstream port reset failed, continuing\n"); ++ } ++ } + tb_switch_remove(tb->root_switch); + tb->root_switch = NULL; + tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ +diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -1103,6 +1103,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter); + int tb_port_unlock(struct tb_port *port); + int tb_port_enable(struct tb_port *port); + int tb_port_disable(struct tb_port *port); ++int tb_port_reset(struct tb_port *port); + int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid); + void tb_port_release_in_hopid(struct tb_port *port, int hopid); + int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid); +diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c +--- a/drivers/thunderbolt/tunnel.c ++++ b/drivers/thunderbolt/tunnel.c +@@ -48,6 +48,9 @@ + #define TB_DP_AUX_PRIORITY 2 + #define TB_DP_AUX_WEIGHT 1 + ++/* struct tb_regs_hop::initial_credits is 7 bits wide */ ++#define TB_MAX_CREDITS 127 ++ + /* Minimum number of credits needed for PCIe path */ + #define TB_MIN_PCIE_CREDITS 6U + /* +@@ -1778,8 +1781,7 @@ static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits) + if (available < TB_MIN_DMA_CREDITS) + return -ENOSPC; + +- while (credits > available) +- credits--; ++ credits = min(credits, available); + + tb_port_dbg(port, "reserving %u credits for DMA path\n", + credits); +@@ -1908,7 +1910,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, + struct tb_tunnel *tunnel; + size_t npaths = 0, i = 0; + struct tb_path *path; +- int credits; ++ unsigned int credits; + + /* Ring 0 is reserved for control channel */ + if (WARN_ON(!receive_ring || !transmit_ring)) +@@ -1931,6 +1933,11 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, + tunnel->destroy = tb_dma_destroy; + + credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits); ++ if (credits > TB_MAX_CREDITS) { ++ tb_tunnel_dbg(tunnel, "%u credits do not fit a hop, using %u\n", ++ credits, TB_MAX_CREDITS); ++ credits = TB_MAX_CREDITS; ++ } + + if (receive_ring > 0) { + path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -968,6 +968,9 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, + */ + int tb_register_service_driver(struct tb_service_driver *drv) + { ++ if (!drv->probe) ++ return -EINVAL; ++ + drv->driver.bus = &tb_bus_type; + return driver_register(&drv->driver); + } +@@ -1811,6 +1814,7 @@ static void tb_xdomain_state_work(struct work_struct *work) + tb_xdomain_failed(xd); + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; ++ tb_xdomain_queue_properties_changed(xd); + } + break; + +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -465,7 +465,7 @@ static inline struct tb_service *tb_to_service(struct device *dev) + */ + struct tb_service_driver { + struct device_driver driver; +- int (*probe)(struct tb_service *svc, const struct tb_service_id *id); ++ int (*probe)(struct tb_service *svc); + void (*remove)(struct tb_service *svc); + void (*shutdown)(struct tb_service *svc); + const struct tb_service_id *id_table; +@@ -514,6 +514,11 @@ void tb_service_properties_changed(struct tb_service *svc); + * @hop_count: Number of rings (end point hops) supported by NHI. + * @quirks: NHI specific quirks if any + * @domain_released: Completed when domain has been fully released ++ * @host_reset: Host router was reset on driver load, or forced on system ++ * shutdown/reboot. When set, tb_stop() asserts DPR on connected ++ * downstream ports to signal disconnect before tearing down the ++ * router tree. Only Thunderbolt 3 devices are reset; USB4 ++ * routers are skipped. + */ + struct tb_nhi { + spinlock_t lock; +@@ -528,6 +533,7 @@ struct tb_nhi { + u32 hop_count; + unsigned long quirks; + struct completion domain_released; ++ bool host_reset; + }; + + /** +@@ -592,6 +598,8 @@ struct tb_ring { + #define RING_FLAG_FRAME BIT(1) + /* Enable end-to-end flow control */ + #define RING_FLAG_E2E BIT(2) ++/* Do not enable interrupt for the ring */ ++#define RING_FLAG_NO_INTERRUPT BIT(3) + + struct ring_frame; + typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); diff --git a/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f52f6aa7cc2e58c463f1fc907acb042c2ef4d304 GIT binary patch literal 594 zcmV-Y0zyt zjRF8q7i?YA`IY_U7sAgN-xYhY&m9n3z*SSq#P|x=;seLIpif@Q4ZAyfsv&$Kr#Nzi z&&`WTsdt!pC&#sj6EJj-K=MB{5F}p(MI9yaxw$!ccoRm6WZj_8qevxmD^i_%DO{CT zQP{i&T_g(MGv6mp${rumyuq`~!*XphN{OUOu71S{@SV7xUFUEcS*jq*Y$xS}K+59D z6eR0aXf!IOM_HMBdb2E4spwn}4;yvbAYjVjDU#+4#INMa%8(N9qLEa)_TfP|wi|8g zDUn2Hyp^iq9`|~6eUJTeIPFwEfk=8)qUi*T@3Ua|q@9-=v2wgNG=qy{`%3A2BX&nhi->dev, format, ## arg) + +-static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); + /* Serializes access to request kref_get/put */ + static DEFINE_MUTEX(tb_cfg_request_lock); + +@@ -133,41 +132,42 @@ void tb_cfg_request_put(struct tb_cfg_request *req) + static int tb_cfg_request_enqueue(struct tb_ctl *ctl, + struct tb_cfg_request *req) + { ++ tb_cfg_request_get(req); ++ + WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)); + WARN_ON(req->ctl); + +- mutex_lock(&ctl->request_queue_lock); ++ guard(mutex)(&ctl->request_queue_lock); + if (!ctl->running) { +- mutex_unlock(&ctl->request_queue_lock); ++ tb_cfg_request_put(req); + return -ENOTCONN; + } + req->ctl = ctl; + list_add_tail(&req->list, &ctl->request_queue); + set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +- mutex_unlock(&ctl->request_queue_lock); + return 0; + } + ++static bool tb_cfg_request_is_active(struct tb_cfg_request *req) ++{ ++ return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); ++} ++ ++static bool tb_cfg_request_is_canceled(struct tb_cfg_request *req) ++{ ++ return test_bit(TB_CFG_REQUEST_CANCELED, &req->flags); ++} ++ + static void tb_cfg_request_dequeue(struct tb_cfg_request *req) + { + struct tb_ctl *ctl = req->ctl; + +- mutex_lock(&ctl->request_queue_lock); +- if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) { +- mutex_unlock(&ctl->request_queue_lock); +- return; ++ guard(mutex)(&ctl->request_queue_lock); ++ if (tb_cfg_request_is_active(req)) { ++ list_del(&req->list); ++ clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); ++ tb_cfg_request_put(req); + } +- +- list_del(&req->list); +- clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +- if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) +- wake_up(&tb_cfg_request_cancel_queue); +- mutex_unlock(&ctl->request_queue_lock); +-} +- +-static bool tb_cfg_request_is_active(struct tb_cfg_request *req) +-{ +- return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); + } + + static struct tb_cfg_request * +@@ -178,7 +178,7 @@ tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg) + mutex_lock(&pkg->ctl->request_queue_lock); + list_for_each_entry(iter, &pkg->ctl->request_queue, list) { + tb_cfg_request_get(iter); +- if (iter->match(iter, pkg)) { ++ if (!tb_cfg_request_is_canceled(iter) && iter->match(iter, pkg)) { + req = iter; + break; + } +@@ -512,8 +512,11 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, + trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req); + + if (req) { +- if (req->copy(req, pkg)) +- schedule_work(&req->work); ++ scoped_guard(mutex, &pkg->ctl->request_queue_lock) { ++ if (!tb_cfg_request_is_canceled(req) && ++ req->copy(req, pkg)) ++ schedule_work(&req->work); ++ } + tb_cfg_request_put(req); + } + +@@ -525,11 +528,10 @@ static void tb_cfg_request_work(struct work_struct *work) + { + struct tb_cfg_request *req = container_of(work, typeof(*req), work); + +- if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) ++ if (!tb_cfg_request_is_canceled(req)) + req->callback(req->callback_data); + + tb_cfg_request_dequeue(req); +- tb_cfg_request_put(req); + } + + /** +@@ -555,10 +557,9 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + INIT_WORK(&req->work, tb_cfg_request_work); + INIT_LIST_HEAD(&req->list); + +- tb_cfg_request_get(req); + ret = tb_cfg_request_enqueue(ctl, req); + if (ret) +- goto err_put; ++ return ret; + + ret = tb_ctl_tx(ctl, req->request, req->request_size, + req->request_type); +@@ -572,9 +573,6 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + + err_dequeue: + tb_cfg_request_dequeue(req); +-err_put: +- tb_cfg_request_put(req); +- + return ret; + } + +@@ -588,9 +586,10 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + */ + void tb_cfg_request_cancel(struct tb_cfg_request *req, int err) + { +- set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); +- schedule_work(&req->work); +- wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req)); ++ scoped_guard(mutex, &req->ctl->request_queue_lock) ++ set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); ++ cancel_work_sync(&req->work); ++ tb_cfg_request_dequeue(req); + req->result.err = err; + } + +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -560,6 +561,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + INIT_LIST_HEAD(&ring->in_flight); + INIT_WORK(&ring->work, ring_work); + init_waitqueue_head(&ring->wait); ++ lockdep_register_key(&ring->lock_key); ++ lockdep_init_map(&ring->work.lockdep_map, "ring.work", &ring->lock_key, 0); + + ring->nhi = nhi; + ring->hop = hop; +@@ -599,6 +602,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + ring->size * sizeof(*ring->descriptors), + ring->descriptors, ring->descriptors_dma); + err_free_ring: ++ lockdep_unregister_key(&ring->lock_key); + kfree(ring); + + return NULL; +@@ -848,6 +852,7 @@ void tb_ring_free(struct tb_ring *ring) + * to finish before freeing the ring. + */ + flush_work(&ring->work); ++ lockdep_unregister_key(&ring->lock_key); + kfree(ring); + } + EXPORT_SYMBOL_GPL(tb_ring_free); +diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -774,6 +774,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, + { + int port_max_hopid; + struct ida *ida; ++ int ret; + + if (in) { + port_max_hopid = port->config.max_in_hop_id; +@@ -793,7 +794,11 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, + if (max_hopid < 0 || max_hopid > port_max_hopid) + max_hopid = port_max_hopid; + +- return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); ++ ret = ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); ++ if (ret >= 0) ++ tb_switch_get(port->sw); ++ ++ return ret; + } + + /** +@@ -832,6 +837,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid) + void tb_port_release_in_hopid(struct tb_port *port, int hopid) + { + ida_free(&port->in_hopids, hopid); ++ tb_switch_put(port->sw); + } + + /** +@@ -842,6 +848,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid) + void tb_port_release_out_hopid(struct tb_port *port, int hopid) + { + ida_free(&port->out_hopids, hopid); ++ tb_switch_put(port->sw); + } + + static inline bool tb_switch_is_reachable(const struct tb_switch *parent, +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -89,6 +89,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, + const char *reason); + static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port, + int retry, unsigned long delay); ++static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data); + + static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) + { +@@ -385,7 +386,8 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, + + switch (port->config.type) { + case TB_TYPE_DP_HDMI_IN: +- tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); ++ tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids, ++ tb_dp_tunnel_active, tb); + tb_increase_tmu_accuracy(tunnel); + break; + +@@ -1910,6 +1912,18 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) + struct tb *tb = data; + + mutex_lock(&tb->lock); ++ ++ /* ++ * If the DPRX read was canceled the tunnel is already being torn ++ * down by whoever canceled it. Do not touch the adapters here ++ * because the routers may be gone by now. ++ */ ++ if (tunnel->dprx_canceled) { ++ tb_tunnel_dbg(tunnel, "DPRX read canceled, not activating\n"); ++ mutex_unlock(&tb->lock); ++ return; ++ } ++ + if (tb_tunnel_is_active(tunnel)) { + int consumed_up, consumed_down, ret; + +@@ -1964,8 +1978,6 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) + tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed"); + } + mutex_unlock(&tb->lock); +- +- tb_domain_put(tb); + } + + static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, +@@ -2026,8 +2038,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + available_up, available_down); + + tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up, +- available_down, tb_dp_tunnel_active, +- tb_domain_get(tb)); ++ available_down, tb_dp_tunnel_active, tb); + if (!tunnel) { + tb_port_dbg(out, "could not allocate DP tunnel\n"); + goto err_reclaim_usb; +@@ -2048,7 +2059,6 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + tb_tunnel_put(tunnel); + err_reclaim_usb: + tb_reclaim_usb3_bandwidth(tb, in, out); +- tb_domain_put(tb); + err_detach_group: + tb_detach_bandwidth_group(in); + err_dealloc_dp: +@@ -2950,11 +2960,12 @@ static void tb_stop(struct tb *tb) + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { + /* +- * DMA tunnels require the driver to be functional so we +- * tear them down. Other protocol tunnels can be left +- * intact. ++ * DMA tunnels and DP tunnels which are not yet active require ++ * the driver to be functional so we tear them down. ++ * Other protocol tunnels can be left intact. + */ +- if (tb_tunnel_is_dma(tunnel)) ++ if (tb_tunnel_is_dma(tunnel) || ++ (tb_tunnel_is_dp(tunnel) && !tb_tunnel_is_active(tunnel))) + tb_tunnel_deactivate(tunnel); + tb_tunnel_put(tunnel); + } +@@ -3274,11 +3285,11 @@ static void tb_remove_work(struct work_struct *work) + struct tb *tb = tcm_to_tb(tcm); + + mutex_lock(&tb->lock); +- if (tb->root_switch) ++ if (tb->root_switch) { + tb_free_unplugged_children(tb->root_switch); ++ tb_free_unplugged_xdomains(tb->root_switch); ++ } + mutex_unlock(&tb->lock); +- +- tb_free_unplugged_xdomains(tb->root_switch); + } + + static int tb_runtime_resume(struct tb *tb) +diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c +--- a/drivers/thunderbolt/tunnel.c ++++ b/drivers/thunderbolt/tunnel.c +@@ -510,6 +510,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, + goto err_deactivate; + } + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +@@ -1093,8 +1094,14 @@ static void tb_dp_dprx_work(struct work_struct *work) + struct tb_tunnel *tunnel = container_of(work, typeof(*tunnel), dprx_work.work); + struct tb *tb = tunnel->tb; + ++ /* ++ * The DPRX read can be canceled while this work is waiting for ++ * tb->lock. Check the flag only once it is held: while the lock is ++ * held the tunnel cannot be torn down under us and the adapters are ++ * safe to access. ++ */ ++ mutex_lock(&tb->lock); + if (!tunnel->dprx_canceled) { +- mutex_lock(&tb->lock); + if (tb_dp_is_usb4(tunnel->src_port->sw) && + tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) { + if (ktime_before(ktime_get(), tunnel->dprx_timeout)) { +@@ -1106,41 +1113,42 @@ static void tb_dp_dprx_work(struct work_struct *work) + } else { + tb_tunnel_set_active(tunnel, true); + } +- mutex_unlock(&tb->lock); + } ++ mutex_unlock(&tb->lock); + +- if (tunnel->callback) +- tunnel->callback(tunnel, tunnel->callback_data); ++ tunnel->callback(tunnel, tunnel->callback_data); + tb_tunnel_put(tunnel); ++ tb_domain_put(tb); + } + + static int tb_dp_dprx_start(struct tb_tunnel *tunnel) + { + /* +- * Bump up the reference to keep the tunnel around. It will be +- * dropped in tb_dp_dprx_stop() once the tunnel is deactivated. ++ * Bump up the references to keep the tunnel and the domain around ++ * until the work has run or has been canceled. + */ + tb_tunnel_get(tunnel); ++ tb_domain_get(tunnel->tb); + + tunnel->dprx_started = true; ++ tunnel->dprx_canceled = false; ++ tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); ++ queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); + +- if (tunnel->callback) { +- tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); +- queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); +- return -EINPROGRESS; +- } +- +- return tb_dp_is_usb4(tunnel->src_port->sw) ? +- tb_dp_wait_dprx(tunnel, dprx_timeout) : 0; ++ return -EINPROGRESS; + } + + static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) + { ++ struct tb *tb = tunnel->tb; ++ + if (tunnel->dprx_started) { + tunnel->dprx_started = false; + tunnel->dprx_canceled = true; +- if (cancel_delayed_work(&tunnel->dprx_work)) ++ if (cancel_delayed_work(&tunnel->dprx_work)) { + tb_tunnel_put(tunnel); ++ tb_domain_put(tb); ++ } + } + } + +@@ -1582,20 +1590,28 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) + * @tb: Pointer to the domain structure + * @in: DP in adapter + * @alloc_hopid: Allocate HopIDs from visited ports ++ * @callback: Callback that is called when the DP tunnel is fully ++ * activated (or there is an error) ++ * @callback_data: Data for @callback + * + * If @in adapter is active, follows the tunnel to the DP out adapter + * and back. Returns the discovered tunnel or %NULL if there was no +- * tunnel. ++ * tunnel. See tb_tunnel_alloc_dp() for @callback. + * + * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. + */ + struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, +- bool alloc_hopid) ++ bool alloc_hopid, ++ void (*callback)(struct tb_tunnel *, void *), ++ void *callback_data) + { + struct tb_tunnel *tunnel; + struct tb_port *port; + struct tb_path *path; + ++ if (WARN_ON(!callback)) ++ return NULL; ++ + if (!tb_dp_port_is_enabled(in)) + return NULL; + +@@ -1611,6 +1627,9 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; + tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; + tunnel->src_port = in; ++ tunnel->callback = callback; ++ tunnel->callback_data = callback_data; ++ INIT_DELAYED_WORK(&tunnel->dprx_work, tb_dp_dprx_work); + + path = tb_path_discover(in, TB_DP_VIDEO_HOPID, NULL, -1, + &tunnel->dst_port, "Video", alloc_hopid); +@@ -1656,6 +1675,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + + tb_dp_dump(tunnel); + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +@@ -1677,16 +1697,16 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + * %0 if no available bandwidth. + * @max_down: Maximum available downstream bandwidth for the DP tunnel. + * %0 if no available bandwidth. +- * @callback: Optional callback that is called when the DP tunnel is +- * fully activated (or there is an error) +- * @callback_data: Optional data for @callback ++ * @callback: Callback that is called when the DP tunnel is fully ++ * activated (or there is an error) ++ * @callback_data: Data for @callback + * + * Allocates a tunnel between @in and @out that is capable of tunneling +- * Display Port traffic. If @callback is not %NULL it will be called +- * after tb_tunnel_activate() once the tunnel has been fully activated. +- * It can call tb_tunnel_is_active() to check if activation was +- * successful (or if it returns %false there was some sort of issue). +- * The @callback is called without @tb->lock held. ++ * Display Port traffic. The @callback is called after tb_tunnel_activate() ++ * once the tunnel has been fully activated. It can call ++ * tb_tunnel_is_active() to check if activation was successful (or if it ++ * returns %false there was some sort of issue). The @callback is called ++ * without @tb->lock held. + * + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. + */ +@@ -1701,7 +1721,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, + struct tb_path *path; + bool pm_support; + +- if (WARN_ON(!in->cap_adap || !out->cap_adap)) ++ if (WARN_ON(!in->cap_adap || !out->cap_adap || !callback)) + return NULL; + + tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); +@@ -2288,6 +2308,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, + tb_usb3_reclaim_available_bandwidth; + } + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h +--- a/drivers/thunderbolt/tunnel.h ++++ b/drivers/thunderbolt/tunnel.h +@@ -66,8 +66,8 @@ enum tb_tunnel_state { + * @dprx_canceled: Was DPRX capabilities read poll canceled + * @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes + * @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read +- * @callback: Optional callback called when DP tunnel is fully activated +- * @callback_data: Optional data for @callback ++ * @callback: Callback called when DP tunnel is fully activated ++ * @callback_data: Data for @callback + * @paths: All paths required by the tunnel + */ + struct tb_tunnel { +@@ -117,7 +117,9 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, + bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, + int *reserved_down); + struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, +- bool alloc_hopid); ++ bool alloc_hopid, ++ void (*callback)(struct tb_tunnel *, void *), ++ void *callback_data); + struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, + struct tb_port *out, int link_nr, + int max_up, int max_down, +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -1814,7 +1814,6 @@ static void tb_xdomain_state_work(struct work_struct *work) + tb_xdomain_failed(xd); + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; +- tb_xdomain_queue_properties_changed(xd); + } + break; + +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -22,6 +22,7 @@ struct device; + #include + #include + #include ++#include + #include + #include + #include +@@ -565,6 +566,7 @@ struct tb_nhi { + * @interval_nsec: Interval counter if interrupt throttling is to be + * used with this ring (in ns) + * @wait: Used to signal that the ring may be empty now ++ * @lock_key: Lock validator class key per-ring + */ + struct tb_ring { + spinlock_t lock; +@@ -590,6 +592,7 @@ struct tb_ring { + void *poll_data; + unsigned int interval_nsec; + wait_queue_head_t wait; ++ struct lock_class_key lock_key; + }; + + /* Leave ring interrupt enabled on suspend */ diff --git a/pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..2c4aa9c1797fefd6110081365f587964862f4fc6 GIT binary patch literal 594 zcmV-Y0<~>iJ*`P=2v|SDMr+ z3+N8&jS?^Nt|=c8I*6nXOr5(0Z^>+%SBr$nqC69zc5BlsMS;HuZaO!iys{J~B_;Np z5$^``S!k}wEYKpc_dBc(iq>aZ7ltuir}{(&iq~dmC5Z;!d9ndePl)q8Gf2i^=FnF` zx#|8{rlT~-^@$Sw{m3e*#4u|7WW`qf60D3%IpcOO$g8{CObgY|P5Prx@co9v7pt54 z+Kd7BRjgYPU_}>E-BmU;f6<36zq|qXo3DCLz1cGj^Lh-=Eyh#0JiaD@#*w7f`I8U< zj5dSxlTsV<8gTBuxAUaI{BpCml^zu-Nufe4lhWk%wBC#TmUcuE2daPVFi_l?bgk@G6*A(cGFhP5qFHGoyE>%AI_Z z>73&Dx!*2qcV$>4qN6xkrB33b!{D5!z>3e_{1U%!j7hydesc->type != POWER_SUPPLY_TYPE_BATTERY) ++ return false; ++ ++ if (!power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_SCOPE, ++ &val)) ++ if (val.intval == POWER_SUPPLY_SCOPE_DEVICE) ++ return false; ++ ++ return true; ++} ++ ++static int __power_supply_get_num_system_batteries(struct power_supply *epsy, ++ void *data) ++{ ++ int *count = data; ++ ++ if (power_supply_is_system_battery(epsy)) ++ (*count)++; ++ ++ return 0; ++} ++ ++static int power_supply_get_num_system_batteries(struct device *dev) ++{ ++ int ret, count = 0; ++ ++ ret = power_supply_for_each_psy(&count, ++ __power_supply_get_num_system_batteries); ++ ++ dev_dbg(dev, "%s: count: %d ret %d\n", __func__, count, ret); ++ ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ ++struct psy_get_supplies_data { ++ int cnt; ++ int size; ++ struct power_supply **psys; ++}; ++ ++static int ++__power_supply_populate_system_batteries_array(struct power_supply *epsy, ++ void *_data) ++{ ++ struct psy_get_supplies_data *data = _data; ++ ++ if (power_supply_is_system_battery(epsy)) { ++ if (data->size <= data->cnt) ++ return -EOVERFLOW; ++ ++ get_device(&epsy->dev); ++ data->psys[data->cnt] = epsy; ++ atomic_inc(&epsy->use_cnt); ++ data->cnt++; ++ } ++ ++ return 0; ++} ++ ++static int ++power_supply_populate_system_batteries_array(struct device *dev, int size, ++ struct power_supply **batteries) ++{ ++ int ret; ++ ++ struct psy_get_supplies_data data = { ++ .cnt = 0, ++ .size = size, ++ .psys = batteries, ++ }; ++ ++ ret = power_supply_for_each_psy(&data, ++ __power_supply_populate_system_batteries_array); ++ ++ dev_dbg(dev, "%s Found %d batteries with array size %d ret %d\n", ++ __func__, data.cnt, data.size, ret); ++ ++ if (ret < 0 || !data.cnt) { ++ power_supply_put_system_batteries(batteries, data.cnt); ++ return ret; ++ } ++ ++ return data.cnt; ++} ++ ++/** ++ * power_supply_get_system_batteries() - Fetches references to battery type ++ * power supplies in the system. ++ * @dev: Pointer to device requesting the power supply refs. ++ * @psys: Pointer to an array of power supply refs. ++ * ++ * Helper function to get handles to battery type power supplies in the system. ++ * If acquiring a ref to a power supply fails, then the search for battery type ++ * power supplies will abort and the acquired power supply references will be ++ * released. ++ * ++ * Return: Indicates the number of battery type power supplies returned on ++ * success or a negative error code on failure. ++ * ++ * Call power_supply_put_system_batteries() after use to cleanup resources. ++ */ ++int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys) ++{ ++ int ret; ++ ++ if (!psys) ++ return -EINVAL; ++ ++ ret = power_supply_get_num_system_batteries(dev); ++ if (ret <= 0) { ++ *psys = NULL; ++ return ret; ++ } ++ ++ *psys = kzalloc_objs(**psys, ret); ++ if (!*psys) ++ return -ENOMEM; ++ ++ ret = power_supply_populate_system_batteries_array(dev, ret, *psys); ++ if (ret <= 0) ++ *psys = NULL; ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(power_supply_get_system_batteries); ++ ++/** ++ * power_supply_put_system_batteries() - Cleanup resources allocated by ++ * power_supply_get_system_batteries() ++ * @psys: Array of power supply references to release and free. ++ * @count: Number of elements in the array. ++ */ ++void power_supply_put_system_batteries(struct power_supply **psys, int count) ++{ ++ int i; ++ ++ for (i = 0; i < count; i++) { ++ if (psys[i]) ++ power_supply_put(psys[i]); ++ } ++ ++ kfree(psys); ++} ++EXPORT_SYMBOL_GPL(power_supply_put_system_batteries); ++ + /** + * power_supply_put() - Drop reference obtained with power_supply_get_by_name + * @psy: Reference to put +diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c +--- a/drivers/usb/cdns3/cdns3-plat.c ++++ b/drivers/usb/cdns3/cdns3-plat.c +@@ -147,12 +147,12 @@ static int cdns3_plat_probe(struct platform_device *pdev) + cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); + if (IS_ERR(cdns->usb2_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy), +- "Failed to get cdn3,usb2-phy\n"); ++ "Failed to get cdns3,usb2-phy\n"); + + cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); + if (IS_ERR(cdns->usb3_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy), +- "Failed to get cdn3,usb3-phy\n"); ++ "Failed to get cdns3,usb3-phy\n"); + + ret = phy_init(cdns->usb2_phy); + if (ret) +diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c +--- a/drivers/usb/core/config.c ++++ b/drivers/usb/core/config.c +@@ -151,7 +151,7 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, + usb_endpoint_xfer_int(&ep->desc)) && + desc->bmAttributes != 0) { + dev_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n", +- usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk", ++ usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt", + desc->bmAttributes, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 0; +diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c +--- a/drivers/usb/core/devices.c ++++ b/drivers/usb/core/devices.c +@@ -37,6 +37,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -408,7 +409,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + return 0; + /* allocate 2^1 pages = 8K (on i386); + * should be more than enough for one device */ +- pages_start = (char *)__get_free_pages(GFP_NOIO, 1); ++ pages_start = kmalloc(PAGE_SIZE << 1, GFP_NOIO); + if (!pages_start) + return -ENOMEM; + +@@ -479,7 +480,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + if (length > *nbytes) + length = *nbytes; + if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { +- free_pages((unsigned long)pages_start, 1); ++ kfree(pages_start); + return -EFAULT; + } + *nbytes -= length; +@@ -490,7 +491,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + } else + *skip_bytes -= length; + +- free_pages((unsigned long)pages_start, 1); ++ kfree(pages_start); + + /* Now look at all of this device's children. */ + usb_hub_for_each_child(usbdev, chix, childdev) { +diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c +--- a/drivers/usb/core/devio.c ++++ b/drivers/usb/core/devio.c +@@ -2329,6 +2329,13 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) + if (!connected(ps)) + return -ENODEV; + ++ if (ps->dev->state != USB_STATE_CONFIGURED) ++ return -EHOSTUNREACH; ++ ++ intf = usb_ifnum_to_if(ps->dev, ctl->ifno); ++ if (!intf) ++ return -EINVAL; ++ + /* alloc buffer */ + size = _IOC_SIZE(ctl->ioctl_code); + if (size > 0) { +@@ -2345,11 +2352,7 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) + } + } + +- if (ps->dev->state != USB_STATE_CONFIGURED) +- retval = -EHOSTUNREACH; +- else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno))) +- retval = -EINVAL; +- else switch (ctl->ioctl_code) { ++ switch (ctl->ioctl_code) { + + /* disconnect kernel driver from interface */ + case USBDEVFS_DISCONNECT: +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -623,11 +623,11 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, + mutex_lock(&hub->status_mutex); + ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len); + if (ret < len) { +- if (ret != -ENODEV) +- dev_err(hub->intfdev, +- "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; ++ if (ret != -ENODEV) ++ dev_dbg(hub->intfdev, ++ "get_port_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->port.wPortStatus); + *change = le16_to_cpu(hub->status->port.wPortChange); +@@ -995,11 +995,11 @@ static int hub_hub_status(struct usb_hub *hub, + mutex_lock(&hub->status_mutex); + ret = get_hub_status(hub->hdev, &hub->status->hub); + if (ret < (int)sizeof(hub->status->hub)) { +- if (ret != -ENODEV) +- dev_err(hub->intfdev, +- "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; ++ if (ret != -ENODEV) ++ dev_dbg(hub->intfdev, ++ "get_hub_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->hub.wHubStatus); + *change = le16_to_cpu(hub->status->hub.wHubChange); +diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c +--- a/drivers/usb/dwc2/core_intr.c ++++ b/drivers/usb/dwc2/core_intr.c +@@ -334,9 +334,8 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) + + /** + * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state +- * + * @hsotg: Programming view of DWC_otg controller +- * ++ * @remotewakeup: Whether this is a remote wakeup + */ + void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup) + { +diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c +--- a/drivers/usb/dwc3/core.c ++++ b/drivers/usb/dwc3/core.c +@@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work) + ret, dwc->current_limit); + } + +-static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) ++static int dwc3_psy_notifier(struct notifier_block *nb, ++ unsigned long event, void *data) + { +- struct power_supply *usb_psy; +- const char *usb_psy_name; ++ struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb); ++ struct power_supply *psy = data; ++ unsigned long flags; ++ ++ if (dwc->usb_psy) ++ return NOTIFY_DONE; ++ ++ if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0) ++ return NOTIFY_DONE; ++ ++ /* Explicitly get the reference for this psy */ ++ psy = power_supply_get_by_name(dwc->usb_psy_name); ++ if (!psy) ++ return NOTIFY_DONE; ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ /* ++ * The USB power_supply may already be set. This can happen if notifier ++ * callbacks for the USB power_supply race, or if a previous notifier ++ * callback has already successfully fetched and associated the instance. ++ * In such cases, release the newly acquired reference and ignore ++ * subsequent notifications until the notifier is unregistered. ++ */ ++ if (dwc->usb_psy) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ power_supply_put(psy); ++ return NOTIFY_DONE; ++ } ++ ++ dwc->usb_psy = psy; ++ if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED) ++ schedule_work(&dwc->vbus_draw_work); ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ ++ return NOTIFY_OK; ++} ++ ++static void dwc3_get_usb_power_supply(struct dwc3 *dwc) ++{ ++ struct power_supply *psy; ++ unsigned long flags; + int ret; + +- ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name); ++ ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name); + if (ret < 0) +- return NULL; +- +- usb_psy = power_supply_get_by_name(usb_psy_name); +- if (!usb_psy) +- return ERR_PTR(-EPROBE_DEFER); ++ return; + + INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work); +- return usb_psy; ++ ++ dwc->current_limit = DWC3_CURRENT_UNSPECIFIED; ++ dwc->psy_nb.notifier_call = dwc3_psy_notifier; ++ ret = power_supply_reg_notifier(&dwc->psy_nb); ++ if (ret) { ++ dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret); ++ dwc->psy_nb.notifier_call = NULL; ++ return; ++ } ++ ++ psy = power_supply_get_by_name(dwc->usb_psy_name); ++ if (!psy) ++ return; ++ ++ /* Unregister the notifier now that we have the power supply */ ++ power_supply_unreg_notifier(&dwc->psy_nb); ++ dwc->psy_nb.notifier_call = NULL; ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ /* ++ * It is possible that the notifier callback ran before we reached here ++ * and successfully fetched the power supply. In that case we need to ++ * release the above reference. ++ */ ++ if (dwc->usb_psy) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ power_supply_put(psy); ++ return; ++ } ++ ++ dwc->usb_psy = psy; ++ spin_unlock_irqrestore(&dwc->lock, flags); + } + + int dwc3_core_probe(const struct dwc3_probe_data *data) +@@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + + dwc3_get_software_properties(dwc, &data->properties); + +- dwc->usb_psy = dwc3_get_usb_power_supply(dwc); +- if (IS_ERR(dwc->usb_psy)) +- return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); ++ spin_lock_init(&dwc->lock); ++ ++ dwc3_get_usb_power_supply(dwc); + + if (!data->ignore_clocks_and_resets) { + dwc->reset = devm_reset_control_array_get_optional_shared(dev); +@@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + dwc->num_usb3_ports = 1; + } + +- spin_lock_init(&dwc->lock); + mutex_init(&dwc->mutex); + + pm_runtime_get_noresume(dev); +@@ -2373,6 +2439,8 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + err_assert_reset: + reset_control_assert(dwc->reset); + err_put_psy: ++ if (dwc->psy_nb.notifier_call) ++ power_supply_unreg_notifier(&dwc->psy_nb); + if (dwc->usb_psy) + power_supply_put(dwc->usb_psy); + +@@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc) + + dwc3_free_event_buffers(dwc); + ++ if (dwc->psy_nb.notifier_call) ++ power_supply_unreg_notifier(&dwc->psy_nb); ++ + if (dwc->usb_psy) { + cancel_work_sync(&dwc->vbus_draw_work); + power_supply_put(dwc->usb_psy); +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -722,7 +722,6 @@ struct dwc3_event_buffer { + * @cancelled_list: list of cancelled requests for this endpoint + * @pending_list: list of pending requests for this endpoint + * @started_list: list of started requests on this endpoint +- * @regs: pointer to first endpoint register + * @trb_pool: array of transaction buffers + * @trb_pool_dma: dma address of @trb_pool + * @trb_enqueue: enqueue 'pointer' into TRB array +@@ -1059,6 +1058,8 @@ struct dwc3_glue_ops { + * @role_switch_default_mode: default operation mode of controller while + * usb role is USB_ROLE_NONE. + * @usb_psy: pointer to power supply interface. ++ * @usb_psy_name: name of the USB power supply ++ * @psy_nb: power supply notifier block + * @vbus_draw_work: Work to set the vbus drawing limit + * @current_limit: How much current to draw from vbus, in milliAmperes. + * @usb2_phy: pointer to USB2 PHY +@@ -1251,9 +1252,13 @@ struct dwc3 { + enum usb_dr_mode role_switch_default_mode; + + struct power_supply *usb_psy; ++ const char *usb_psy_name; ++ struct notifier_block psy_nb; + struct work_struct vbus_draw_work; + unsigned int current_limit; + ++#define DWC3_CURRENT_UNSPECIFIED UINT_MAX ++ + u32 fladj; + u32 ref_clk_per; + u32 irq_gadget; +diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c +--- a/drivers/usb/dwc3/dwc3-am62.c ++++ b/drivers/usb/dwc3/dwc3-am62.c +@@ -205,7 +205,9 @@ static int dwc3_ti_init(struct dwc3_am62 *am62) + + dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); + +- clk_prepare_enable(am62->usb2_refclk); ++ ret = clk_prepare_enable(am62->usb2_refclk); ++ if (ret) ++ return ret; + + /* Set mode valid bit to indicate role is valid */ + reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); +@@ -361,14 +363,19 @@ static int dwc3_ti_resume_common(struct device *dev) + { + struct dwc3_am62 *am62 = dev_get_drvdata(dev); + u32 reg; ++ int ret; + + reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG); + if (reg != USBSS_DEBUG_CFG_DISABLED) { + /* lost power/context */ +- dwc3_ti_init(am62); ++ ret = dwc3_ti_init(am62); ++ if (ret) ++ return ret; + } else { + dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF); +- clk_prepare_enable(am62->usb2_refclk); ++ ret = clk_prepare_enable(am62->usb2_refclk); ++ if (ret) ++ return ret; + } + + if (device_may_wakeup(dev)) { +diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c +--- a/drivers/usb/dwc3/dwc3-qcom.c ++++ b/drivers/usb/dwc3/dwc3-qcom.c +@@ -602,7 +602,7 @@ static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) + pm_runtime_mark_last_busy(qcom->dev); + } + +-struct dwc3_glue_ops dwc3_qcom_glue_ops = { ++static struct dwc3_glue_ops dwc3_qcom_glue_ops = { + .pre_set_role = dwc3_qcom_set_role_notifier, + .pre_run_stop = dwc3_qcom_run_stop_notifier, + }; +diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c +--- a/drivers/usb/dwc3/dwc3-xilinx.c ++++ b/drivers/usb/dwc3/dwc3-xilinx.c +@@ -98,18 +98,10 @@ static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data) + + dwc3_xlnx_mask_phy_rst(priv_data, false); + +- /* Assert and De-assert reset */ +- ret = reset_control_assert(crst); +- if (ret < 0) { +- dev_err_probe(dev, ret, "failed to assert Reset\n"); +- return ret; +- } +- +- ret = reset_control_deassert(crst); +- if (ret < 0) { +- dev_err_probe(dev, ret, "failed to De-assert Reset\n"); +- return ret; +- } ++ /* assert and deassert reset */ ++ ret = reset_control_reset(crst); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to assert and deassert reset\n"); + + dwc3_xlnx_mask_phy_rst(priv_data, true); + dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG); +@@ -194,7 +186,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) + } + + if (priv_data->usb3_phy) { +- /* Set PIPE Power Present signal in FPD Power Present Register*/ ++ /* Set PIPE Power Present signal in FPD Power Present Register */ + writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT); + /* Set the PIPE Clock Select bit in FPD PIPE Clock register */ + writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK); +diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c +--- a/drivers/usb/dwc3/gadget.c ++++ b/drivers/usb/dwc3/gadget.c +@@ -3129,15 +3129,26 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g, + static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) + { + struct dwc3 *dwc = gadget_to_dwc(g); ++ unsigned long flags; + + if (dwc->usb2_phy) + return usb_phy_set_power(dwc->usb2_phy, mA); + +- if (!dwc->usb_psy) +- return -EOPNOTSUPP; ++ spin_lock_irqsave(&dwc->lock, flags); ++ if (!dwc->usb_psy) { ++ if (!dwc->psy_nb.notifier_call) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -EOPNOTSUPP; ++ } ++ dwc->current_limit = mA; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA); ++ return 0; ++ } + + dwc->current_limit = mA; + schedule_work(&dwc->vbus_draw_work); ++ spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; + } +diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c +--- a/drivers/usb/dwc3/host.c ++++ b/drivers/usb/dwc3/host.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + + #include "../host/xhci-port.h" + #include "../host/xhci-ext-caps.h" +@@ -46,9 +47,9 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) + return; + } + +- op_regs_base = HC_LENGTH(readl(xhci_regs)); ++ op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs)); + reg = readl(xhci_regs + XHCI_HCSPARAMS1); +- port_num = HCS_MAX_PORTS(reg); ++ port_num = FIELD_GET(HCS_MAX_PORTS, reg); + + for (i = 1; i <= port_num; i++) { + offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); +diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c +--- a/drivers/usb/gadget/configfs.c ++++ b/drivers/usb/gadget/configfs.c +@@ -125,11 +125,11 @@ static int usb_string_copy(const char *s, char **s_copy) + if (copy) { + str = copy; + } else { +- str = kmalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); ++ str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); + if (!str) + return -ENOMEM; + } +- strcpy(str, s); ++ memcpy(str, s, ret + 1); + if (str[ret - 1] == '\n') + str[ret - 1] = '\0'; + *s_copy = str; +diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -159,7 +159,9 @@ struct ffs_epfile { + struct mutex mutex; + + struct ffs_data *ffs; +- struct ffs_ep *ep; /* P: ffs->eps_lock */ ++ struct ffs_ep *ep; /* P: ffs->eps_lock */ ++ struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */ ++ struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */ + + /* + * Buffer for holding data from partial reads which may happen since +@@ -219,12 +221,13 @@ struct ffs_epfile { + struct ffs_buffer *read_buffer; + #define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) + +- char name[5]; ++ char name[8]; + + unsigned char in; /* P: ffs->eps_lock */ + unsigned char isoc; /* P: ffs->eps_lock */ + +- unsigned char _pad; ++ u8 zlp_enabled; /* P: ffs->eps_lock */ ++ bool is_rw_proxy; + + /* Protects dmabufs */ + struct mutex dmabufs_mutex; +@@ -991,9 +994,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, + return ret; + } + +-static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) ++static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file) + { +- struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; + +@@ -1020,17 +1022,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + char *data = NULL; + ssize_t ret, data_len = -EINVAL; + int halt; ++ bool is_rw_proxy = epfile->is_rw_proxy; + + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + +- ep = ffs_epfile_wait_ep(file); ++ /* Proxy to base endpoint if rw_proxy */ ++ if (is_rw_proxy) ++ epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in; ++ ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + + /* Do we halt? */ +- halt = (!io_data->read == !epfile->in); ++ halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in); + if (halt && epfile->isoc) + return -EINVAL; + +@@ -1127,6 +1134,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + req->buf = data; + req->num_sgs = 0; + } ++ ++ req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; +@@ -1178,6 +1187,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + req->buf = data; + req->num_sgs = 0; + } ++ ++ req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; +@@ -1666,7 +1677,7 @@ static int ffs_dmabuf_transfer(struct file *file, + + priv = attach->importer_priv; + +- ep = ffs_epfile_wait_ep(file); ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + goto err_attachment_put; +@@ -1732,6 +1743,7 @@ static int ffs_dmabuf_transfer(struct file *file, + + /* Now that the dma_fence is in place, queue the transfer. */ + ++ usb_req->zero = epfile->zlp_enabled; + usb_req->length = req->length; + usb_req->buf = NULL; + usb_req->sg = priv->sgt->sgl; +@@ -1779,10 +1791,14 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; ++ __u32 enable_zlp = 0; + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + ++ if (epfile->is_rw_proxy) ++ return -ENOTTY; ++ + switch (code) { + case FUNCTIONFS_DMABUF_ATTACH: + { +@@ -1811,12 +1827,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + + return ffs_dmabuf_transfer(file, &req); + } ++ /* ++ * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace ++ * to configure ZLP behavior immediately without blocking indefinitely ++ * while waiting for the USB host to connect and enable the endpoint. ++ */ ++ case FUNCTIONFS_ENDPOINT_ENABLE_ZLP: ++ if (!epfile->in) ++ return -EINVAL; ++ ++ if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp))) ++ return -EFAULT; ++ ++ spin_lock_irq(&epfile->ffs->eps_lock); ++ epfile->zlp_enabled = !!enable_zlp; ++ spin_unlock_irq(&epfile->ffs->eps_lock); ++ ++ return 0; + default: + break; + } + + /* Wait for endpoint to be enabled */ +- ep = ffs_epfile_wait_ep(file); ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + +@@ -2214,7 +2247,7 @@ static void ffs_data_closed(struct ffs_data *ffs) + + if (epfiles) + ffs_epfiles_destroy(ffs->sb, epfiles, +- ffs->eps_count); ++ ffs->epfiles_count); + + if (ffs->setup_state == FFS_SETUP_PENDING) + __ffs_ep0_stall(ffs); +@@ -2273,7 +2306,7 @@ static void ffs_data_clear(struct ffs_data *ffs) + * copy of epfile will save us from use-after-free. + */ + if (epfiles) { +- ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); ++ ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count); + ffs->epfiles = NULL; + } + +@@ -2371,11 +2404,16 @@ static void functionfs_unbind(struct ffs_data *ffs) + static int ffs_epfiles_create(struct ffs_data *ffs) + { + struct ffs_epfile *epfile, *epfiles; +- unsigned i, count; ++ unsigned int i, count, epfiles_count; + int err; + + count = ffs->eps_count; +- epfiles = kzalloc_objs(*epfiles, count); ++ epfiles_count = count; ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) ++ epfiles_count += count / 2; ++ ffs->epfiles_count = epfiles_count; ++ ++ epfiles = kzalloc_objs(*epfiles, epfiles_count); + if (!epfiles) + return -ENOMEM; + +@@ -2398,6 +2436,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs) + } + } + ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { ++ struct ffs_epfile *comp = epfiles + count; ++ ++ for (i = 0; i < count; i += 2, ++comp) { ++ struct ffs_epfile *ep1 = &epfiles[i]; ++ struct ffs_epfile *ep2 = &epfiles[i + 1]; ++ bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK; ++ ++ comp->ffs = ffs; ++ comp->is_rw_proxy = true; ++ comp->epfile_in = ep1_in ? ep1 : ep2; ++ comp->epfile_out = ep1_in ? ep2 : ep1; ++ mutex_init(&comp->mutex); ++ mutex_init(&comp->dmabufs_mutex); ++ INIT_LIST_HEAD(&comp->dmabufs); ++ snprintf(comp->name, sizeof(comp->name), "%s_rw", ++ epfiles[i].name); ++ err = ffs_sb_create_file(ffs->sb, comp->name, ++ comp, &ffs_epfile_operations); ++ if (err) { ++ ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2)); ++ return err; ++ } ++ } ++ } ++ + ffs->epfiles = epfiles; + return 0; + } +@@ -2975,7 +3039,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, + FUNCTIONFS_VIRTUAL_ADDR | + FUNCTIONFS_EVENTFD | + FUNCTIONFS_ALL_CTRL_RECIP | +- FUNCTIONFS_CONFIG0_SETUP)) { ++ FUNCTIONFS_CONFIG0_SETUP | ++ FUNCTIONFS_RW_PROXY_EPS)) { + ret = -ENOSYS; + goto error; + } +@@ -3063,6 +3128,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, + goto error; + } + ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { ++ if (ffs->eps_count % 2) { ++ ret = -EINVAL; ++ goto error; ++ } ++ ++ for (i = 1; i < ffs->eps_count; i += 2) { ++ if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) == ++ (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) { ++ ret = -EINVAL; ++ goto error; ++ } ++ } ++ } ++ + ffs->raw_descs_data = _data; + ffs->raw_descs = raw_descs; + ffs->raw_descs_length = data - raw_descs; +@@ -3358,7 +3438,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, + struct usb_request *req; + struct usb_ep *ep; + u8 bEndpointAddress; +- u16 wMaxPacketSize; ++ __le16 wMaxPacketSize; + + /* + * We back up bEndpointAddress because autoconfig overwrites +diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c +--- a/drivers/usb/gadget/function/f_mass_storage.c ++++ b/drivers/usb/gadget/function/f_mass_storage.c +@@ -203,7 +203,6 @@ + /*------------------------------------------------------------------------*/ + + #define FSG_DRIVER_DESC "Mass Storage Function" +-#define FSG_DRIVER_VERSION "2009/09/11" + + static const char fsg_string_interface[] = "Mass Storage"; + +@@ -3516,8 +3515,6 @@ static struct usb_function_instance *fsg_alloc_inst(void) + if (rc) + goto release_common; + +- pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); +- + memset(&config, 0, sizeof(config)); + config.removable = true; + rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0", +diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c +--- a/drivers/usb/gadget/function/u_audio.c ++++ b/drivers/usb/gadget/function/u_audio.c +@@ -641,15 +641,15 @@ int u_audio_start_capture(struct g_audio *audio_dev) + ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret); +- return ret; // TODO: Clean up out_ep ++ goto err_out_ep; + } + +- prm->fb_ep_enabled = true; + ret = usb_ep_enable(ep_fback); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret); +- return ret; // TODO: Clean up out_ep ++ goto err_out_ep; + } ++ prm->fb_ep_enabled = true; + req_len = ep_fback->maxpacket; + + req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); +@@ -680,6 +680,12 @@ int u_audio_start_capture(struct g_audio *audio_dev) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + + return 0; ++ ++err_out_ep: ++ set_active(prm, false); ++ free_ep(prm, ep); ++ ++ return ret; + } + EXPORT_SYMBOL_GPL(u_audio_start_capture); + +diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h +--- a/drivers/usb/gadget/function/u_fs.h ++++ b/drivers/usb/gadget/function/u_fs.h +@@ -252,8 +252,14 @@ struct ffs_data { + + unsigned short strings_count; + unsigned short interfaces_count; ++ ++ /* ++ * eps_count tracks the number of underlying hardware endpoints. ++ * epfiles_count tracks the total number of VFS endpoint files. ++ * When companion endpoints are active, epfiles_count > eps_count. ++ */ + unsigned short eps_count; +- unsigned short _pad1; ++ unsigned short epfiles_count; + + /* filled by __ffs_data_got_strings() */ + /* ids in stringtabs are set in functionfs_bind() */ +diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c +--- a/drivers/usb/gadget/legacy/inode.c ++++ b/drivers/usb/gadget/legacy/inode.c +@@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work) + struct kiocb *iocb = priv->iocb; + size_t ret; + +- kthread_use_mm(mm); +- ret = copy_to_iter(priv->buf, priv->actual, &priv->to); +- kthread_unuse_mm(mm); +- if (!ret) ++ if (mmget_not_zero(mm)) { ++ kthread_use_mm(mm); ++ ret = copy_to_iter(priv->buf, priv->actual, &priv->to); ++ kthread_unuse_mm(mm); ++ mmput(mm); ++ if (!ret) ++ ret = -EFAULT; ++ } else { + ret = -EFAULT; ++ } ++ mmdrop(mm); + + /* completing the iocb can drop the ctx and mm, don't touch mm after */ + iocb->ki_complete(iocb, ret); +@@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) + * complete the aio request immediately. + */ + if (priv->to_free == NULL || unlikely(req->actual == 0)) { ++ mmdrop(priv->mm); + kfree(req->buf); + kfree(priv->to_free); + kfree(priv); +@@ -541,6 +548,7 @@ static ssize_t ep_aio(struct kiocb *iocb, + priv->epdata = epdata; + priv->actual = 0; + priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ ++ mmgrab(priv->mm); + + /* each kiocb is coupled to one usb_request, but we can't + * allocate or submit those if the host disconnected. +@@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb, + + fail: + spin_unlock_irq(&epdata->dev->lock); ++ mmdrop(priv->mm); + kfree(priv->to_free); + kfree(priv); + put_ep(epdata); +diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c +--- a/drivers/usb/gadget/legacy/nokia.c ++++ b/drivers/usb/gadget/legacy/nokia.c +@@ -50,7 +50,7 @@ static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; + */ + #define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS + +-#endif /* CONFIG_USB_DEBUG */ ++#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + + FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig +--- a/drivers/usb/gadget/udc/Kconfig ++++ b/drivers/usb/gadget/udc/Kconfig +@@ -90,7 +90,7 @@ config USB_BCM63XX_UDC + + config USB_FSL_USB2 + tristate "Freescale Highspeed USB DR Peripheral Controller" +- depends on FSL_SOC ++ depends on FSL_SOC || COMPILE_TEST + help + Some of Freescale PowerPC and i.MX processors have a High Speed + Dual-Role(DR) USB controller, which supports device mode. +diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c +--- a/drivers/usb/gadget/udc/fsl_qe_udc.c ++++ b/drivers/usb/gadget/udc/fsl_qe_udc.c +@@ -1945,6 +1945,7 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) + { + u16 usb_status = 0; ++ struct usb_request *usb_req; + struct qe_req *req; + struct qe_ep *ep; + int status = 0; +@@ -1983,8 +1984,11 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + } + } + +- req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL), +- struct qe_req, req); ++ usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL); ++ if (!usb_req) ++ goto stall; ++ ++ req = container_of(usb_req, struct qe_req, req); + req->req.length = 2; + req->req.buf = udc->statusbuf; + *(u16 *)req->req.buf = cpu_to_le16(usb_status); +diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c +--- a/drivers/usb/gadget/udc/pch_udc.c ++++ b/drivers/usb/gadget/udc/pch_udc.c +@@ -320,7 +320,6 @@ struct pch_vbus_gpio_data { + * @lock: protects all state + * @stall: stall requested + * @prot_stall: protcol stall requested +- * @registered: driver registered with system + * @suspended: driver in suspended state + * @connected: gadget driver associated + * @vbus_session: required vbus_session state +diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c +--- a/drivers/usb/host/fsl-mph-dr-of.c ++++ b/drivers/usb/host/fsl-mph-dr-of.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + + struct fsl_usb2_dev_data { + char *dr_mode; /* controller mode */ +@@ -183,7 +184,7 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) + const struct of_device_id *match; + const unsigned char *prop; + static unsigned int idx; +- int i; ++ int i, err; + + if (!of_device_is_available(np)) + return -ENODEV; +@@ -246,6 +247,10 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) + } + } + ++ err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus"); ++ if (err) ++ return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n"); ++ + for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) { + if (!dev_data->drivers[i]) + continue; +diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c +--- a/drivers/usb/host/ohci-dbg.c ++++ b/drivers/usb/host/ohci-dbg.c +@@ -683,7 +683,7 @@ static int fill_buffer(struct debug_buffer *buf) + int ret; + + if (!buf->page) +- buf->page = (char *)get_zeroed_page(GFP_KERNEL); ++ buf->page = kzalloc(PAGE_SIZE, GFP_KERNEL); + + if (!buf->page) { + ret = -ENOMEM; +@@ -729,11 +729,8 @@ static int debug_close(struct inode *inode, struct file *file) + { + struct debug_buffer *buf = file->private_data; + +- if (buf) { +- if (buf->page) +- free_page((unsigned long)buf->page); +- kfree(buf); +- } ++ kfree(buf->page); ++ kfree(buf); + + return 0; + } +diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h +--- a/drivers/usb/host/xhci-caps.h ++++ b/drivers/usb/host/xhci-caps.h +@@ -5,22 +5,23 @@ + */ + + #include ++#include + + /* hc_capbase - bitmasks */ + /* bits 7:0 - Capability Registers Length */ +-#define HC_LENGTH(p) ((p) & 0xff) ++#define HC_LENGTH GENMASK(7, 0) + /* bits 15:8 - Rsvd */ + /* bits 31:16 - Host Controller Interface Version Number */ +-#define HC_VERSION(p) (((p) >> 16) & 0xffff) ++#define HC_VERSION GENMASK(31, 16) + + /* HCSPARAMS1 - hcs_params1 - bitmasks */ + /* bits 7:0 - Number of Device Slots */ +-#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +-#define HCS_SLOTS_MASK 0xff +-/* bits 18:8 - Number of Interrupters, max values is 1024 */ +-#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +-/* bits 31:24, Max Ports - max value is 255 */ +-#define HCS_MAX_PORTS(p) (((p) >> 24) & 0xff) ++#define HCS_SLOTS_MASK GENMASK(7, 0) ++/* bits 18:8 - Number of Interrupters, max values is 1024 */ ++#define HCS_MAX_INTRS GENMASK(18, 8) ++/* bits 23:19 - Rsvd */ ++/* bits 31:24 - Max Ports, max values is 255 */ ++#define HCS_MAX_PORTS GENMASK(31, 24) + + /* HCSPARAMS2 - hcs_params2 - bitmasks */ + /* +@@ -33,24 +34,25 @@ + * Note: 1 Frame = 8 Microframes + * xHCI specification section 5.3.4. + */ +-#define HCS_IST_VALUE(p) ((p) & 0x7) ++#define HCS_IST_VALUE GENMASK(2, 0) + #define HCS_IST_UNIT BIT(3) + /* bits 7:4 - Event Ring Segment Table Max, 2^(n) */ +-#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) ++#define HCS_ERST_MAX GENMASK(7, 4) + /* bits 20:8 - Rsvd */ + /* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */ +-#define HCS_MAX_SP_HI(p) (((p) >> 21) & 0x1f) ++#define HCS_MAX_SP_HI GENMASK(25, 21) + /* bit 26 - Scratchpad restore, for save/restore HW state */ + /* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */ +-#define HCS_MAX_SP_LO(p) (((p) >> 27) & 0x1f) +-#define HCS_MAX_SCRATCHPAD(p) (HCS_MAX_SP_HI(p) << 5 | HCS_MAX_SP_LO(p)) ++#define HCS_MAX_SP_LO GENMASK(31, 27) ++#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \ ++ FIELD_GET(HCS_MAX_SP_LO, (p))) + + /* HCSPARAMS3 - hcs_params3 - bitmasks */ + /* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */ +-#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) ++#define HCS_U1_LATENCY GENMASK(7, 0) + /* bits 15:8 - Rsvd */ + /* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */ +-#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) ++#define HCS_U2_LATENCY GENMASK(31, 16) + + /* HCCPARAMS1 - hcc_params - bitmasks */ + /* bit 0 - 64-bit Addressing Capability */ +@@ -77,19 +79,20 @@ + /* bit 11 - Contiguous Frame ID Capability */ + #define HCC_CFC BIT(11) + /* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */ +-#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) ++#define HCC_MAX_PSA GENMASK(15, 12) ++#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1)) + /* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */ +-#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff) ++#define HCC_EXT_CAPS GENMASK(31, 16) + + /* DBOFF - db_off - bitmasks */ + /* bits 1:0 - Rsvd */ + /* bits 31:2 - Doorbell Array Offset */ +-#define DBOFF_MASK (0xfffffffc) ++#define DBOFF_MASK GENMASK(31, 2) + + /* RTSOFF - run_regs_off - bitmasks */ + /* bits 4:0 - Rsvd */ + /* bits 31:5 - Runtime Register Space Offse */ +-#define RTSOFF_MASK (~0x1f) ++#define RTSOFF_MASK GENMASK(31, 5) + + /* HCCPARAMS2 - hcc_params2 - bitmasks */ + /* bit 0 - U3 Entry Capability */ +diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c +--- a/drivers/usb/host/xhci-dbgtty.c ++++ b/drivers/usb/host/xhci-dbgtty.c +@@ -665,11 +665,11 @@ int dbc_tty_init(void) + + void dbc_tty_exit(void) + { +- if (dbc_tty_driver) { +- tty_unregister_driver(dbc_tty_driver); +- tty_driver_kref_put(dbc_tty_driver); +- dbc_tty_driver = NULL; +- } ++ if (IS_ERR_OR_NULL(dbc_tty_driver)) ++ return; + ++ tty_unregister_driver(dbc_tty_driver); ++ tty_driver_kref_put(dbc_tty_driver); + idr_destroy(&dbc_tty_minors); ++ dbc_tty_driver = NULL; + } +diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c +--- a/drivers/usb/host/xhci-debugfs.c ++++ b/drivers/usb/host/xhci-debugfs.c +@@ -9,6 +9,7 @@ + + #include + #include ++#include + + #include "xhci.h" + #include "xhci-debugfs.h" +@@ -791,7 +792,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci) + xhci->debugfs_root, "reg-cap"); + + xhci_debugfs_regset(xhci, +- HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)), ++ FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)), + xhci_op_regs, ARRAY_SIZE(xhci_op_regs), + xhci->debugfs_root, "reg-op"); + +diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c +--- a/drivers/usb/host/xhci-histb.c ++++ b/drivers/usb/host/xhci-histb.c +@@ -276,7 +276,7 @@ static int xhci_histb_probe(struct platform_device *pdev) + if (ret) + goto put_usb3_hcd; + +- if (HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); +diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c +--- a/drivers/usb/host/xhci-hub.c ++++ b/drivers/usb/host/xhci-hub.c +@@ -115,8 +115,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, + + if ((xhci->quirks & XHCI_LPM_SUPPORT)) { + reg = readl(&xhci->cap_regs->hcs_params3); +- ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg); +- ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg)); ++ ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg); ++ ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg)); + } + + if (wLength < le16_to_cpu(bos->wTotalLength)) +@@ -1295,7 +1295,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + } + /* In spec software should not attempt to suspend + * a port unless the port reports that it is in the +- * enabled (PED = ‘1’,PLS < ‘3’) state. ++ * enabled (PED = '1',PLS < '3') state. + */ + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) || +diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1001,7 +1002,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, + for (i = 0; i < 31; i++) { + dev->eps[i].ep_index = i; + dev->eps[i].vdev = dev; +- dev->eps[i].xhci = xhci; + INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); + INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); + } +@@ -1493,6 +1493,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, + return -ENOMEM; + + virt_dev->eps[ep_index].skip = false; ++ virt_dev->eps[ep_index].next_uframe = -1; + ep_ring = virt_dev->eps[ep_index].new_ring; + xhci_ring_init(xhci, ep_ring); + +@@ -2083,7 +2084,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, + addr, port_offset, port_count, major_revision); + /* Port count includes the current port offset */ + if (port_offset == 0 || (port_offset + port_count - 1) > num_ports) +- /* WTF? "Valid values are ‘1’ to MaxPorts" */ ++ /* WTF? "Valid values are '1' to MaxPorts" */ + return; + + port_cap = &xhci->port_caps[xhci->num_port_caps++]; +@@ -2300,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) + if (!segs) + segs = ERST_DEFAULT_SEGS; + +- max_segs = BIT(HCS_ERST_MAX(xhci->hcs_params2)); ++ max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); +diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c +--- a/drivers/usb/host/xhci-mtk.c ++++ b/drivers/usb/host/xhci-mtk.c +@@ -468,7 +468,7 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) + * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream, + * and it's 3 when support it. + */ +- if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4) ++ if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4) + xhci->quirks |= XHCI_BROKEN_STREAMS; + } + +@@ -650,7 +650,7 @@ static int xhci_mtk_probe(struct platform_device *pdev) + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && ++ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + +diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c +--- a/drivers/usb/host/xhci-pci.c ++++ b/drivers/usb/host/xhci-pci.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -662,7 +663,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && ++ GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + usb3_hcd->can_do_streams = 1; + + /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ +diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c +--- a/drivers/usb/host/xhci-plat.c ++++ b/drivers/usb/host/xhci-plat.c +@@ -340,7 +340,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && ++ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -57,6 +57,8 @@ + #include + #include + #include ++#include ++ + #include "xhci.h" + #include "xhci-trace.h" + +@@ -561,8 +563,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + * pointer command pending because the device can choose to start any + * stream once the endpoint is on the HW schedule. + */ +- if ((ep_state & EP_STOP_CMD_PENDING) || (ep_state & SET_DEQ_PENDING) || +- (ep_state & EP_HALTED) || (ep_state & EP_CLEARING_TT)) ++ if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED | ++ EP_CLEARING_TT | EP_DROP_PENDING)) + return; + + trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); +@@ -573,9 +575,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + } + + /* Ring the doorbell for any rings with pending URBs */ +-static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, +- unsigned int slot_id, +- unsigned int ep_index) ++void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, unsigned int slot_id, ++ unsigned int ep_index) + { + unsigned int stream_id; + struct xhci_virt_ep *ep; +@@ -598,13 +599,6 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, + } + } + +-void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, +- unsigned int slot_id, +- unsigned int ep_index) +-{ +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); +-} +- + static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index) +@@ -686,112 +680,100 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, + return le64_to_cpu(ep_ctx->deq); + } + +-static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, +- unsigned int slot_id, unsigned int ep_index, +- unsigned int stream_id, struct xhci_td *td) ++/* ++ * Move the endpoint dequeue pointer to the next queued TD on ring->td_list or ++ * to enqueue if no TDs are queued (empty ring) ++ * All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list ++ * before calling this function ++ */ ++static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci, ++ struct xhci_virt_ep *ep, ++ unsigned int stream_id) + { +- struct xhci_virt_device *dev = xhci->devs[slot_id]; +- struct xhci_virt_ep *ep = &dev->eps[ep_index]; +- struct xhci_ring *ep_ring; + struct xhci_command *cmd; +- struct xhci_segment *new_seg; +- union xhci_trb *new_deq; +- int new_cycle; ++ struct xhci_ring *ring; ++ struct xhci_td *td; + dma_addr_t addr; +- u64 hw_dequeue; +- bool hw_dequeue_found = false; +- bool td_last_trb_found = false; ++ int new_cycle; + u32 trb_sct = 0; +- int ret; ++ int ret = 0; + +- ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, +- ep_index, stream_id); +- if (!ep_ring) { ++ ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); ++ if (!ring) { + xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n", + stream_id); + return -ENODEV; + } + +- hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK; +- new_seg = ep_ring->deq_seg; +- new_deq = ep_ring->dequeue; +- new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE; +- +- /* +- * Walk the ring until both the next TRB and hw_dequeue are found (don't +- * move hw_dequeue back if it went forward due to a HW bug). Cycle state +- * is loaded from a known good TRB, track later toggles to maintain it. +- */ +- do { +- if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq) +- == (dma_addr_t)hw_dequeue) { +- hw_dequeue_found = true; +- if (td_last_trb_found) +- break; +- } +- if (new_deq == td->end_trb) +- td_last_trb_found = true; +- +- if (td_last_trb_found && trb_is_link(new_deq) && +- link_trb_toggles_cycle(new_deq)) +- new_cycle ^= 0x1; +- +- next_trb(&new_seg, &new_deq); +- +- /* Search wrapped around, bail out */ +- if (new_deq == ep->ring->dequeue) { +- xhci_err(xhci, "Error: Failed finding new dequeue state\n"); +- return -EINVAL; +- } +- +- } while (!hw_dequeue_found || !td_last_trb_found); +- +- /* Don't update the ring cycle state for the producer (us). */ +- addr = xhci_trb_virt_to_dma(new_seg, new_deq); +- if (addr == 0) { +- xhci_warn(xhci, "Can't find dma of new dequeue ptr\n"); +- xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq); +- return -EINVAL; +- } +- + if ((ep->ep_state & SET_DEQ_PENDING)) { +- xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n", +- &addr); ++ xhci_warn(xhci, "Set TR Deq already pending\n"); + return -EBUSY; + } + + /* This function gets called from contexts where it cannot sleep */ + cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!cmd) { +- xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr); ++ xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n"); + return -ENOMEM; + } + ++ /* ++ * Move dequeue to the beginning of next td, or to enqueue if ring is ++ * empty. Avoid moving dequeue to a link trb (empty ring) as it causes ++ * issues on some hosts. In that case advance the enqueue to next segment ++ * before moving dequeue to it ++ */ ++ ++ if (list_empty(&ring->td_list)) { ++ if (trb_is_link(ring->enqueue)) ++ inc_enq_past_link(xhci, ring, 0); ++ ep->queued_deq_seg = ring->enq_seg; ++ ep->queued_deq_ptr = ring->enqueue; ++ new_cycle = ring->cycle_state; ++ } else { ++ td = list_first_entry(&ring->td_list, struct xhci_td, td_list); ++ ep->queued_deq_seg = td->start_seg; ++ ep->queued_deq_ptr = td->start_trb; ++ new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE; ++ } ++ ++ addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr); ++ if (addr == 0) { ++ xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n", ++ ep->queued_deq_seg, ep->queued_deq_ptr); ++ ret = -EINVAL; ++ goto err_out; ++ } ++ + if (stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + ret = queue_command(xhci, cmd, + lower_32_bits(addr) | trb_sct | new_cycle, + upper_32_bits(addr), +- STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) | +- EP_INDEX_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false); +- if (ret < 0) { +- xhci_free_command(xhci, cmd); +- return ret; +- } +- ep->queued_deq_seg = new_seg; +- ep->queued_deq_ptr = new_deq; ++ STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) | ++ EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false); ++ if (ret < 0) ++ goto err_out; + + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle); + +- /* Stop the TD queueing code from ringing the doorbell until +- * this command completes. The HC won't set the dequeue pointer +- * if the ring is running, and ringing the doorbell starts the +- * ring running. ++ /* ++ * Stop the TD queueing code from ringing the doorbell until this ++ * command completes. The HC won't set the dequeue pointer if the ring ++ * is running, and ringing the doorbell starts the ring. + */ + ep->ep_state |= SET_DEQ_PENDING; + xhci_ring_cmd_db(xhci); ++ + return 0; ++ ++err_out: ++ xhci_free_command(xhci, cmd); ++ ep->queued_deq_seg = NULL; ++ ep->queued_deq_ptr = NULL; ++ ++ return ret; + } + + /* flip_cycle means flip the cycle bit of all but the first and last TRB. +@@ -950,7 +932,7 @@ static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xh + } + + /* Complete the cancelled URBs we unlinked from td_list. */ +-static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) ++static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { + struct xhci_ring *ring; + struct xhci_td *td, *tmp_td; +@@ -958,17 +940,17 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { + +- ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); ++ ring = xhci_urb_to_transfer_ring(xhci, td->urb); + + if (td->cancel_status == TD_CLEARED) { +- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", ++ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); +- xhci_td_cleanup(ep->xhci, td, ring, td->status); ++ xhci_td_cleanup(xhci, td, ring, td->status); + } else { +- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", ++ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } +- if (ep->xhci->xhc_state & XHCI_STATE_DYING) ++ if (xhci->xhc_state & XHCI_STATE_DYING) + return; + } + } +@@ -1002,15 +984,19 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + struct xhci_td *td, + enum xhci_ep_reset_type reset_type) + { ++ struct xhci_port *rhub_port = ep->vdev->rhub_port; + unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* +- * Avoid resetting endpoint if link is inactive. Can cause host hang. +- * Device will be reset soon to recover the link so don't do anything ++ * Avoid resetting endpoint if link is inactive or device disonnected. ++ * Can cause host hang. ++ * Device will be reset to recover an inactive link, so don't do anything + */ +- if (ep->vdev->flags & VDEV_PORT_ERROR) ++ if (rhub_port->link_inactive || !rhub_port->connected) { ++ ep->ep_state |= EP_DROP_PENDING; + return -ENODEV; ++ } + + /* add td to cancelled list and let reset ep handler take care of it */ + if (reset_type == EP_HARD_RESET) { +@@ -1047,15 +1033,13 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + * only call this when ring is not in a running state + */ + +-static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) ++static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { +- struct xhci_hcd *xhci; + struct xhci_td *td = NULL; + struct xhci_td *tmp_td = NULL; + struct xhci_td *cached_td = NULL; + struct xhci_ring *ring; + u64 hw_deq; +- unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* +@@ -1065,8 +1049,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + if (ep->ep_state & SET_DEQ_PENDING) + return 0; + +- xhci = ep->xhci; +- + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p", +@@ -1080,6 +1062,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + td->urb, td->urb->stream_id); + continue; + } ++ ++ /* device disconnected or link error, ep will be dropped */ ++ if (ep->ep_state & EP_DROP_PENDING) { ++ td->cancel_status = TD_CLEARED; ++ continue; ++ } ++ + /* + * If a ring stopped on the TD we need to cancel then we have to + * move the xHC endpoint ring dequeue pointer past this TD. +@@ -1131,9 +1120,8 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + if (!cached_td) + return 0; + +- err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, +- cached_td->urb->stream_id, +- cached_td); ++ err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id); ++ + if (err) { + /* Failed to move past cached td, just set cached TDs to no-op */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { +@@ -1161,23 +1149,23 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + * + * Call under xhci->lock on a stopped endpoint. + */ +-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep) ++void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { +- xhci_invalidate_cancelled_tds(ep); +- xhci_giveback_invalidated_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + } + + /* + * Returns the TD the endpoint ring halted on. + * Only call for non-running rings without streams. + */ +-static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) ++static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { + struct xhci_td *td; + u64 hw_deq; + + if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ +- hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); ++ hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0); + hw_deq &= TR_DEQ_PTR_MASK; + td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); + if (trb_in_td(td, hw_deq)) +@@ -1252,7 +1240,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + reset_type = EP_SOFT_RESET; + } else { + reset_type = EP_HARD_RESET; +- td = find_halted_td(ep); ++ td = find_halted_td(xhci, ep); + if (td) + td->status = -EPROTO; + } +@@ -1310,13 +1298,17 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + } + } + ++ /* link is inactive or disconnected, ep is not running and shouldn't be restarted */ ++ if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected) ++ ep->ep_state |= EP_DROP_PENDING; ++ + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + ep->ep_state &= ~EP_STOP_CMD_PENDING; + + /* Otherwise ring the doorbell(s) to restart queued transfers */ +- xhci_giveback_invalidated_tds(ep); +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_giveback_invalidated_tds(xhci, ep); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) +@@ -1537,14 +1529,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + /* HW cached TDs cleared from cache, give them back */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { +- ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); ++ ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb); + if (td->cancel_status == TD_CLEARING_CACHE) { + td->cancel_status = TD_CLEARED; +- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", ++ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); +- xhci_td_cleanup(ep->xhci, td, ep_ring, td->status); ++ xhci_td_cleanup(xhci, td, ep_ring, td->status); + } else { +- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", ++ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } + } +@@ -1555,17 +1547,17 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + + /* Check for deferred or newly cancelled TDs */ + if (!list_empty(&ep->cancelled_td_list)) { +- xhci_dbg(ep->xhci, "%s: Pending TDs to clear, continuing with invalidation\n", ++ xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n", + __func__); +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + /* Try to restart the endpoint if all is done */ +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + /* Start giving back any TDs invalidated above */ +- xhci_giveback_invalidated_tds(ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + } else { + /* Restart any rings with pending URBs */ +- xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__); +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + } + +@@ -1591,16 +1583,16 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, + "Ignoring reset ep completion code of %u", cmd_comp_code); + + /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + + /* Clear our internal halted state */ + ep->ep_state &= ~EP_HALTED; + +- xhci_giveback_invalidated_tds(ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + + /* if this was a soft reset, then restart */ + if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command, +@@ -2008,13 +2000,15 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci) + static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + { + struct xhci_virt_device *vdev = NULL; +- struct usb_hcd *hcd; +- u32 port_id; +- u32 portsc, cmd_reg; +- unsigned int hcd_portnum; + struct xhci_bus_state *bus_state; +- bool bogus_port_status = false; + struct xhci_port *port; ++ struct usb_hcd *hcd; ++ bool bogus_port_status = false; ++ unsigned int hcd_portnum; ++ u32 cmd_reg; ++ u32 port_id; ++ u32 portsc; ++ u32 pls; + + /* Port status change events always have a successful completion code */ + if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) +@@ -2051,6 +2045,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + bus_state = &port->rhub->bus_state; + hcd_portnum = port->hcd_portnum; + portsc = xhci_portsc_readl(port); ++ pls = portsc & PORT_PLS_MASK; + + xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n", + hcd->self.busnum, hcd_portnum + 1, port_id, portsc); +@@ -2062,11 +2057,13 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + usb_hcd_resume_root_hub(hcd); + } + +- if (vdev && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) { +- if (!(portsc & PORT_RESET)) +- vdev->flags |= VDEV_PORT_ERROR; +- } else if (vdev && portsc & PORT_RC) { +- vdev->flags &= ~VDEV_PORT_ERROR; ++ /* ++ * Tag broken links to avoid retries while hub driver sorts it out. ++ * Link status is not relible while port is in reset. ++ */ ++ if (!(portsc & PORT_RESET)) { ++ port->link_inactive = (pls == XDEV_INACTIVE); ++ port->connected = !!(portsc & PORT_CONNECT); + } + + if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { +@@ -3978,88 +3975,102 @@ static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci, + /* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */ + static int xhci_ist_microframes(struct xhci_hcd *xhci) + { +- int ist = HCS_IST_VALUE(xhci->hcs_params2); ++ int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2); + + if (xhci->hcs_params2 & HCS_IST_UNIT) + ist *= 8; + return ist; + } + +-/* +- * Calculates Frame ID field of the isochronous TRB identifies the +- * target frame that the Interval associated with this Isochronous +- * Transfer Descriptor will start on. Refer to 4.11.2.5 in 1.1 spec. +- * +- * Returns actual frame id on success, negative value on error. +- */ +-static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci, +- struct urb *urb, int index) ++ ++static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb, ++ struct xhci_virt_ep *ep, int i) + { +- int start_frame, ist, ret = 0; +- int start_frame_id, end_frame_id, current_frame_id; ++ if (urb->transfer_flags & URB_ISO_ASAP) ++ return false; + +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) +- start_frame = urb->start_frame + index * urb->interval; +- else +- start_frame = (urb->start_frame + index * urb->interval) >> 3; ++ if (xhci->hcc_params & HCC_CFC) ++ return true; + ++ /* set frame id for first TD of first URB in stream */ ++ if (ep->next_uframe == -1 && i == 0) ++ return true; ++ ++ return false; ++} ++ ++/* ++ * Check if frame is in the valid frame window, including start and end. ++ * If start > end then assume window wrapped around at a limit the frame ++ * value won't exceed. ++ */ ++static bool xhci_frame_in_range(u32 frame, u32 start, u32 end) ++{ ++ /* frame window end wrapped around */ ++ if (start > end) ++ return frame >= start || frame <= end; ++ ++ return frame >= start && frame <= end; ++} ++ ++/* ++ * Set the urb->start_frame of the URB. ++ * ++ * Returns microframe index of first TD ++ */ ++static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb, ++ struct xhci_virt_ep *ep) ++{ ++ u32 curr_frame, start_uframe; ++ u32 urb_start, urb_end; ++ u32 win_start, win_end; ++ bool frame_unit; ++ int uinterval; ++ u32 mfindex; ++ int ist; ++ ++ /* check if urb uses frame units instead of microframes */ ++ frame_unit = (urb->dev->speed == USB_SPEED_FULL || ++ urb->dev->speed == USB_SPEED_LOW); ++ ++ uinterval = urb->interval; ++ if (frame_unit) ++ uinterval *= 8; ++ ++ /* get current microframe index and isoc scheduling threshold */ ++ mfindex = readl(&xhci->run_regs->microframe_index); + ist = xhci_ist_microframes(xhci); + +- /* Software shall not schedule an Isoch TD with a Frame ID value that +- * is less than the Start Frame ID or greater than the End Frame ID, +- * where: +- * +- * End Frame ID = (Current MFINDEX register value + 895 ms.) MOD 2048 +- * Start Frame ID = (Current MFINDEX register value + IST + 1) MOD 2048 +- * +- * Both the End Frame ID and Start Frame ID values are calculated +- * in microframes. When software determines the valid Frame ID value; +- * The End Frame ID value should be rounded down to the nearest Frame +- * boundary, and the Start Frame ID value should be rounded up to the +- * nearest Frame boundary. +- */ +- current_frame_id = readl(&xhci->run_regs->microframe_index); +- start_frame_id = roundup(current_frame_id + ist + 1, 8); +- end_frame_id = rounddown(current_frame_id + 895 * 8, 8); ++ /* calculate valid frame window, in frame units, see xhci 4.11.2.5 */ ++ curr_frame = MFINDEX_TO_FRAME(mfindex); ++ win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES; ++ win_end = (curr_frame + 895) % MAX_FRAMES; + +- start_frame &= 0x7ff; +- start_frame_id = (start_frame_id >> 3) & 0x7ff; +- end_frame_id = (end_frame_id >> 3) & 0x7ff; +- +- if (start_frame_id < end_frame_id) { +- if (start_frame > end_frame_id || +- start_frame < start_frame_id) +- ret = -EINVAL; +- } else if (start_frame_id > end_frame_id) { +- if ((start_frame > end_frame_id && +- start_frame < start_frame_id)) +- ret = -EINVAL; ++ /* Is this the first URB starting the whole isoc data flow? */ ++ if (ep->next_uframe < 0) { ++ /* align first URB to next interval boundary, or at last to full frame */ ++ start_uframe = mfindex + ist + XHCI_CFC_DELAY; ++ start_uframe = roundup(start_uframe, 8); ++ start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES; + } else { +- ret = -EINVAL; +- } ++ /* URB is mid stream and expected to handle the next frame */ ++ start_uframe = ep->next_uframe; ++ urb_start = start_uframe / 8; ++ urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8; ++ urb_end %= MAX_FRAMES; + +- if (index == 0) { +- if (ret == -EINVAL || start_frame == start_frame_id) { +- start_frame = start_frame_id + 1; +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) +- urb->start_frame = start_frame; +- else +- urb->start_frame = start_frame << 3; +- ret = 0; +- } +- } ++ if (!xhci_frame_in_range(urb_start, win_start, win_end)) ++ xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n", ++ urb, urb_start, win_start, win_end); + +- if (ret) { +- xhci_warn(xhci, "Frame ID %d (reg %d, index %d) beyond range (%d, %d)\n", +- start_frame, current_frame_id, index, +- start_frame_id, end_frame_id); +- xhci_warn(xhci, "Ignore frame ID field, use SIA bit instead\n"); +- return ret; ++ if (!xhci_frame_in_range(urb_end, win_start, win_end)) ++ xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n", ++ urb, urb_start, win_start, win_end); + } ++ /* set urb->start_frame */ ++ urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe; + +- return start_frame; ++ return start_uframe; + } + + /* Check if we should generate event interrupt for a TD in an isoc URB */ +@@ -4099,7 +4110,8 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + int i, j; + bool more_trbs_coming; + struct xhci_virt_ep *xep; +- int frame_id; ++ int uinterval = urb->interval; ++ int start_uframe; + + xep = &xhci->devs[slot_id]->eps[ep_index]; + ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; +@@ -4115,6 +4127,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + start_cycle = ep_ring->cycle_state; + + urb_priv = urb->hcpriv; ++ ++ if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW) ++ uinterval = urb->interval * 8; ++ ++ start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep); ++ + /* Queue the TRBs for each TD, even if they are zero-length */ + for (i = 0; i < num_tds; i++) { + unsigned int total_pkt_count, max_pkt; +@@ -4146,14 +4164,16 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + goto cleanup; + } + td = &urb_priv->td[i]; +- /* use SIA as default, if frame id is used overwrite it */ +- sia_frame_id = TRB_SIA; +- if (!(urb->transfer_flags & URB_ISO_ASAP) && +- (xhci->hcc_params & HCC_CFC)) { +- frame_id = xhci_get_isoc_frame_id(xhci, urb, i); +- if (frame_id >= 0) +- sia_frame_id = TRB_FRAME_ID(frame_id); ++ ++ ++ /* Choose SIA or frame ID based scheduling for this TD */ ++ if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) { ++ sia_frame_id = (start_uframe + i * uinterval) / 8; ++ sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES); ++ } else { ++ sia_frame_id = TRB_SIA; + } ++ + /* + * Set isoc specific data for the first TRB in a TD. + * Prevent HW from getting the TRBs by keeping the cycle state +@@ -4232,9 +4252,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + } + } + +- /* store the next frame id */ +- if (xhci->hcc_params & HCC_CFC) +- xep->next_frame_id = urb->start_frame + num_tds * urb->interval; ++ xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES; + + if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) { + if (xhci->quirks & XHCI_AMD_PLL_FIX) +@@ -4281,11 +4299,9 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + struct xhci_virt_device *xdev; + struct xhci_ring *ep_ring; + struct xhci_ep_ctx *ep_ctx; +- int start_frame; ++ struct xhci_virt_ep *xep; + int num_tds, num_trbs, i; + int ret; +- struct xhci_virt_ep *xep; +- int ist; + + xdev = xhci->devs[slot_id]; + xep = &xhci->devs[slot_id]->eps[ep_index]; +@@ -4311,38 +4327,12 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + */ + check_interval(urb, ep_ctx); + +- /* Calculate the start frame and put it in urb->start_frame. */ +- if ((xhci->hcc_params & HCC_CFC) && !list_empty(&ep_ring->td_list)) { +- if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) { +- urb->start_frame = xep->next_frame_id; +- goto skip_start_over; +- } +- } +- +- start_frame = readl(&xhci->run_regs->microframe_index); +- start_frame &= 0x3fff; + /* +- * Round up to the next frame and consider the time before trb really +- * gets scheduled by hardare. ++ * Check if this starts the isoc data flow. Relies on hw setting ep ctx ++ * state after doorbell ring. Consider adding list_empty(td_list) check + */ +- ist = xhci_ist_microframes(xhci); +- start_frame += ist + XHCI_CFC_DELAY; +- start_frame = roundup(start_frame, 8); +- +- /* +- * Round up to the next ESIT (Endpoint Service Interval Time) if ESIT +- * is greate than 8 microframes. +- */ +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) { +- start_frame = roundup(start_frame, urb->interval << 3); +- urb->start_frame = start_frame >> 3; +- } else { +- start_frame = roundup(start_frame, urb->interval); +- urb->start_frame = start_frame; +- } +- +-skip_start_over: ++ if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) ++ xep->next_uframe = -1; + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); + } +diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c +--- a/drivers/usb/host/xhci-tegra.c ++++ b/drivers/usb/host/xhci-tegra.c +@@ -6,6 +6,7 @@ + * Copyright (C) 2014 Google, Inc. + */ + ++#include + #include + #include + #include +@@ -993,7 +994,7 @@ static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) + u32 value; + + cap_regs = tegra->regs; +- op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase)); ++ op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)), + + ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); + +@@ -1895,7 +1896,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) + goto remove_usb2; + } + +- if (HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); +diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c +--- a/drivers/usb/host/xhci.c ++++ b/drivers/usb/host/xhci.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1664,7 +1665,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag + goto free_priv; + } + +- if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) { ++ if (xhci->devs[slot_id]->rhub_port->link_inactive) { + xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n"); + ret = -ENODEV; + goto free_priv; +@@ -1852,11 +1853,11 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) + } + + /* In this case no commands are pending but the endpoint is stopped */ +- if (ep->ep_state & EP_CLEARING_TT) { ++ if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) { + /* and cancelled TDs can be given back right away */ + xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n", + urb->dev->slot_id, ep_index, ep->ep_state); +- xhci_process_cancelled_tds(ep); ++ xhci_process_cancelled_tds(xhci, ep); + } else { + /* Otherwise, queue a new Stop Endpoint command */ + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); +@@ -3507,7 +3508,7 @@ static void xhci_calculate_streams_entries(struct xhci_hcd *xhci, + * level page entries), but that's an optional feature for xHCI host + * controllers. xHCs must support at least 4 stream IDs. + */ +- max_streams = HCC_MAX_PSA(xhci->hcc_params); ++ max_streams = GET_MAX_PSA_SIZE(xhci->hcc_params); + if (*num_stream_ctxs > max_streams) { + xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n", + max_streams); +@@ -3637,7 +3638,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + + /* MaxPSASize value 0 (2 streams) means streams are not supported */ + if ((xhci->quirks & XHCI_BROKEN_STREAMS) || +- HCC_MAX_PSA(xhci->hcc_params) < 4) { ++ GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) { + xhci_dbg(xhci, "xHCI controller does not support streams.\n"); + return -ENOSYS; + } +@@ -4035,7 +4036,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + xhci_get_slot_state(xhci, virt_dev->out_ctx)); + xhci_dbg(xhci, "Not freeing device rings.\n"); + /* Don't treat this as an error. May change my mind later. */ +- virt_dev->flags = 0; + ret = 0; + goto command_cleanup; + case COMP_SUCCESS: +@@ -4087,10 +4087,12 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + } + /* If necessary, update the number of active TTs on this root port */ + xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); +- virt_dev->flags = 0; + ret = 0; + + command_cleanup: ++ for (i = 0; i < EP_CTX_PER_DEV; i++) ++ virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING; ++ + xhci_free_command(xhci, reset_device_cmd); + return ret; + } +@@ -4607,7 +4609,7 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, + int besl_device = 0; + u32 field; + +- u2del = HCS_U2_LATENCY(xhci->hcs_params3); ++ u2del = FIELD_GET(HCS_U2_LATENCY, xhci->hcs_params3); + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + + if (field & USB_BESL_SUPPORT) { +@@ -5459,26 +5461,28 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + xhci_warn(xhci, "Host controller not accessible, removed?\n"); + return -ENODEV; + } +- xhci->op_regs = hcd->regs + HC_LENGTH(hc_capbase); ++ xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase); ++ + xhci->run_regs = hcd->regs + + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + hcs_params1 = readl(&xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); +- xhci->hci_version = HC_VERSION(hc_capbase); ++ xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase); + xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); + if (xhci->hci_version > 0x100) + xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); + + xhci->dma_mask_bits = 64; +- xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS); +- xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS); ++ xhci->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS); ++ xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS); ++ + /* xhci-plat or xhci-pci might have set max_interrupters already */ + if (!xhci->max_interrupters) +- xhci->max_interrupters = min(HCS_MAX_INTRS(hcs_params1), MAX_HC_INTRS); +- else if (xhci->max_interrupters > HCS_MAX_INTRS(hcs_params1)) +- xhci->max_interrupters = HCS_MAX_INTRS(hcs_params1); ++ xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS); ++ else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1)) ++ xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1); + + xhci->quirks |= quirks; + +@@ -5519,7 +5523,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + * DMA_BIT_MASK(32)) in this xhci_gen_setup(). + */ + if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) +- xhci->hcc_params &= ~BIT(0); ++ xhci->hcc_params &= ~HCC_64BIT_ADDR; + + /* + * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports +diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h +--- a/drivers/usb/host/xhci.h ++++ b/drivers/usb/host/xhci.h +@@ -290,6 +290,11 @@ struct xhci_run_regs { + struct xhci_intr_reg ir_set[1024]; + }; + ++/* Bits [13:3] of the microframe index equals the 1ms frame index */ ++#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff) ++#define MAX_FRAMES 2048 ++#define MAX_UFRAMES (MAX_FRAMES * 8) ++ + /** + * struct doorbell_array + * +@@ -677,9 +682,9 @@ struct xhci_virt_ep { + #define EP_SOFT_CLEAR_TOGGLE BIT(7) + /* usb_hub_clear_tt_buffer is in progress */ + #define EP_CLEARING_TT BIT(8) ++#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */ + /* ---- Related to URB cancellation ---- */ + struct list_head cancelled_td_list; +- struct xhci_hcd *xhci; + /* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue + * command. We'll need to update the ring's dequeue segment and dequeue + * pointer after the command completes. +@@ -699,7 +704,7 @@ struct xhci_virt_ep { + struct list_head bw_endpoint_list; + unsigned long stop_time; + /* Isoch Frame ID checking storage */ +- int next_frame_id; ++ int next_uframe; + /* Use new Isoch TRB layout needed for extended TBC support */ + bool use_extended_tbc; + /* set if this endpoint is controlled via sideband access*/ +@@ -753,14 +758,6 @@ struct xhci_virt_device { + struct xhci_port *rhub_port; + struct xhci_interval_bw_table *bw_table; + struct xhci_tt_bw_info *tt_info; +- /* +- * flags for state tracking based on events and issued commands. +- * Software can not rely on states from output contexts because of +- * latency between events and xHC updating output context values. +- * See xhci 1.1 section 4.8.3 for more details +- */ +- unsigned long flags; +-#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */ + + /* The current max exit latency for the enabled USB3 link states. */ + u16 current_mel; +@@ -1480,6 +1477,8 @@ struct xhci_port { + int hcd_portnum; + struct xhci_hub *rhub; + struct xhci_port_cap *port_cap; ++ unsigned int link_inactive:1; ++ unsigned int connected:1; + unsigned int lpm_incapable:1; + unsigned long resume_timestamp; + bool rexit_active; +@@ -1961,7 +1960,7 @@ unsigned int count_trbs(u64 addr, u64 len); + unsigned int xhci_num_trbs_free(struct xhci_ring *ring); + int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + int suspend, gfp_t gfp_flags); +-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); ++void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep); + void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + bool clear_ehb); +diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c +--- a/drivers/usb/misc/cypress_cy7c63.c ++++ b/drivers/usb/misc/cypress_cy7c63.c +@@ -89,8 +89,11 @@ static int vendor_command(struct cypress *dev, unsigned char request, + address, data, iobuf, CYPRESS_MAX_REQSIZE, + USB_CTRL_GET_TIMEOUT); + /* we must not process garbage */ +- if (retval < 2) ++ if (retval < 2) { ++ if (retval >= 0) ++ retval = -EIO; + goto err_buf; ++ } + + /* store returned data (more READs to be added) */ + switch (request) { +@@ -175,8 +178,9 @@ static ssize_t read_port(struct device *dev, struct device_attribute *attr, + dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); + + result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); +- + dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); ++ if (result < 0) ++ return result; + + return sprintf(buf, "%d", cyp->port[port_num]); + } +diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c +--- a/drivers/usb/misc/uss720.c ++++ b/drivers/usb/misc/uss720.c +@@ -223,6 +223,7 @@ static int get_1284_register(struct parport *pp, unsigned char reg, unsigned cha + } + printk(KERN_WARNING "get_1284_register timeout\n"); + kill_all_async_requests_priv(priv); ++ kref_put(&rq->ref_count, destroy_async); + return -EIO; + } + +diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c +--- a/drivers/usb/mtu3/mtu3_host.c ++++ b/drivers/usb/mtu3/mtu3_host.c +@@ -46,6 +46,14 @@ + #define WC1_IS_P_95 BIT(12) + #define WC1_IS_EN_P0_95 BIT(6) + ++/* mt8196 */ ++#define PERI_WK_CTRL0_8196 0x08 ++#define WC0_IS_EN_P0_96 BIT(0) ++#define WC0_IS_EN_P1_96 BIT(7) ++ ++#define PERI_WK_CTRL1_8196 0x10 ++#define WC1_IS_EN_P2_96 BIT(0) ++ + /* mt2712 etc */ + #define PERI_SSUSB_SPM_CTRL 0x0 + #define SSC_IP_SLEEP_EN BIT(4) +@@ -59,6 +67,9 @@ enum ssusb_uwk_vers { + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ ++ SSUSB_UWK_V1_7, /* mt8196 IP0 */ ++ SSUSB_UWK_V1_8, /* mt8196 IP1 */ ++ SSUSB_UWK_V1_9, /* mt8196 IP2 */ + }; + + /* +@@ -100,6 +111,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; ++ case SSUSB_UWK_V1_7: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; ++ msk = WC0_IS_EN_P0_96; ++ val = enable ? msk : 0; ++ break; ++ case SSUSB_UWK_V1_8: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; ++ msk = WC0_IS_EN_P1_96; ++ val = enable ? msk : 0; ++ break; ++ case SSUSB_UWK_V1_9: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8196; ++ msk = WC1_IS_EN_P2_96; ++ val = enable ? msk : 0; ++ break; + case SSUSB_UWK_V2: + reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; + msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; +diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c +--- a/drivers/usb/mtu3/mtu3_plat.c ++++ b/drivers/usb/mtu3/mtu3_plat.c +@@ -544,7 +544,8 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) + + ssusb_phy_power_off(ssusb); + clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); +- ssusb_wakeup_set(ssusb, true); ++ if (device_may_wakeup(dev) && (ssusb->is_host || PMSG_IS_AUTO(msg))) ++ ssusb_wakeup_set(ssusb, true); + return 0; + + sleep_err: +diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig +--- a/drivers/usb/musb/Kconfig ++++ b/drivers/usb/musb/Kconfig +@@ -116,7 +116,6 @@ config USB_MUSB_POLARFIRE_SOC + tristate "Microchip PolarFire SoC platforms" + depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on NOP_USB_XCEIV +- select USB_MUSB_DUAL_ROLE + help + Say Y here to enable support for USB on Microchip's PolarFire SoC. + +diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c +--- a/drivers/usb/musb/musb_host.c ++++ b/drivers/usb/musb/musb_host.c +@@ -1774,7 +1774,8 @@ void musb_host_rx(struct musb *musb, u8 epnum) + status = -EPIPE; + + } else if (rx_csr & MUSB_RXCSR_H_ERROR) { +- dev_err(musb->controller, "ep%d RX three-strikes error", epnum); ++ dev_err_ratelimited(musb->controller, ++ "ep%d RX three-strikes error\n", epnum); + + /* + * The three-strikes error could only happen when the USB +@@ -1788,6 +1789,17 @@ void musb_host_rx(struct musb *musb, u8 epnum) + rx_csr &= ~MUSB_RXCSR_H_ERROR; + musb_writew(epio, MUSB_RXCSR, rx_csr); + ++ /* ++ * Unplugging a USB-Ethernet adapter while it is busy can make ++ * the controller keep re-asserting the three-strikes error for ++ * this endpoint before the disconnect is processed. That floods ++ * the log and can wedge the host port until reboot. Drop the ++ * stale pending RX interrupt on platforms that support it (e.g. ++ * AM335x/DSPS) to break the storm; the transfer is still ++ * aborted below via the fault path. ++ */ ++ musb_platform_clear_ep_rxintr(musb, epnum); ++ + } else if (rx_csr & MUSB_RXCSR_DATAERROR) { + + if (USB_ENDPOINT_XFER_ISOC != qh->type) { +diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c +--- a/drivers/usb/serial/digi_acceleport.c ++++ b/drivers/usb/serial/digi_acceleport.c +@@ -32,10 +32,6 @@ + /* so we can be sure to send the full buffer in one urb */ + #define DIGI_OUT_BUF_SIZE 8 + +-/* port input buffer length -- must be >= transfer buffer length - 3 */ +-/* so we can be sure to hold at least one full buffer from one urb */ +-#define DIGI_IN_BUF_SIZE 64 +- + /* retry timeout while sleeping */ + #define DIGI_RETRY_TIMEOUT (HZ/10) + +@@ -176,10 +172,10 @@ + /* Structures */ + + struct digi_serial { +- spinlock_t ds_serial_lock; ++ struct mutex open_mutex; + struct usb_serial_port *ds_oob_port; /* out-of-band port */ + int ds_oob_port_num; /* index of out-of-band port */ +- int ds_device_started; ++ int open_count; + }; + + struct digi_port { +@@ -194,7 +190,6 @@ struct digi_port { + int dp_throttled; + int dp_throttle_restart; + wait_queue_head_t dp_flush_wait; +- wait_queue_head_t dp_close_wait; /* wait queue for close */ + wait_queue_head_t write_wait; + struct usb_serial_port *dp_port; + }; +@@ -227,9 +222,7 @@ static unsigned int digi_chars_in_buffer(struct tty_struct *tty); + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port); + static void digi_close(struct usb_serial_port *port); + static void digi_dtr_rts(struct usb_serial_port *port, int on); +-static int digi_startup_device(struct usb_serial *serial); + static int digi_startup(struct usb_serial *serial); +-static void digi_disconnect(struct usb_serial *serial); + static void digi_release(struct usb_serial *serial); + static int digi_port_probe(struct usb_serial_port *port); + static void digi_port_remove(struct usb_serial_port *port); +@@ -272,7 +265,7 @@ static struct usb_serial_driver digi_acceleport_2_device = { + .dtr_rts = digi_dtr_rts, + .write = digi_write, + .write_room = digi_write_room, +- .write_bulk_callback = digi_write_bulk_callback, ++ .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, +@@ -282,7 +275,6 @@ static struct usb_serial_driver digi_acceleport_2_device = { + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, +- .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +@@ -301,7 +293,7 @@ static struct usb_serial_driver digi_acceleport_4_device = { + .close = digi_close, + .write = digi_write, + .write_room = digi_write_room, +- .write_bulk_callback = digi_write_bulk_callback, ++ .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, +@@ -311,7 +303,6 @@ static struct usb_serial_driver digi_acceleport_4_device = { + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, +- .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +@@ -335,7 +326,6 @@ static struct usb_serial_driver * const serial_drivers[] = { + * interruptible_sleep_on_timeout is deprecated and has been replaced + * with the equivalent code. + */ +- + static long cond_wait_interruptible_timeout_irqrestore( + wait_queue_head_t *q, long timeout, + spinlock_t *lock, unsigned long flags) +@@ -351,6 +341,13 @@ __releases(lock) + return timeout; + } + ++static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ ++ return serial_priv->ds_oob_port; ++} ++ + /* + * Digi Write OOB Command + * +@@ -361,15 +358,14 @@ __releases(lock) + * the interruptible flag is true, or a negative error + * returned by usb_submit_urb. + */ +- + static int digi_write_oob_command(struct usb_serial_port *port, + unsigned char *buf, int count, int interruptible) + { +- int ret = 0; +- int len; +- struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; ++ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned long flags; ++ int ret = 0; ++ int len; + + dev_dbg(&port->dev, + "digi_write_oob_command: TOP: port=%d, count=%d\n", +@@ -406,10 +402,8 @@ static int digi_write_oob_command(struct usb_serial_port *port, + dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", + __func__, ret); + return ret; +- + } + +- + /* + * Digi Write In Band Command + * +@@ -421,16 +415,15 @@ static int digi_write_oob_command(struct usb_serial_port *port, + * timeout ticks. Returns 0 if successful, or a negative + * error returned by digi_write. + */ +- + static int digi_write_inb_command(struct usb_serial_port *port, + unsigned char *buf, int count, unsigned long timeout) + { +- int ret = 0; +- int len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; + unsigned long expire; + unsigned long flags; ++ int ret = 0; ++ int len; + + dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); +@@ -459,7 +452,7 @@ static int digi_write_inb_command(struct usb_serial_port *port, + /* len must be a multiple of 4 and small enough to */ + /* guarantee the write will send buffered data first, */ + /* so commands are in order with data and not split */ +- len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); ++ len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); + if (len > 4) + len &= ~3; + +@@ -467,11 +460,10 @@ static int digi_write_inb_command(struct usb_serial_port *port, + if (priv->dp_out_buf_len > 0) { + data[0] = DIGI_CMD_SEND_DATA; + data[1] = priv->dp_out_buf_len; +- memcpy(data + 2, priv->dp_out_buf, +- priv->dp_out_buf_len); ++ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); + memcpy(data + 2 + priv->dp_out_buf_len, buf, len); +- port->write_urb->transfer_buffer_length +- = priv->dp_out_buf_len + 2 + len; ++ port->write_urb->transfer_buffer_length = ++ priv->dp_out_buf_len + 2 + len; + } else { + memcpy(data, buf, len); + port->write_urb->transfer_buffer_length = len; +@@ -484,7 +476,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, + count -= len; + buf += len; + } +- + } + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + +@@ -495,7 +486,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, + return ret; + } + +- + /* + * Digi Set Modem Signals + * +@@ -505,17 +495,15 @@ static int digi_write_inb_command(struct usb_serial_port *port, + * -EINTR if interrupted while sleeping, or a non-zero error + * returned by usb_submit_urb. + */ +- + static int digi_set_modem_signals(struct usb_serial_port *port, + unsigned int modem_signals, int interruptible) + { +- +- int ret; + struct digi_port *port_priv = usb_get_serial_port_data(port); +- struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; ++ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned char *data = oob_port->write_urb->transfer_buffer; + unsigned long flags; ++ int ret; + + dev_dbg(&port->dev, + "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n", +@@ -573,14 +561,13 @@ static int digi_set_modem_signals(struct usb_serial_port *port, + * is only called from close, and only one process can be in close on a + * port at a time, so its ok. + */ +- + static int digi_transmit_idle(struct usb_serial_port *port, + unsigned long timeout) + { +- int ret; +- unsigned char buf[2]; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char buf[2]; + unsigned long flags; ++ int ret; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_transmit_idle = 0; +@@ -607,16 +594,15 @@ static int digi_transmit_idle(struct usb_serial_port *port, + } + priv->dp_transmit_idle = 0; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return 0; +- + } + +- + static void digi_rx_throttle(struct tty_struct *tty) + { +- unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned long flags; + + /* stop receiving characters by not resubmitting the read urb */ + spin_lock_irqsave(&priv->dp_port_lock, flags); +@@ -625,13 +611,12 @@ static void digi_rx_throttle(struct tty_struct *tty) + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + } + +- + static void digi_rx_unthrottle(struct tty_struct *tty) + { +- int ret = 0; +- unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned long flags; ++ int ret = 0; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + +@@ -651,7 +636,6 @@ static void digi_rx_unthrottle(struct tty_struct *tty) + __func__, ret, priv->dp_port_num); + } + +- + static void digi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +@@ -762,7 +746,6 @@ static void digi_set_termios(struct tty_struct *tty, + + /* set stop bits */ + if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { +- + if ((cflag & CSTOPB)) + arg = DIGI_STOP_BITS_2; + else +@@ -772,7 +755,6 @@ static void digi_set_termios(struct tty_struct *tty, + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; +- + } + + /* set input flow control */ +@@ -841,7 +823,6 @@ static void digi_set_termios(struct tty_struct *tty, + tty_encode_baud_rate(tty, baud, baud); + } + +- + static int digi_break_ctl(struct tty_struct *tty, int break_state) + { + struct usb_serial_port *port = tty->driver_data; +@@ -855,50 +836,48 @@ static int digi_break_ctl(struct tty_struct *tty, int break_state) + return digi_write_inb_command(port, buf, 4, 0); + } + +- + static int digi_tiocmget(struct tty_struct *tty) + { + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); +- unsigned int val; + unsigned long flags; ++ unsigned int val; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = priv->dp_modem_signals; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return val; + } + +- + static int digi_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) + { + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); +- unsigned int val; + unsigned long flags; ++ unsigned int val; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = (priv->dp_modem_signals & ~clear) | set; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return digi_set_modem_signals(port, val, 1); + } + +- + static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) + { +- +- int ret, data_len, new_len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; ++ int ret, data_len, new_len; + unsigned long flags; + + dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); + + /* copy user data (which can sleep) before getting spin lock */ +- count = min(count, port->bulk_out_size-2); ++ count = min(count, port->bulk_out_size - 2); + count = min(64, count); + + /* be sure only one write proceeds at a time */ +@@ -920,7 +899,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + + /* allow space for any buffered data and for new data, up to */ + /* transfer buffer size - 2 (for command and length bytes) */ +- new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); ++ new_len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); + data_len = new_len + priv->dp_out_buf_len; + + if (data_len == 0) { +@@ -928,7 +907,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + return 0; + } + +- port->write_urb->transfer_buffer_length = data_len+2; ++ port->write_urb->transfer_buffer_length = data_len + 2; + + *data++ = DIGI_CMD_SEND_DATA; + *data++ = data_len; +@@ -954,39 +933,22 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + dev_dbg(&port->dev, "digi_write: returning %d\n", ret); +- return ret; + ++ return ret; + } + + static void digi_write_bulk_callback(struct urb *urb) + { +- + struct usb_serial_port *port = urb->context; +- struct usb_serial *serial; +- struct digi_port *priv; +- struct digi_serial *serial_priv; ++ struct digi_serial *serial_priv = usb_get_serial_data(port->serial); ++ struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char *data = urb->transfer_buffer; + unsigned long flags; +- int ret = 0; +- int status = urb->status; + bool wakeup; +- +- /* port and serial sanity check */ +- if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { +- pr_err("%s: port or port->private is NULL, status=%d\n", +- __func__, status); +- return; +- } +- serial = port->serial; +- if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) { +- dev_err(&port->dev, +- "%s: serial or serial->private is NULL, status=%d\n", +- __func__, status); +- return; +- } ++ int ret = 0; + + /* handle oob callback */ + if (priv->dp_port_num == serial_priv->ds_oob_port_num) { +- dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + wake_up_interruptible(&priv->write_wait); +@@ -999,15 +961,13 @@ static void digi_write_bulk_callback(struct urb *urb) + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + if (priv->dp_out_buf_len > 0) { +- *((unsigned char *)(port->write_urb->transfer_buffer)) +- = (unsigned char)DIGI_CMD_SEND_DATA; +- *((unsigned char *)(port->write_urb->transfer_buffer) + 1) +- = (unsigned char)priv->dp_out_buf_len; +- port->write_urb->transfer_buffer_length = +- priv->dp_out_buf_len + 2; +- memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf, +- priv->dp_out_buf_len); +- ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); ++ data[0] = DIGI_CMD_SEND_DATA; ++ data[1] = priv->dp_out_buf_len; ++ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); ++ ++ urb->transfer_buffer_length = priv->dp_out_buf_len + 2; ++ ++ ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret == 0) { + priv->dp_write_urb_in_use = 1; + priv->dp_out_buf_len = 0; +@@ -1041,8 +1001,8 @@ static unsigned int digi_write_room(struct tty_struct *tty) + + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room); +- return room; + ++ return room; + } + + static unsigned int digi_chars_in_buffer(struct tty_struct *tty) +@@ -1070,17 +1030,53 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) + digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1); + } + ++static int digi_open_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct usb_serial_port *oob_port = serial_priv->ds_oob_port; ++ int ret = 0; ++ ++ mutex_lock(&serial_priv->open_mutex); ++ ++ if (serial_priv->open_count++ == 0) { ++ ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); ++ if (ret) { ++ dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", ++ ret); ++ serial_priv->open_count--; ++ } ++ } ++ ++ mutex_unlock(&serial_priv->open_mutex); ++ ++ return ret; ++} ++ ++static void digi_close_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct usb_serial_port *oob_port = serial_priv->ds_oob_port; ++ ++ mutex_lock(&serial_priv->open_mutex); ++ ++ if (serial_priv->open_count-- == 1) { ++ usb_kill_urb(oob_port->read_urb); ++ usb_kill_urb(oob_port->write_urb); ++ } ++ ++ mutex_unlock(&serial_priv->open_mutex); ++} ++ + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) + { +- int ret; +- unsigned char buf[32]; + struct digi_port *priv = usb_get_serial_port_data(port); + struct ktermios not_termios; +- int throttled; ++ unsigned char buf[32]; ++ int ret; + +- /* be sure the device is started up */ +- if (digi_startup_device(port->serial) != 0) +- return -ENXIO; ++ ret = digi_open_oob_port(port->serial); ++ if (ret) ++ return ret; + + /* read modem signals automatically whenever they change */ + buf[0] = DIGI_CMD_READ_INPUT_SIGNALS; +@@ -1106,32 +1102,34 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) + } + + spin_lock_irq(&priv->dp_port_lock); +- throttled = priv->dp_throttle_restart; + priv->dp_throttled = 0; + priv->dp_throttle_restart = 0; + spin_unlock_irq(&priv->dp_port_lock); + +- if (throttled) { +- ret = usb_submit_urb(port->read_urb, GFP_KERNEL); +- if (ret) { +- dev_err(&port->dev, "failed to submit read urb: %d\n", ret); +- return ret; +- } ++ ret = usb_submit_urb(port->read_urb, GFP_KERNEL); ++ if (ret) { ++ dev_err(&port->dev, "failed to submit read urb: %d\n", ret); ++ goto err_close_oob; + } + + return 0; +-} + ++err_close_oob: ++ digi_close_oob_port(port->serial); ++ ++ return ret; ++} + + static void digi_close(struct usb_serial_port *port) + { ++ struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char buf[32]; + DEFINE_WAIT(wait); + int ret; +- unsigned char buf[32]; +- struct digi_port *priv = usb_get_serial_port_data(port); ++ ++ usb_kill_urb(port->read_urb); + + mutex_lock(&port->serial->disc_mutex); +- /* if disconnected, just clear flags */ + if (port->serial->disconnected) + goto exit; + +@@ -1177,57 +1175,18 @@ static void digi_close(struct usb_serial_port *port) + TASK_INTERRUPTIBLE); + schedule_timeout(DIGI_CLOSE_TIMEOUT); + finish_wait(&priv->dp_flush_wait, &wait); ++exit: ++ mutex_unlock(&port->serial->disc_mutex); + + /* shutdown any outstanding bulk writes */ + usb_kill_urb(port->write_urb); +-exit: +- spin_lock_irq(&priv->dp_port_lock); +- priv->dp_write_urb_in_use = 0; +- wake_up_interruptible(&priv->dp_close_wait); +- spin_unlock_irq(&priv->dp_port_lock); +- mutex_unlock(&port->serial->disc_mutex); +-} + +- +-/* +- * Digi Startup Device +- * +- * Starts reads on all ports. Must be called AFTER startup, with +- * urbs initialized. Returns 0 if successful, non-zero error otherwise. +- */ +- +-static int digi_startup_device(struct usb_serial *serial) +-{ +- int i, ret = 0; +- struct digi_serial *serial_priv = usb_get_serial_data(serial); +- struct usb_serial_port *port; +- +- /* be sure this happens exactly once */ +- spin_lock(&serial_priv->ds_serial_lock); +- if (serial_priv->ds_device_started) { +- spin_unlock(&serial_priv->ds_serial_lock); +- return 0; +- } +- serial_priv->ds_device_started = 1; +- spin_unlock(&serial_priv->ds_serial_lock); +- +- /* start reading from each bulk in endpoint for the device */ +- /* set USB_DISABLE_SPD flag for write bulk urbs */ +- for (i = 0; i < serial->type->num_ports + 1; i++) { +- port = serial->port[i]; +- ret = usb_submit_urb(port->read_urb, GFP_KERNEL); +- if (ret != 0) { +- dev_err(&port->dev, +- "%s: usb_submit_urb failed, ret=%d, port=%d\n", +- __func__, ret, i); +- break; +- } +- } +- return ret; ++ digi_close_oob_port(port->serial); + } + + static int digi_port_init(struct usb_serial_port *port, unsigned port_num) + { ++ struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv; + + priv = kzalloc_obj(*priv); +@@ -1235,10 +1194,13 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) + return -ENOMEM; + + spin_lock_init(&priv->dp_port_lock); ++ ++ if (port == serial_priv->ds_oob_port) ++ lockdep_set_subclass(&priv->dp_port_lock, SINGLE_DEPTH_NESTING); ++ + priv->dp_port_num = port_num; + init_waitqueue_head(&priv->dp_transmit_idle_wait); + init_waitqueue_head(&priv->dp_flush_wait); +- init_waitqueue_head(&priv->dp_close_wait); + init_waitqueue_head(&priv->write_wait); + priv->dp_port = port; + +@@ -1275,10 +1237,13 @@ static int digi_startup(struct usb_serial *serial) + if (!serial_priv) + return -ENOMEM; + +- spin_lock_init(&serial_priv->ds_serial_lock); ++ mutex_init(&serial_priv->open_mutex); ++ + serial_priv->ds_oob_port_num = oob_port_num; + serial_priv->ds_oob_port = serial->port[oob_port_num]; + ++ usb_set_serial_data(serial, serial_priv); ++ + ret = digi_port_init(serial_priv->ds_oob_port, + serial_priv->ds_oob_port_num); + if (ret) { +@@ -1286,24 +1251,9 @@ static int digi_startup(struct usb_serial *serial) + return ret; + } + +- usb_set_serial_data(serial, serial_priv); +- + return 0; + } + +- +-static void digi_disconnect(struct usb_serial *serial) +-{ +- int i; +- +- /* stop reads and writes on all ports */ +- for (i = 0; i < serial->type->num_ports + 1; i++) { +- usb_kill_urb(serial->port[i]->read_urb); +- usb_kill_urb(serial->port[i]->write_urb); +- } +-} +- +- + static void digi_release(struct usb_serial *serial) + { + struct digi_serial *serial_priv; +@@ -1333,30 +1283,25 @@ static void digi_port_remove(struct usb_serial_port *port) + static void digi_read_bulk_callback(struct urb *urb) + { + struct usb_serial_port *port = urb->context; +- struct digi_port *priv; +- struct digi_serial *serial_priv; +- int ret; ++ struct usb_serial *serial = port->serial; ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct digi_port *priv = usb_get_serial_port_data(port); + int status = urb->status; +- +- /* port sanity check, do not resubmit if port is not valid */ +- if (port == NULL) +- return; +- priv = usb_get_serial_port_data(port); +- if (priv == NULL) { +- dev_err(&port->dev, "%s: port->private is NULL, status=%d\n", +- __func__, status); +- return; +- } +- if (port->serial == NULL || +- (serial_priv = usb_get_serial_data(port->serial)) == NULL) { +- dev_err(&port->dev, "%s: serial is bad or serial->private " +- "is NULL, status=%d\n", __func__, status); +- return; +- } ++ int ret; + + /* do not resubmit urb if it has any status error */ +- if (status) { +- dev_err(&port->dev, ++ switch (status) { ++ case 0: ++ break; ++ case -ENOENT: ++ case -ECONNRESET: ++ case -ESHUTDOWN: ++ dev_err(&serial->interface->dev, ++ "%s: nonzero read bulk status: status=%d, port=%d\n", ++ __func__, status, priv->dp_port_num); ++ return; ++ default: ++ dev_err(&serial->interface->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; +@@ -1374,11 +1319,10 @@ static void digi_read_bulk_callback(struct urb *urb) + /* continue read */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret != 0 && ret != -EPERM) { +- dev_err(&port->dev, ++ dev_err(&serial->interface->dev, + "%s: failed resubmitting urb, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + } +- + } + + /* +@@ -1390,7 +1334,6 @@ static void digi_read_bulk_callback(struct urb *urb) + * It returns 0 if successful, 1 if successful but the port is + * throttled, and -1 if the sanity checks failed. + */ +- + static int digi_read_inb_callback(struct urb *urb) + { + struct usb_serial_port *port = urb->context; +@@ -1468,10 +1411,8 @@ static int digi_read_inb_callback(struct urb *urb) + dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode); + + return throttled ? 1 : 0; +- + } + +- + /* + * Digi Read OOB Callback + * +@@ -1480,10 +1421,8 @@ static int digi_read_inb_callback(struct urb *urb) + * the port->serial is valid. It returns 0 if successful, and + * -1 if the sanity checks failed. + */ +- + static int digi_read_oob_callback(struct urb *urb) + { +- + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + struct tty_struct *tty; +@@ -1491,8 +1430,8 @@ static int digi_read_oob_callback(struct urb *urb) + unsigned char *buf = urb->transfer_buffer; + int opcode, line, status, val; + unsigned long flags; +- int i; + unsigned int rts; ++ int i; + + if (urb->actual_length < 4) + return -1; +@@ -1504,7 +1443,8 @@ static int digi_read_oob_callback(struct urb *urb) + status = buf[i + 2]; + val = buf[i + 3]; + +- dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", ++ dev_dbg(&serial->interface->dev, ++ "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", + opcode, line, status, val); + + if (status != 0 || line >= serial->type->num_ports) +@@ -1562,8 +1502,8 @@ static int digi_read_oob_callback(struct urb *urb) + } + tty_kref_put(tty); + } +- return 0; + ++ return 0; + } + + module_usb_serial_driver(serial_drivers, id_table_combined); +diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c +--- a/drivers/usb/serial/keyspan_pda.c ++++ b/drivers/usb/serial/keyspan_pda.c +@@ -31,7 +31,6 @@ + struct keyspan_pda_private { + int tx_room; + struct work_struct unthrottle_work; +- struct usb_serial *serial; + struct usb_serial_port *port; + bool throttled; + bool throttle_req; +diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c +--- a/drivers/usb/serial/metro-usb.c ++++ b/drivers/usb/serial/metro-usb.c +@@ -36,6 +36,7 @@ + struct metrousb_private { + spinlock_t lock; + int throttled; ++ int throttle_req; + unsigned long control_state; + }; + +@@ -108,7 +109,7 @@ static void metrousb_read_int_callback(struct urb *urb) + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + int throttled = 0; +- int result = 0; ++ int result; + + dev_dbg(&port->dev, "%s\n", __func__); + +@@ -143,7 +144,10 @@ static void metrousb_read_int_callback(struct urb *urb) + + /* Set any port variables. */ + spin_lock_irqsave(&metro_priv->lock, flags); +- throttled = metro_priv->throttled; ++ if (metro_priv->throttle_req) { ++ metro_priv->throttled = 1; ++ throttled = 1; ++ } + spin_unlock_irqrestore(&metro_priv->lock, flags); + + if (throttled) +@@ -169,12 +173,13 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) + struct usb_serial *serial = port->serial; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags; +- int result = 0; ++ int result; + + /* Set the private data information for the port. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->control_state = 0; + metro_priv->throttled = 0; ++ metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + /* Clear the urb pipe. */ +@@ -269,7 +274,7 @@ static void metrousb_throttle(struct tty_struct *tty) + + /* Set the private information for the port to stop reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); +- metro_priv->throttled = 1; ++ metro_priv->throttle_req = 1; + spin_unlock_irqrestore(&metro_priv->lock, flags); + } + +@@ -321,19 +326,23 @@ static void metrousb_unthrottle(struct tty_struct *tty) + struct usb_serial_port *port = tty->driver_data; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags; +- int result = 0; ++ int throttled; ++ int result; + + /* Set the private information for the port to resume reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); ++ throttled = metro_priv->throttled; + metro_priv->throttled = 0; ++ metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + +- /* Submit the urb to read from the port. */ +- result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); +- if (result) +- dev_err(&port->dev, +- "failed submitting interrupt in urb error code=%d\n", +- result); ++ if (throttled) { ++ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); ++ if (result) { ++ dev_err(&port->dev, "failed to submit interrupt in urb: %d\n", ++ result); ++ } ++ } + } + + static struct usb_serial_driver metrousb_device = { +diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c +--- a/drivers/usb/serial/pl2303.c ++++ b/drivers/usb/serial/pl2303.c +@@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) }, ++ { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), +@@ -141,10 +142,15 @@ MODULE_DEVICE_TABLE(usb, id_table); + #define VENDOR_WRITE_REQUEST_TYPE 0x40 + #define VENDOR_WRITE_REQUEST 0x01 + #define VENDOR_WRITE_NREQUEST 0x80 ++#define VENDOR_WRITE_MPREQUEST 0x80 + + #define VENDOR_READ_REQUEST_TYPE 0xc0 + #define VENDOR_READ_REQUEST 0x01 + #define VENDOR_READ_NREQUEST 0x81 ++#define VENDOR_READ_MPREQUEST 0x80 ++ ++#define PL256X_RESET_REQUEST_TYPE 0x40 ++#define PL256X_RESET_REQUEST 0x96 + + #define UART_STATE_INDEX 8 + #define UART_STATE_MSR_MASK 0x8b +@@ -172,6 +178,16 @@ MODULE_DEVICE_TABLE(usb, id_table); + #define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 + #define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c + ++#define PL256X_PORT_A_FLOWCTRL_REG 0xc005 ++#define PL256X_PORT_B_FLOWCTRL_REG 0xd005 ++#define PL256X_PORT_C_FLOWCTRL_REG 0xe005 ++#define PL256X_PORT_D_FLOWCTRL_REG 0xf005 ++ ++#define PL256X_FLOWCTRL_MASK 0x43 ++#define PL256X_FLOWCTRL_XON_XOFF 0x40 ++#define PL256X_FLOWCTRL_RTS_CTS 0x03 ++#define PL256X_FLOWCTRL_NONE 0x00 ++ + static int pl2303_set_break(struct usb_serial_port *port, bool enable); + + enum pl2303_type { +@@ -181,6 +197,7 @@ enum pl2303_type { + TYPE_TB, + TYPE_HXD, + TYPE_HXN, ++ TYPE_MP, + TYPE_COUNT + }; + +@@ -196,6 +213,8 @@ struct pl2303_type_data { + struct pl2303_serial_private { + const struct pl2303_type_data *type; + unsigned long quirks; ++ u16 interface_num; ++ u16 flowctrl_reg; + }; + + struct pl2303_private { +@@ -236,8 +255,30 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { + .max_baud_rate = 12000000, + .no_divisors = true, + }, ++ [TYPE_MP] = { ++ .name = "MP", ++ .max_baud_rate = 24000000, ++ .no_divisors = true, ++ }, + }; + ++static int pl256x_uart_reset(struct usb_serial *serial) ++{ ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); ++ struct device *dev = &serial->interface->dev; ++ int res; ++ ++ res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), ++ PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE, ++ 0, spriv->interface_num, NULL, 0, 100); ++ if (res) { ++ dev_err(dev, "failed to reset device: %d\n", res); ++ return res; ++ } ++ ++ return 0; ++} ++ + static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + unsigned char buf[1]) + { +@@ -248,6 +289,8 @@ static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_READ_NREQUEST; ++ else if (spriv->type == &pl2303_type_data[TYPE_MP]) ++ request = VENDOR_READ_MPREQUEST; + else + request = VENDOR_READ_REQUEST; + +@@ -279,6 +322,8 @@ static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index) + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_WRITE_NREQUEST; ++ else if (spriv->type == &pl2303_type_data[TYPE_MP]) ++ request = VENDOR_WRITE_MPREQUEST; + else + request = VENDOR_WRITE_REQUEST; + +@@ -304,10 +349,12 @@ static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val) + if (!buf) + return -ENOMEM; + +- if (spriv->type == &pl2303_type_data[TYPE_HXN]) ++ if (spriv->type == &pl2303_type_data[TYPE_HXN] || ++ spriv->type == &pl2303_type_data[TYPE_MP]) { + ret = pl2303_vendor_read(serial, reg, buf); +- else ++ } else { + ret = pl2303_vendor_read(serial, reg | 0x80, buf); ++ } + + if (ret) + goto out_free; +@@ -458,6 +505,14 @@ static int pl2303_detect_type(struct usb_serial *serial) + case 0x905: /* GT-2AB */ + case 0x1005: /* GC-Q20 */ + return TYPE_HXN; ++ case 0x3302: /* PL2533 VC 2 Port */ ++ case 0x3304: /* PL2533 VC 4 Port */ ++ case 0x4302: /* PL2543 VC 2 Port */ ++ case 0x4304: /* PL2543 VC 4 Port */ ++ case 0x6502: /* PL2565 VC 2 Port */ ++ case 0x6504: /* PL2565 VC 4 Port */ ++ case 0x6506: /* PL2565 VC 4 Port QFN64 package */ ++ return TYPE_MP; + } + break; + } +@@ -491,6 +546,7 @@ static int pl2303_startup(struct usb_serial *serial) + struct pl2303_serial_private *spriv; + enum pl2303_type type; + unsigned char *buf; ++ unsigned int ifnum; + int ret; + + ret = pl2303_detect_type(serial); +@@ -500,20 +556,43 @@ static int pl2303_startup(struct usb_serial *serial) + type = ret; + dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name); + ++ ifnum = serial->interface->altsetting->desc.bInterfaceNumber; ++ + spriv = kzalloc_obj(*spriv); + if (!spriv) + return -ENOMEM; + ++ if (type == TYPE_MP) { ++ switch (ifnum) { ++ case 0: ++ spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG; ++ break; ++ case 1: ++ spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG; ++ break; ++ case 2: ++ spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG; ++ break; ++ case 3: ++ spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG; ++ break; ++ default: ++ kfree(spriv); ++ return -ENODEV; ++ } ++ } ++ + spriv->type = &pl2303_type_data[type]; + spriv->quirks = (unsigned long)usb_get_serial_data(serial); + spriv->quirks |= spriv->type->quirks; ++ spriv->interface_num = ifnum; + + if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) + spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; + + usb_set_serial_data(serial, spriv); + +- if (type != TYPE_HXN) { ++ if (type != TYPE_HXN && type != TYPE_MP) { + buf = kmalloc(1, GFP_KERNEL); + if (!buf) { + kfree(spriv); +@@ -575,13 +654,15 @@ static void pl2303_port_remove(struct usb_serial_port *port) + static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) + { + struct usb_device *dev = port->serial->dev; ++ struct usb_serial *serial = port->serial; ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int retval; + + dev_dbg(&port->dev, "%s - %02x\n", __func__, value); + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, +- value, 0, NULL, 0, 100); ++ value, spriv->interface_num, NULL, 0, 100); + if (retval) + dev_err(&port->dev, "%s - failed: %d\n", __func__, retval); + +@@ -761,7 +842,7 @@ static int pl2303_get_line_request(struct usb_serial_port *port, + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, +- 0, 0, buf, 7, 100); ++ 0, spriv->interface_num, buf, 7, 100); + if (ret != 7) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + +@@ -780,11 +861,13 @@ static int pl2303_set_line_request(struct usb_serial_port *port, + unsigned char buf[7]) + { + struct usb_device *udev = port->serial->dev; ++ struct usb_serial *serial = port->serial; ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int ret; + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, +- 0, 0, buf, 7, 100); ++ 0, spriv->interface_num, buf, 7, 100); + if (ret < 0) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + return ret; +@@ -939,6 +1022,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_RTS_CTS); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_RTS_CTS); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60); + } +@@ -947,6 +1033,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_XON_XOFF); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_XON_XOFF); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0); + } +@@ -955,6 +1044,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_NONE); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_NONE); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0); + } +@@ -1002,6 +1094,8 @@ static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) + pl2303_vendor_write(serial, PL2303_HXN_RESET_REG, + PL2303_HXN_RESET_UPSTREAM_PIPE | + PL2303_HXN_RESET_DOWNSTREAM_PIPE); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl256x_uart_reset(serial); + } else { + pl2303_vendor_write(serial, 8, 0); + pl2303_vendor_write(serial, 9, 0); +@@ -1112,7 +1206,7 @@ static int pl2303_set_break(struct usb_serial_port *port, bool enable) + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + BREAK_REQUEST, BREAK_REQUEST_TYPE, state, +- 0, NULL, 0, 100); ++ spriv->interface_num, NULL, 0, 100); + if (result) { + dev_err(&port->dev, "error sending break = %d\n", result); + return result; +diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h +--- a/drivers/usb/serial/pl2303.h ++++ b/drivers/usb/serial/pl2303.h +@@ -26,7 +26,7 @@ + #define PL2303_PRODUCT_ID_HCR331 0x331a + #define PL2303_PRODUCT_ID_MOTOROLA 0x0307 + #define PL2303_PRODUCT_ID_ZTEK 0xe1f1 +- ++#define PL256X_PRODUCT_ID_4P 0x2533 + + #define ATEN_VENDOR_ID 0x0557 + #define ATEN_VENDOR_ID2 0x0547 +diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig +--- a/drivers/usb/typec/altmodes/Kconfig ++++ b/drivers/usb/typec/altmodes/Kconfig +@@ -5,6 +5,7 @@ menu "USB Type-C Alternate Mode drivers" + config TYPEC_DP_ALTMODE + tristate "DisplayPort Alternate Mode driver" + depends on DRM ++ select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + DisplayPort USB Type-C Alternate Mode allows DisplayPort + displays and adapters to be attached to the USB Type-C +diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c +--- a/drivers/usb/typec/altmodes/displayport.c ++++ b/drivers/usb/typec/altmodes/displayport.c +@@ -764,6 +764,7 @@ int dp_altmode_probe(struct typec_altmode *alt) + struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); + struct fwnode_handle *fwnode; + struct dp_altmode *dp; ++ u32 cap = DP_CAP_CAPABILITY(alt->vdo); + + /* Port can only be DFP_U. */ + if (typec_altmode_get_data_role(alt) != TYPEC_HOST) +@@ -778,6 +779,18 @@ int dp_altmode_probe(struct typec_altmode *alt) + return -ENODEV; + } + ++ /* ++ * Make sure the DisplayPort VDO is valid (VESA DPAM v2.1a, Section ++ * 5.4.1, Table 5-6, DP Capabilities VDO). A device exposing DP on a ++ * USB-C receptacle must advertise at least one pin assignment for the ++ * capability it claims, otherwise Alt Mode can never be configured. ++ */ ++ if ((cap == DP_CAP_DFP_D && !DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo)) || ++ (cap == DP_CAP_UFP_D && !DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo))) { ++ typec_altmode_put_plug(plug); ++ return -ENODEV; ++ } ++ + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) { + typec_altmode_put_plug(plug); +@@ -790,7 +803,6 @@ int dp_altmode_probe(struct typec_altmode *alt) + dp->alt = alt; + + alt->desc = "DisplayPort"; +- typec_altmode_set_ops(alt, &dp_altmode_ops); + + if (plug) { + plug->desc = "Displayport"; +@@ -811,6 +823,10 @@ int dp_altmode_probe(struct typec_altmode *alt) + if (plug) + typec_altmode_set_drvdata(plug, dp); + ++ if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) ++ return 0; ++ ++ typec_altmode_set_ops(alt, &dp_altmode_ops); + if (!alt->mode_selection) { + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; + schedule_work(&dp->work); +diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c +--- a/drivers/usb/typec/altmodes/thunderbolt.c ++++ b/drivers/usb/typec/altmodes/thunderbolt.c +@@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt) + + alt->desc = "Thunderbolt3"; + typec_altmode_set_drvdata(alt, tbt); ++ ++ if (typec_cable_altmode_unsupported(alt)) ++ return 0; ++ + typec_altmode_set_ops(alt, &tbt_altmode_ops); + + if (!alt->mode_selection && tbt_ready(alt)) { +diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c +--- a/drivers/usb/typec/class.c ++++ b/drivers/usb/typec/class.c +@@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable) + } + EXPORT_SYMBOL_GPL(typec_cable_is_active); + ++enum typec_cable_altmode_support { ++ CABLE_SUPPORT_UNKNOWN, ++ CABLE_SUPPORTED, ++ CABLE_NOT_SUPPORTED, ++}; ++ ++static enum typec_cable_altmode_support ++typec_cable_check_altmode_support(struct typec_cable *cable, ++ struct typec_altmode *alt) ++{ ++ struct typec_altmode *plug; ++ u32 speed; ++ ++ /* ++ * Check if the cable has an e-marker, supports modal operation, and the ++ * SOP' altmode nodes are created. ++ */ ++ plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); ++ if (plug) { ++ typec_altmode_put_plug(plug); ++ return CABLE_SUPPORTED; ++ } ++ ++ /* The identity is not specified */ ++ if (!cable->identity) ++ return CABLE_SUPPORT_UNKNOWN; ++ ++ /* Non-e-marked cable */ ++ if (!cable->identity->id_header) ++ return CABLE_NOT_SUPPORTED; ++ ++ switch (PD_IDH_PTYPE(cable->identity->id_header)) { ++ case IDH_PTYPE_PCABLE: ++ speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); ++ if (speed == CABLE_USB2_ONLY) ++ return CABLE_NOT_SUPPORTED; ++ return CABLE_SUPPORTED; ++ case IDH_PTYPE_ACABLE: ++ /* ++ * Active cables must establish an SOP' communication ++ * node. Since that check failed at the beginning of ++ * this function, this active cable does not support ++ * this specific altmode. ++ */ ++ return CABLE_NOT_SUPPORTED; ++ } ++ ++ return CABLE_SUPPORT_UNKNOWN; ++} ++ ++/** ++ * typec_cable_altmode_unsupported - Check if a cable restricts altmode ++ * @alt: The Alternate Mode to evaluate ++ * ++ * Returns true if the connected cable is incapable of handling the altmode. ++ */ ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt) ++{ ++ enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; ++ struct typec_cable *cable; ++ ++ cable = typec_cable_get(typec_altmode2port(alt)); ++ if (cable) { ++ support = typec_cable_check_altmode_support(cable, alt); ++ typec_cable_put(cable); ++ } ++ ++ return support == CABLE_NOT_SUPPORTED; ++} ++EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); ++ + /** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values +diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c +--- a/drivers/usb/typec/mux/tusb1046.c ++++ b/drivers/usb/typec/mux/tusb1046.c +@@ -178,6 +178,7 @@ static const struct of_device_id tusb1046_match_table[] = { + {.compatible = "ti,tusb1046"}, + {}, + }; ++MODULE_DEVICE_TABLE(of, tusb1046_match_table); + + static struct i2c_driver tusb1046_driver = { + .driver = { +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -212,6 +213,24 @@ static const char * const tcpm_ams_str[] = { + FOREACH_AMS(GENERATE_STRING) + }; + ++#define FOREACH_VDM_DISCOVERY(S) \ ++ S(VDM_DISCOVERY_UNKNOWN), \ ++ S(VDM_DISCOVERY_PARTNER_IDENT), \ ++ S(VDM_DISCOVERY_CABLE_IDENT), \ ++ S(VDM_DISCOVERY_PARTNER_SVIDS), \ ++ S(VDM_DISCOVERY_PARTNER_MODES), \ ++ S(VDM_DISCOVERY_CABLE_SVIDS), \ ++ S(VDM_DISCOVERY_CABLE_MODES), \ ++ S(VDM_DISCOVERY_COMPLETE) ++ ++enum vdm_discovery_states { ++ FOREACH_VDM_DISCOVERY(GENERATE_ENUM) ++}; ++ ++static const char * const vdm_discovery_state_strings[] = { ++ FOREACH_VDM_DISCOVERY(GENERATE_STRING) ++}; ++ + enum vdm_states { + VDM_STATE_ERR_BUSY = -3, + VDM_STATE_ERR_SEND = -2, +@@ -232,7 +251,9 @@ enum pd_msg_request { + PD_MSG_DATA_SINK_CAP, + PD_MSG_DATA_SOURCE_CAP, + PD_MSG_DATA_REV, +- PD_MSG_EXT_SINK_CAP_EXT ++ PD_MSG_EXT_SINK_CAP_EXT, ++ PD_MSG_DATA_BATT_STATUS, ++ PD_MSG_EXT_BATT_CAP, + }; + + enum adev_actions { +@@ -274,7 +295,7 @@ enum frs_typec_current { + #define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) + + #define GET_SINK_CAP_RETRY_MS 100 +-#define SEND_DISCOVER_RETRY_MS 100 ++#define SEND_DISCOVERY_VDM_RETRY_MS 100 + + struct pd_mode_data { + int svid_index; /* current SVID index */ +@@ -387,7 +408,15 @@ struct pd_timings { + }; + + /* Convert microwatt to watt */ +-#define UW_TO_W(pow) ((pow) / 1000000) ++#define UW_TO_W(pow) (div_u64((pow), 1000000)) ++ ++/* ++ * As per USB PD Spec Rev 3.18 (Sec. 6.5.13.11), the number of fixed batteries ++ * that a port can be queried is restricted to 4. ++ */ ++#define MAX_NUM_FIXED_BATT 4 ++ ++#define BATTERY_PROPERTY_UNKNOWN 0xffff + + /* + * struct pd_identifier - Contains info about PD identifiers +@@ -483,8 +512,6 @@ struct tcpm_port { + bool vbus_source; + bool vbus_charge; + +- /* Set to true when Discover_Identity Command is expected to be sent in Ready states. */ +- bool send_discover; + bool op_vsafe5v; + + int try_role; +@@ -510,8 +537,8 @@ struct tcpm_port { + struct kthread_work vdm_state_machine; + struct hrtimer enable_frs_timer; + struct kthread_work enable_frs; +- struct hrtimer send_discover_timer; +- struct kthread_work send_discover_work; ++ struct hrtimer vdm_discovery_timer; ++ struct kthread_work vdm_discovery_work; + bool state_machine_running; + /* Set to true when VDM State Machine has following actions. */ + bool vdm_sm_running; +@@ -578,6 +605,9 @@ struct tcpm_port { + + u32 bist_request; + ++ /* VDM Discovery State to determine message sent */ ++ enum vdm_discovery_states vdm_discovery_state; ++ + /* PD state for Vendor Defined Messages */ + enum vdm_states vdm_state; + u32 vdm_retries; +@@ -641,12 +671,6 @@ struct tcpm_port { + bool potential_contaminant; + + /* SOP* Related Fields */ +- /* +- * Flag to determine if SOP' Discover Identity is available. The flag +- * is set if Discover Identity on SOP' does not immediately follow +- * Discover Identity on SOP. +- */ +- bool send_discover_prime; + /* + * tx_sop_type determines which SOP* a message is being sent on. + * For messages that are queued and not sent immediately such as in +@@ -683,6 +707,9 @@ struct tcpm_port { + + struct pd_identifier pd_ident; + struct sink_caps_ext_data sink_caps_ext; ++ struct power_supply **fixed_batt; ++ u32 fixed_batt_cnt; ++ u32 batt_request_id; + #ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + struct mutex logbuffer_lock; /* log buffer access lock */ +@@ -771,6 +798,9 @@ static const char * const pd_rev[] = { + #define tcpm_wait_for_discharge(port) \ + (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0) + ++#define tcpm_can_send_vdm(state) \ ++ ((state == SRC_READY || state == SNK_READY || state == SRC_VDM_IDENTITY_REQUEST)) ++ + static enum tcpm_state tcpm_default_state(struct tcpm_port *port) + { + if (port->port_type == TYPEC_PORT_DRP) { +@@ -1470,6 +1500,20 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + } + ++static void tcpm_get_fixed_batt(struct tcpm_port *port) ++{ ++ int ret; ++ ++ if (!port->self_powered || port->fixed_batt_cnt > 0) ++ return; ++ ++ ret = power_supply_get_system_batteries(port->dev, &port->fixed_batt); ++ if (ret < 0) ++ tcpm_log(port, "Failed to get battery array, ret=%d", ret); ++ else ++ port->fixed_batt_cnt = ret; ++} ++ + static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + { + u16 operating_snk_watt = port->operating_snk_mw / 1000; +@@ -1482,6 +1526,8 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + if (!port->self_powered) + data->spr_op_pdp = operating_snk_watt; + ++ tcpm_get_fixed_batt(port); ++ + /* + * SPR Sink Minimum PDP indicates the minimum power required to operate + * a sink device in its lowest level of functionality without requiring +@@ -1507,6 +1553,7 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + skedb.load_step = data->load_step; + skedb.load_char = cpu_to_le16(data->load_char); + skedb.compliance = data->compliance; ++ skedb.batt_info = min(port->fixed_batt_cnt, MAX_NUM_FIXED_BATT); + skedb.modes = data->modes; + skedb.spr_min_pdp = data->spr_min_pdp; + skedb.spr_op_pdp = data->spr_op_pdp; +@@ -1525,6 +1572,153 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + port->message_id, + data_obj_cnt, + 1 /* Denotes if ext header */)); ++ ++ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); ++} ++ ++static u16 tcpm_charge_to_energy(int charge, int voltage) ++{ ++ u64 energy = div_u64((u64)charge * voltage, 1000000); ++ ++ /* Battery telemetry is reported in increments of 0.1Wh */ ++ return (u16)UW_TO_W(energy * 10); ++} ++ ++static int tcpm_pd_send_batt_status(struct tcpm_port *port) ++{ ++ u16 present_charge = BATTERY_PROPERTY_UNKNOWN; ++ bool batt_present = false, invalid_ref = true; ++ u32 batt_id = port->batt_request_id; ++ union power_supply_propval val; ++ struct power_supply *batt; ++ u8 charging_status = 0; ++ struct pd_message msg; ++ int ret, charge_now; ++ u32 bsdo; ++ ++ tcpm_get_fixed_batt(port); ++ memset(&msg, 0, sizeof(msg)); ++ ++ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) ++ goto send_status; ++ ++ invalid_ref = false; ++ batt = port->fixed_batt[batt_id]; ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_PRESENT, &val); ++ if (ret) ++ tcpm_log(port, ++ "Failed to fetch power_supply_prop_present ret %d", ++ ret); ++ else ++ batt_present = val.intval > 0; ++ ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW, ++ &val); ++ if (!ret) { ++ charge_now = val.intval; ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_VOLTAGE_AVG, ++ &val); ++ if (!ret) ++ present_charge = tcpm_charge_to_energy(charge_now, ++ val.intval); ++ } ++ ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); ++ if (!ret) { ++ switch (val.intval) { ++ case POWER_SUPPLY_STATUS_CHARGING: ++ charging_status = BSDO_BATTERY_INFO_CHARGING; ++ break; ++ case POWER_SUPPLY_STATUS_DISCHARGING: ++ charging_status = BSDO_BATTERY_INFO_DISCHARGING; ++ break; ++ case POWER_SUPPLY_STATUS_NOT_CHARGING: ++ case POWER_SUPPLY_STATUS_FULL: ++ charging_status = BSDO_BATTERY_INFO_IDLE; ++ break; ++ default: ++ charging_status = BSDO_BATTERY_INFO_RSVD; ++ break; ++ } ++ } ++ ++send_status: ++ ++ bsdo = BSDO(present_charge, charging_status, batt_present, invalid_ref); ++ msg.payload[0] = cpu_to_le32(bsdo); ++ msg.header = PD_HEADER_LE(PD_DATA_BATT_STATUS, ++ port->pwr_role, ++ port->data_role, ++ port->negotiated_rev, ++ port->message_id, ++ 1); ++ ++ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); ++} ++ ++static int tcpm_pd_send_batt_cap(struct tcpm_port *port) ++{ ++ u16 design_cap = BATTERY_PROPERTY_UNKNOWN; ++ u16 charge_cap = BATTERY_PROPERTY_UNKNOWN; ++ u32 batt_id = port->batt_request_id; ++ union power_supply_propval val; ++ struct batt_cap_ext_msg bcdb; ++ struct power_supply *batt; ++ bool invalid_ref = true; ++ struct pd_message msg; ++ u8 data_obj_cnt; ++ int ret, vol; ++ ++ tcpm_get_fixed_batt(port); ++ memset(&msg, 0, sizeof(msg)); ++ ++ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) ++ goto send_cap; ++ ++ invalid_ref = false; ++ batt = port->fixed_batt[batt_id]; ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, ++ &val); ++ if (!ret) { ++ vol = val.intval; ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, ++ &val); ++ if (!ret) ++ design_cap = tcpm_charge_to_energy(val.intval, vol); ++ ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ &val); ++ if (!ret) ++ charge_cap = tcpm_charge_to_energy(val.intval, vol); ++ } ++ ++send_cap: ++ ++ /* ++ * As per the USB PD Rev3.1 v1.8 spec, if a battery VID (assigned by the ++ * USB-IF) does not exist or an invalid battery reference is made by the ++ * requestor, then set the VID field to 0xffff. If the VID field is ++ * 0xffff, set the PID field to 0. ++ */ ++ bcdb.vid = BATTERY_PROPERTY_UNKNOWN; ++ bcdb.pid = 0; ++ bcdb.batt_design_cap = cpu_to_le16(design_cap); ++ bcdb.batt_last_chg_cap = cpu_to_le16(charge_cap); ++ bcdb.batt_type = invalid_ref ? BATT_CAP_BATT_TYPE_INVALID_REF : 0; ++ memcpy(msg.ext_msg.data, &bcdb, sizeof(bcdb)); ++ msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(bcdb), ++ 0, /* Denotes if request chunk */ ++ 0, /* Chunk number */ ++ 1 /* Chunked */); ++ ++ data_obj_cnt = count_chunked_data_objs(sizeof(bcdb)); ++ msg.header = PD_HEADER_EXT_LE(PD_EXT_BATT_CAP, port->pwr_role, ++ port->data_role, port->negotiated_rev, ++ port->message_id, data_obj_cnt); ++ + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + } + +@@ -1559,13 +1753,19 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del + } + } + +-static void mod_send_discover_delayed_work(struct tcpm_port *port, unsigned int delay_ms) ++static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) ++{ ++ hrtimer_cancel(&port->vdm_discovery_timer); ++ kthread_cancel_work_sync(&port->vdm_discovery_work); ++} ++ ++static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) + { + if (delay_ms) { +- hrtimer_start(&port->send_discover_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); ++ hrtimer_start(&port->vdm_discovery_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { +- hrtimer_cancel(&port->send_discover_timer); +- kthread_queue_work(port->wq, &port->send_discover_work); ++ hrtimer_cancel(&port->vdm_discovery_timer); ++ kthread_queue_work(port->wq, &port->vdm_discovery_work); + } + } + +@@ -1773,16 +1973,11 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, + WARN_ON(!mutex_is_locked(&port->lock)); + + /* If is sending discover_identity, handle received message first */ +- if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) { +- if (tx_sop_type == TCPC_TX_SOP_PRIME) +- port->send_discover_prime = true; +- else +- port->send_discover = true; +- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); +- } else { ++ if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) ++ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); ++ else + /* Make sure we are not still processing a previous VDM packet */ + WARN_ON(port->vdm_state > VDM_STATE_DONE); +- } + + port->vdo_count = cnt + 1; + port->vdo_data[0] = header; +@@ -1805,8 +2000,7 @@ static void tcpm_queue_vdm_work(struct kthread_work *work) + struct tcpm_port *port = event->port; + + mutex_lock(&port->lock); +- if (port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) { ++ if (!tcpm_can_send_vdm(port->state)) { + tcpm_log_force(port, "dropping altmode_vdm_event"); + goto port_unlock; + } +@@ -2034,7 +2228,7 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) + if (!port->partner) + return; + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_partner_register_altmode(port->partner, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { +@@ -2052,9 +2246,10 @@ static void tcpm_register_plug_altmodes(struct tcpm_port *port) + struct typec_altmode *altmode; + int i; + +- typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes); ++ typec_plug_set_num_altmodes(port->plug_prime, ++ min(modep->altmodes, ALTMODE_DISCOVERY_MAX)); + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_plug_register_altmode(port->plug_prime, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { +@@ -2154,6 +2349,19 @@ static bool tcpm_cable_vdm_supported(struct tcpm_port *port) + tcpm_can_communicate_sop_prime(port); + } + ++static void tcpm_update_vdm_discovery_state(struct tcpm_port *port, ++ enum vdm_discovery_states new_state) ++{ ++ enum vdm_discovery_states old_state = port->vdm_discovery_state; ++ ++ if (old_state != new_state) ++ tcpm_log_force(port, "vdm discovery state changed: %s -> %s", ++ vdm_discovery_state_strings[old_state], ++ vdm_discovery_state_strings[new_state]); ++ ++ port->vdm_discovery_state = new_state; ++} ++ + static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + enum tcpm_transmit_type rx_sop_type, + enum tcpm_transmit_type *response_tx_sop_type) +@@ -2171,6 +2379,7 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(svid, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_MODES); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + +@@ -2179,10 +2388,12 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(USB_SID_PD, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_SVID); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + modep = &port->mode_data_prime; + modep->svid_index++; +@@ -2193,11 +2404,13 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(svid, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_MODES); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_MODES); + return 1; + } + + tcpm_register_plug_altmodes(port); + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + return 0; +@@ -2376,18 +2589,18 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + typec_cable_set_svdm_version(port->cable, + svdm_version); + } ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_IDENT); ++ + /* 6.4.4.3.1 */ + svdm_consume_identity(port, p, cnt); + /* Attempt Vconn swap, delay SOP' discovery if necessary */ + if (tcpm_attempt_vconn_swap_discovery(port)) { +- port->send_discover_prime = true; + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (!ret) + return 0; + /* Cannot perform Vconn swap */ + port->upcoming_state = INVALID_STATE; +- port->send_discover_prime = false; + } + + /* +@@ -2398,7 +2611,6 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + if (IS_ERR_OR_NULL(port->cable) && + tcpm_can_communicate_sop_prime(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; +- port->send_discover_prime = true; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_IDENT); +@@ -2426,6 +2638,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + return 0; + } ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, +@@ -2445,16 +2658,27 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + rlen = 1; + } else { + if (rx_sop_type == TCPC_TX_SOP) { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_PARTNER_SVIDS); + if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; ++ } else { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_COMPLETE); + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_CABLE_SVIDS); + if (modep_prime->nsvids) { + response[0] = VDO(modep_prime->svids[0], 1, + svdm_version, CMD_DISCOVER_MODES); + rlen = 1; ++ } else { ++ tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_COMPLETE); + } + } + } +@@ -2505,8 +2729,13 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + case CMDT_RSP_NAK: + tcpm_ams_finish(port); + switch (cmd) { ++ /* ++ * The cable is not allowed to respond with NAK so this must've happened over SOP ++ */ + case CMD_DISCOVER_IDENT: + case CMD_DISCOVER_SVID: ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + break; + case CMD_DISCOVER_MODES: +@@ -2686,44 +2915,6 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, + port->vdm_sm_running = false; + } + +-static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, +- const u32 *data, int count, enum tcpm_transmit_type tx_sop_type) +-{ +- int svdm_version; +- u32 header; +- +- switch (tx_sop_type) { +- case TCPC_TX_SOP_PRIME: +- /* +- * If the port partner is discovered, then the port partner's +- * SVDM Version will be returned +- */ +- svdm_version = typec_get_cable_svdm_version(port->typec_port); +- if (svdm_version < 0) +- svdm_version = SVDM_VER_MAX; +- break; +- case TCPC_TX_SOP: +- svdm_version = typec_get_negotiated_svdm_version(port->typec_port); +- if (svdm_version < 0) +- return; +- break; +- default: +- svdm_version = typec_get_negotiated_svdm_version(port->typec_port); +- if (svdm_version < 0) +- return; +- break; +- } +- +- if (WARN_ON(count > VDO_MAX_SIZE - 1)) +- count = VDO_MAX_SIZE - 1; +- +- /* set VDM header with VID & CMD */ +- header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? +- 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), +- svdm_version, cmd); +- tcpm_queue_vdm(port, header, data, count, tx_sop_type); +-} +- + static unsigned int vdm_ready_timeout(u32 vdm_hdr) + { + unsigned int timeout; +@@ -2769,8 +2960,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + * if there's traffic or we're not in PDO ready state don't send + * a VDM. + */ +- if (port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) { ++ if (!tcpm_can_send_vdm(port->state)) { + port->vdm_sm_running = false; + break; + } +@@ -2780,22 +2970,10 @@ static void vdm_run_state_machine(struct tcpm_port *port) + switch (PD_VDO_CMD(vdo_hdr)) { + case CMD_DISCOVER_IDENT: + res = tcpm_ams_start(port, DISCOVER_IDENTITY); +- if (res == 0) { +- switch (port->tx_sop_type) { +- case TCPC_TX_SOP_PRIME: +- port->send_discover_prime = false; +- break; +- case TCPC_TX_SOP: +- port->send_discover = false; +- break; +- default: +- port->send_discover = false; +- break; +- } +- } else if (res == -EAGAIN) { ++ if (res == -EAGAIN) { + port->vdo_data[0] = 0; +- mod_send_discover_delayed_work(port, +- SEND_DISCOVER_RETRY_MS); ++ mod_vdm_discovery_delayed_work(port, ++ SEND_DISCOVERY_VDM_RETRY_MS); + } + break; + case CMD_DISCOVER_SVID: +@@ -2853,6 +3031,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_ams_finish(port); ++ port->vdo_data[0] = 0; + port->vdm_state = VDM_STATE_DONE; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + /* +@@ -2869,6 +3048,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + tcpm_ams_finish(port); + } else { + tcpm_ams_finish(port); ++ port->vdo_data[0] = 0; + if (port->tx_sop_type == TCPC_TX_SOP) + break; + /* Handle SOP' Transmission Errors */ +@@ -2878,11 +3058,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) + * discovery process on SOP only. + */ + case CMD_DISCOVER_IDENT: +- port->vdo_data[0] = 0; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version( + port->typec_port), + CMD_DISCOVER_SVID); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + tcpm_queue_vdm(port, response[0], &response[1], + 0, TCPC_TX_SOP); + break; +@@ -2892,9 +3072,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) + */ + case CMD_DISCOVER_SVID: + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + case CMD_DISCOVER_MODES: + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + default: + break; +@@ -3807,7 +3989,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3823,7 +4006,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3832,7 +4016,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + } + break; + case PD_CTRL_VCONN_SWAP: +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3891,6 +4076,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + { + enum pd_ext_msg_type type = pd_header_type_le(msg->header); + unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); ++ const struct pd_chunked_ext_message_data *ext_msg = &msg->ext_msg; + + /* stopping VDM state machine if interrupted by other Messages */ + if (tcpm_vdm_ams(port)) { +@@ -3899,7 +4085,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + mod_vdm_delayed_work(port, 0); + } + +- if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) { ++ if (!(le16_to_cpu(ext_msg->header) & PD_EXT_HDR_CHUNKED)) { + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + tcpm_log(port, "Unchunked extended messages unsupported"); + return; +@@ -3924,9 +4110,25 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + NONE_AMS, 0); + } + break; +- case PD_EXT_SOURCE_CAP_EXT: +- case PD_EXT_GET_BATT_CAP: + case PD_EXT_GET_BATT_STATUS: ++ if (data_size >= 1) { ++ port->batt_request_id = ext_msg->data[0]; ++ tcpm_pd_handle_msg(port, PD_MSG_DATA_BATT_STATUS, ++ GETTING_BATTERY_STATUS); ++ } else { ++ tcpm_set_state(port, SOFT_RESET_SEND, 0); ++ } ++ break; ++ case PD_EXT_GET_BATT_CAP: ++ if (data_size >= 1) { ++ port->batt_request_id = ext_msg->data[0]; ++ tcpm_pd_handle_msg(port, PD_MSG_EXT_BATT_CAP, ++ GETTING_BATTERY_CAPABILITIES); ++ } else { ++ tcpm_set_state(port, SOFT_RESET_SEND, 0); ++ } ++ break; ++ case PD_EXT_SOURCE_CAP_EXT: + case PD_EXT_BATT_CAP: + case PD_EXT_GET_MANUFACTURER_INFO: + case PD_EXT_MANUFACTURER_INFO: +@@ -4137,6 +4339,22 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) + ret); + tcpm_ams_finish(port); + break; ++ case PD_MSG_DATA_BATT_STATUS: ++ ret = tcpm_pd_send_batt_status(port); ++ if (ret) ++ tcpm_log(port, ++ "Failed to send battery status ret=%d", ++ ret); ++ tcpm_ams_finish(port); ++ break; ++ case PD_MSG_EXT_BATT_CAP: ++ ret = tcpm_pd_send_batt_cap(port); ++ if (ret) ++ tcpm_log(port, ++ "Failed to send battery cap ret=%d", ++ ret); ++ tcpm_ams_finish(port); ++ break; + default: + break; + } +@@ -4853,8 +5071,7 @@ static int tcpm_src_attach(struct tcpm_port *port) + port->partner = NULL; + + port->attached = true; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + +@@ -4896,11 +5113,11 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port) + struct pd_mode_data *modep_prime = &port->mode_data_prime; + int i; + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->partner_altmode[i]); + port->partner_altmode[i] = NULL; + } +- for (i = 0; i < modep_prime->altmodes; i++) { ++ for (i = 0; i < modep_prime->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->plug_prime_altmode[i]); + port->plug_prime_altmode[i] = NULL; + } +@@ -4931,6 +5148,8 @@ static void tcpm_reset_port(struct tcpm_port *port) + port->in_ams = false; + port->ams = NONE_AMS; + port->vdm_sm_running = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); ++ mod_vdm_discovery_cancel_delayed_work(port); + tcpm_unregister_altmodes(port); + tcpm_typec_disconnect(port); + port->attached = false; +@@ -5014,8 +5233,7 @@ static int tcpm_snk_attach(struct tcpm_port *port) + port->partner = NULL; + + port->attached = true; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + } +@@ -5422,16 +5640,11 @@ static void run_state_machine(struct tcpm_port *port) + * as well. + */ + if (port->explicit_contract) { +- if (port->send_discover_prime) { +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- } else { +- port->tx_sop_type = TCPC_TX_SOP; ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); +- } +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + } else { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + /* +@@ -5814,16 +6027,11 @@ static void run_state_machine(struct tcpm_port *port) + * as well. + */ + if (port->explicit_contract) { +- if (port->send_discover_prime) { +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- } else { +- port->tx_sop_type = TCPC_TX_SOP; ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); +- } +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + } else { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + power_supply_changed(port->psy); +@@ -5869,8 +6077,8 @@ static void run_state_machine(struct tcpm_port *port) + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_unregister_altmodes(port); + port->nr_sink_caps = 0; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); ++ mod_vdm_discovery_cancel_delayed_work(port); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, + PD_T_PS_HARD_RESET); +@@ -6017,25 +6225,15 @@ static void run_state_machine(struct tcpm_port *port) + /* DR_Swap states */ + case DR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); +- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { +- port->send_discover = true; +- port->send_discover_prime = false; +- } + tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case DR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); +- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { +- port->send_discover = true; +- port->send_discover_prime = false; +- } + tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); + break; + case DR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); +- port->send_discover = false; +- port->send_discover_prime = false; + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; +@@ -6047,6 +6245,8 @@ static void run_state_machine(struct tcpm_port *port) + else + tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, + TYPEC_HOST); ++ if (port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20) ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; +@@ -6330,9 +6530,7 @@ static void run_state_machine(struct tcpm_port *port) + + /* Cable states */ + case SRC_VDM_IDENTITY_REQUEST: +- port->send_discover_prime = true; +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + port->upcoming_state = SRC_SEND_CAPABILITIES; + break; + +@@ -7052,8 +7250,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work) + goto unlock; + + /* Send when the state machine is idle */ +- if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover || +- port->send_discover_prime) ++ if (port->state != SNK_READY || port->vdm_sm_running || ++ port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + goto resched; + + port->upcoming_state = GET_SINK_CAP; +@@ -7070,29 +7268,160 @@ static void tcpm_enable_frs_work(struct kthread_work *work) + mutex_unlock(&port->lock); + } + +-static void tcpm_send_discover_work(struct kthread_work *work) ++static void tcpm_vdm_discovery_work(struct kthread_work *work) + { +- struct tcpm_port *port = container_of(work, struct tcpm_port, send_discover_work); ++ struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_discovery_work); ++ enum tcpm_transmit_type tx_sop_type = TCPC_TX_SOP; ++ struct typec_port *typec = port->typec_port; ++ struct pd_mode_data *modep, *modep_prime; ++ u32 msg[2] = { }; ++ int svdm_version; + + mutex_lock(&port->lock); +- /* No need to send DISCOVER_IDENTITY anymore */ +- if (!port->send_discover && !port->send_discover_prime) +- goto unlock; + +- if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_log_force(port, "%s state [%s]", __func__, ++ vdm_discovery_state_strings[port->vdm_discovery_state]); ++ ++ /* No need to perform work if Discovery process is complete */ ++ if (port->vdm_discovery_state == VDM_DISCOVERY_COMPLETE) + goto unlock; +- } + + /* Retry if the port is not idle */ +- if ((port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) { +- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); ++ if (!tcpm_can_send_vdm(port->state) || port->vdm_sm_running) { ++ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); + goto unlock; + } + +- tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type); ++ modep = &port->mode_data; ++ modep_prime = &port->mode_data_prime; ++ ++ svdm_version = typec_get_negotiated_svdm_version(typec); ++ ++ switch (port->vdm_discovery_state) { ++ /* ++ * The port has not received a Discover Identity response from the port partner. ++ * ++ * 1. The port will send Discover Identity to the partner over SOP in the SRC_READY and ++ * SNK_READY states if there is an explicit contract ++ * 2. The port will send Discover Identity to the cable over SOP' in the ++ * SRC_VDM_IDENTITY_REQUEST state if capable of doing so. ++ */ ++ case VDM_DISCOVERY_UNKNOWN: ++ /* Can't send Discover Identity, VDM discovery is complete */ ++ if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ ++ if (port->state == SRC_VDM_IDENTITY_REQUEST) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = SVDM_VER_MAX; ++ } ++ ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); ++ break; ++ /* ++ * The port has received a Discover Identity ACK from the port partner. ++ * ++ * 1. The port will send Discover Identity to the cable over SOP' in the SRC_READY and ++ * SNK_READY states if it did not previously discover the cable but is capable of doing ++ * so. ++ * 2. The port will send Discover SVIDs to the partner over SOP in the SRC_READY and ++ * SNK_READY states otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_IDENT: ++ if (tcpm_can_communicate_sop_prime(port) && !port->cable) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); ++ } else { ++ if (tcpm_can_communicate_sop_prime(port)) ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ } ++ break; ++ /* ++ * The port has received a Discover Identity ACK from the cable. ++ * ++ * 1. The port will send Discover SVIDs to the partner over SOP. ++ */ ++ case VDM_DISCOVERY_CABLE_IDENT: ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ break; ++ /* ++ * The port has received a Discover SVIDs ACK from the partner or the last SVIDs supported ++ * by the partner. ++ * ++ * 1. The port will send Discover Modes for the first SVID over SOP if the partner supports ++ * modal operation and valid SVIDs were registered. ++ * 2. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_SVIDS: ++ if (modep->nsvids && supports_modal(port)) { ++ msg[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); ++ } else { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover Modes ACK from the partner for any mode. ++ * ++ * 1. The port will send Discover Modes for the next SVID that has not been discovered to ++ * the port partner over SOP. ++ * 2. The port will send Discover SVIDs over SOP' if the port can communicate over SOP' ++ * and the cable supports VDMs. ++ * 3. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_MODES: ++ /* Not all modes have been discovered yet */ ++ if (modep->svid_index < modep->nsvids) { ++ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, ++ CMD_DISCOVER_MODES); ++ } else if (tcpm_can_communicate_sop_prime(port) && tcpm_cable_vdm_supported(port)) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ } else { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover SVIDs ACK from the cable over SOP'. ++ * ++ * 1. The port will send Discover Modes for the first SVID over SOP'. ++ */ ++ case VDM_DISCOVERY_CABLE_SVIDS: ++ if (modep_prime->nsvids) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); ++ } else { ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover Modes ACK from the cable for any mode. ++ * ++ * 1. The port will send Discover Modes for the next SVID that has not been discovered to ++ * the cable over SOP'. ++ */ ++ case VDM_DISCOVERY_CABLE_MODES: ++ if (modep_prime->svid_index < modep_prime->nsvids) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, ++ CMD_DISCOVER_MODES); ++ } else { ++ goto unlock; ++ } ++ break; ++ default: ++ goto unlock; ++ } ++ ++ if (svdm_version >= 0) ++ tcpm_queue_vdm(port, msg[0], &msg[1], 0, tx_sop_type); + + unlock: + mutex_unlock(&port->lock); +@@ -8508,12 +8837,12 @@ static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer) + return HRTIMER_NORESTART; + } + +-static enum hrtimer_restart send_discover_timer_handler(struct hrtimer *timer) ++static enum hrtimer_restart vdm_discovery_timer_handler(struct hrtimer *timer) + { +- struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer); ++ struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_discovery_timer); + + if (port->registered) +- kthread_queue_work(port->wq, &port->send_discover_work); ++ kthread_queue_work(port->wq, &port->vdm_discovery_work); + return HRTIMER_NORESTART; + } + +@@ -8547,14 +8876,14 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) + kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work); + kthread_init_work(&port->event_work, tcpm_pd_event_handler); + kthread_init_work(&port->enable_frs, tcpm_enable_frs_work); +- kthread_init_work(&port->send_discover_work, tcpm_send_discover_work); ++ kthread_init_work(&port->vdm_discovery_work, tcpm_vdm_discovery_work); + hrtimer_setup(&port->state_machine_timer, state_machine_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + hrtimer_setup(&port->vdm_state_machine_timer, vdm_state_machine_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_setup(&port->enable_frs_timer, enable_frs_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); +- hrtimer_setup(&port->send_discover_timer, send_discover_timer_handler, CLOCK_MONOTONIC, ++ hrtimer_setup(&port->vdm_discovery_timer, vdm_discovery_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + spin_lock_init(&port->pd_event_lock); +@@ -8649,11 +8978,12 @@ void tcpm_unregister_port(struct tcpm_port *port) + port->registered = false; + kthread_destroy_worker(port->wq); + +- hrtimer_cancel(&port->send_discover_timer); ++ hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); + hrtimer_cancel(&port->vdm_state_machine_timer); + hrtimer_cancel(&port->state_machine_timer); + ++ power_supply_put_system_batteries(port->fixed_batt, port->fixed_batt_cnt); + tcpm_reset_port(port); + + tcpm_port_unregister_pd(port); +diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c +--- a/drivers/usb/typec/tipd/core.c ++++ b/drivers/usb/typec/tipd/core.c +@@ -129,6 +129,7 @@ enum { + TPS_MODE_BIST, + TPS_MODE_DISC, + TPS_MODE_PTCH, ++ TPS_MODE_APP1, + }; + + static const char *const modes[] = { +@@ -137,6 +138,7 @@ static const char *const modes[] = { + [TPS_MODE_BIST] = "BIST", + [TPS_MODE_DISC] = "DISC", + [TPS_MODE_PTCH] = "PTCH", ++ [TPS_MODE_APP1] = "APP1", + }; + + /* Unrecognized commands will be replaced with "!CMD" */ +@@ -158,6 +160,7 @@ struct tipd_data { + int (*init)(struct tps6598x *tps); + int (*switch_power_state)(struct tps6598x *tps, u8 target_state); + bool (*read_data_status)(struct tps6598x *tps); ++ bool (*read_power_status)(struct tps6598x *tps); + int (*reset)(struct tps6598x *tps); + int (*connect)(struct tps6598x *tps, u32 status); + }; +@@ -629,6 +632,34 @@ static bool tps6598x_read_power_status(struct tps6598x *tps) + return true; + } + ++/* ++ * TPS66993 deprecated Power_Status register (0x3F). BC1.2 is not supported ++ * and the remaining bits are redundant with STATUS register (0x1A). ++ * Synthesize pwr_status from the already-read STATUS register. ++ */ ++static bool tps66993_read_power_status(struct tps6598x *tps) ++{ ++ u16 pwr_status = 0; ++ ++ /* Same masks as TPS_POWER_STATUS_CONNECTION() / SOURCESINK() / PWROPMODE() in tps6598x.h */ ++ if (tps->status & TPS_STATUS_PLUG_PRESENT) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_CONNECTION_MASK, 1); ++ ++ /* SOURCESINK: 1=sink; STATUS.PortRole 1=source, opposite convention */ ++ if (!TPS_STATUS_TO_TYPEC_PORTROLE(tps->status)) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_SOURCESINK_MASK, 1); ++ ++ if (TPS_STATUS_VBUS_STATUS(tps->status) == TPS_STATUS_VBUS_STATUS_PD) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, ++ TPS_POWER_STATUS_TYPEC_CURRENT_PD); ++ ++ tps->pwr_status = pwr_status; ++ ++ tps->data->trace_power_status(pwr_status); ++ ++ return true; ++} ++ + static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) + { + int ret; +@@ -906,7 +937,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) + goto err_unlock; + + if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unlock; + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { + if (tps6598x_read_partner_identity(tps)) { +@@ -961,7 +992,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_POWER_STATUS_UPDATE) +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) +@@ -1034,17 +1065,25 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) + if (!tps6598x_read_status(tps, &status)) + goto err_unlock; + ++ tps->status = status; ++ + if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unlock; + + if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) + if (!tps->data->read_data_status(tps)) + goto err_unlock; + +- /* Handle plug insert or removal */ +- if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) ++ /* ++ * Refresh power status before connect - needed for TPS66993 which ++ * synthesizes pwr_status from STATUS and never gets POWER_STATUS_UPDATE. ++ */ ++ if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) { ++ if (!tps->data->read_power_status(tps)) ++ goto err_unlock; + tps6598x_handle_plug_event(tps, status); ++ } + + err_unlock: + mutex_unlock(&tps->lock); +@@ -1080,6 +1119,7 @@ static int tps6598x_check_mode(struct tps6598x *tps) + + switch (ret) { + case TPS_MODE_APP: ++ case TPS_MODE_APP1: + case TPS_MODE_PTCH: + return ret; + case TPS_MODE_BOOT: +@@ -1753,7 +1793,7 @@ static int tps6598x_probe(struct i2c_client *client) + struct tps6598x *tps; + struct fwnode_handle *fwnode; + u32 status; +- u32 vid; ++ u32 vid = 0; + int ret; + + data = i2c_get_match_data(client); +@@ -1781,8 +1821,11 @@ static int tps6598x_probe(struct i2c_client *client) + + if (!device_is_compatible(tps->dev, "ti,tps25750")) { + ret = tps6598x_read32(tps, TPS_REG_VID, &vid); +- if (ret < 0 || !vid) ++ if (ret < 0 || !vid) { ++ dev_err(tps->dev, "failed to read vendor ID: %d, vid: %#x\n", ++ ret, vid); + return -ENODEV; ++ } + } + + /* +@@ -1818,6 +1861,8 @@ static int tps6598x_probe(struct i2c_client *client) + goto err_clear_mask; + } + ++ tps->status = status; ++ + /* + * This fwnode has a "compatible" property, but is never populated as a + * struct device. Instead we simply parse it to read the properties. +@@ -1845,7 +1890,7 @@ static int tps6598x_probe(struct i2c_client *client) + + if (status & TPS_STATUS_PLUG_PRESENT) { + ret = -EINVAL; +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unregister_port; + if (!tps->data->read_data_status(tps)) + goto err_unregister_port; +@@ -1860,7 +1905,7 @@ static int tps6598x_probe(struct i2c_client *client) + IRQF_SHARED | IRQF_ONESHOT, + dev_name(&client->dev), tps); + } else { +- dev_warn(tps->dev, "Unable to find the interrupt, switching to polling\n"); ++ dev_dbg(tps->dev, "no IRQ specified, using polling mode\n"); + INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work); + queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, + msecs_to_jiffies(POLL_INTERVAL)); +@@ -1987,6 +2032,7 @@ static const struct tipd_data cd321x_data = { + .trace_status = trace_tps6598x_status, + .init = cd321x_init, + .read_data_status = cd321x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, + .reset = cd321x_reset, + .switch_power_state = cd321x_switch_power_state, + .connect = cd321x_connect, +@@ -2006,6 +2052,25 @@ static const struct tipd_data tps6598x_data = { + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, ++ .reset = tps6598x_reset, ++ .connect = tps6598x_connect, ++}; ++ ++static const struct tipd_data tps66993_data = { ++ .irq_handler = tps6598x_interrupt, ++ .irq_mask1 = TPS_REG_INT_DATA_STATUS_UPDATE | ++ TPS_REG_INT_PLUG_EVENT, ++ .tps_struct_size = sizeof(struct tps6598x), ++ .register_port = tps6598x_register_port, ++ .unregister_port = tps6598x_unregister_port, ++ .trace_data_status = trace_tps6598x_data_status, ++ .trace_power_status = trace_tps6598x_power_status, ++ .trace_status = trace_tps6598x_status, ++ .apply_patch = tps6598x_apply_patch, ++ .init = tps6598x_init, ++ .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps66993_read_power_status, + .reset = tps6598x_reset, + .connect = tps6598x_connect, + }; +@@ -2024,12 +2089,14 @@ static const struct tipd_data tps25750_data = { + .apply_patch = tps25750_apply_patch, + .init = tps25750_init, + .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, + .reset = tps25750_reset, + .connect = tps6598x_connect, + }; + + static const struct of_device_id tps6598x_of_match[] = { + { .compatible = "ti,tps6598x", &tps6598x_data}, ++ { .compatible = "ti,tps66993", &tps66993_data}, + { .compatible = "apple,cd321x", &cd321x_data}, + { .compatible = "ti,tps25750", &tps25750_data}, + {} +diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h +--- a/drivers/usb/typec/tipd/tps6598x.h ++++ b/drivers/usb/typec/tipd/tps6598x.h +@@ -142,9 +142,13 @@ + #define TPS_SYSTEM_POWER_STATE_S4 0x04 + #define TPS_SYSTEM_POWER_STATE_S5 0x05 + +-/* TPS_REG_POWER_STATUS bits */ +-#define TPS_POWER_STATUS_CONNECTION(x) TPS_FIELD_GET(BIT(0), (x)) +-#define TPS_POWER_STATUS_SOURCESINK(x) TPS_FIELD_GET(BIT(1), (x)) ++/* TPS_REG_POWER_STATUS bits (masks shared by TPS_FIELD_GET accessors and FIELD_PREP) */ ++#define TPS_POWER_STATUS_CONNECTION_MASK BIT(0) ++#define TPS_POWER_STATUS_SOURCESINK_MASK BIT(1) ++#define TPS_POWER_STATUS_CONNECTION(x) \ ++ TPS_FIELD_GET(TPS_POWER_STATUS_CONNECTION_MASK, (x)) ++#define TPS_POWER_STATUS_SOURCESINK(x) \ ++ TPS_FIELD_GET(TPS_POWER_STATUS_SOURCESINK_MASK, (x)) + #define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x)) + + #define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2) +diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c +--- a/drivers/usb/typec/ucsi/debugfs.c ++++ b/drivers/usb/typec/ucsi/debugfs.c +@@ -82,8 +82,8 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used) + if (ucsi->debugfs->status) + return ucsi->debugfs->status; + +- seq_printf(s, "0x%016llx%016llx\n", ucsi->debugfs->response.high, +- ucsi->debugfs->response.low); ++ seq_printf(s, "0x%016llx%016llx%016llx\n", ucsi->debugfs->response.ext, ++ ucsi->debugfs->response.high, ucsi->debugfs->response.low); + return 0; + } + DEFINE_SHOW_ATTRIBUTE(ucsi_resp); +diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1102,8 +1102,8 @@ static int ucsi_register_plug(struct ucsi_connector *con) + plug = typec_register_plug(con->cable, &desc); + if (IS_ERR(plug)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register plug (%ld)\n", con->num, +- PTR_ERR(plug)); ++ "con%d: failed to register plug (%pe)\n", con->num, ++ plug); + return PTR_ERR(plug); + } + +@@ -1161,8 +1161,8 @@ static int ucsi_register_cable(struct ucsi_connector *con) + cable = typec_register_cable(con->port, &desc); + if (IS_ERR(cable)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register cable (%ld)\n", con->num, +- PTR_ERR(cable)); ++ "con%d: failed to register cable (%pe)\n", con->num, ++ cable); + return PTR_ERR(cable); + } + +@@ -1291,8 +1291,8 @@ static int ucsi_register_partner(struct ucsi_connector *con) + partner = typec_register_partner(con->port, &desc); + if (IS_ERR(partner)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register partner (%ld)\n", con->num, +- PTR_ERR(partner)); ++ "con%d: failed to register partner (%pe)\n", con->num, ++ partner); + return PTR_ERR(partner); + } + +@@ -2221,7 +2221,7 @@ static void ucsi_init_work(struct work_struct *work) + return; + } + +- queue_delayed_work(system_long_wq, &ucsi->work, ++ queue_delayed_work(system_dfl_long_wq, &ucsi->work, + UCSI_ROLE_SWITCH_INTERVAL); + } + } +@@ -2345,7 +2345,7 @@ int ucsi_register(struct ucsi *ucsi) + UCSI_BCD_GET_MINOR(ucsi->version), + UCSI_BCD_GET_SUBMINOR(ucsi->version)); + +- queue_delayed_work(system_long_wq, &ucsi->work, 0); ++ queue_delayed_work(system_dfl_long_wq, &ucsi->work, 0); + + ucsi_debugfs_register(ucsi); + return 0; +diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h +--- a/drivers/usb/typec/ucsi/ucsi.h ++++ b/drivers/usb/typec/ucsi/ucsi.h +@@ -466,6 +466,7 @@ struct ucsi_debugfs_entry { + struct ucsi_data { + u64 low; + u64 high; ++ u64 ext; + } response; + int status; + u8 message_out[MESSAGE_OUT_MAX_LEN]; +diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h +--- a/include/linux/power_supply.h ++++ b/include/linux/power_supply.h +@@ -806,11 +806,26 @@ extern int power_supply_reg_notifier(struct notifier_block *nb); + extern void power_supply_unreg_notifier(struct notifier_block *nb); + #if IS_ENABLED(CONFIG_POWER_SUPPLY) + extern struct power_supply *power_supply_get_by_name(const char *name); ++extern int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys); ++extern void power_supply_put_system_batteries(struct power_supply **psys, int count); + extern void power_supply_put(struct power_supply *psy); + #else + static inline void power_supply_put(struct power_supply *psy) {} + static inline struct power_supply *power_supply_get_by_name(const char *name) + { return NULL; } ++static inline int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys) ++{ ++ if (psys) ++ *psys = NULL; ++ return 0; ++} ++ ++static inline void power_supply_put_system_batteries(struct power_supply **psys, ++ int count) ++{ ++} + #endif + extern struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode, + const char *property); +diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h +--- a/include/linux/usb/pd.h ++++ b/include/linux/usb/pd.h +@@ -106,6 +106,9 @@ enum pd_ext_msg_type { + #define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) + ++#define PD_HEADER_EXT_LE(type, pwr, data, rev, id, cnt) \ ++ cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (1))) ++ + static inline unsigned int pd_header_cnt(u16 header) + { + return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK; +@@ -219,6 +222,25 @@ static inline u8 count_chunked_data_objs(u32 size) + return ((size / 4) + (size % 4 ? 1 : 0)); + } + ++/** ++ * batt_cap_ext_msg - Battery capability extended PD message ++ * @vid: Battery Vendor ID (assigned by USB-IF) ++ * @pid: Battery Product ID (assigned by battery or device vendor) ++ * @batt_design_cap: Battery design capacity in 0.1Wh ++ * @batt_last_chg_cap: Battery last full charge capacity in 0.1Wh ++ * @batt_type: Battery Type. bit0 when set indicates invalid battery reference. ++ * Rest of the bits are reserved. ++ */ ++struct batt_cap_ext_msg { ++ __le16 vid; ++ __le16 pid; ++ __le16 batt_design_cap; ++ __le16 batt_last_chg_cap; ++ u8 batt_type; ++} __packed; ++ ++#define BATT_CAP_BATT_TYPE_INVALID_REF BIT(0) ++ + /* Sink Caps Extended Data Block Version */ + #define SKEDB_VER_1_0 1 + +@@ -724,4 +746,33 @@ void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct devi + + #endif /* CONFIG_TYPEC */ + ++/* Battery Status Data Object */ ++#define BSDO_PRESENT_CAPACITY GENMASK(31, 16) ++#define BSDO_CHG_STATUS GENMASK(11, 10) ++#define BSDO_BATTERY_PRESENT BIT(9) ++#define BSDO_INVALID_BATTERY_REFERENCE BIT(8) ++ ++/* ++ * Battery Charge Status: Battery Charging Status Values as defined in ++ * "USB PD Spec Rev3.1 Ver1.8", "Table 6-46 Battery Status Data Object (BSDO)". ++ */ ++#define BSDO_BATTERY_INFO_CHARGING 0x0 ++#define BSDO_BATTERY_INFO_DISCHARGING 0x1 ++#define BSDO_BATTERY_INFO_IDLE 0x2 ++#define BSDO_BATTERY_INFO_RSVD 0x3 ++ ++/** ++ * BSDO() - Pack data into Battery Status Data Object format. ++ * @batt_charge: Battery's present state of charge in 0.1WH increment. ++ * @chg_status: Battery charge status. ++ * @batt_present: Indicates that battery is present/attached when set else absent when unset. ++ * @invalid_ref: Indicates that an invalid battery reference was made in the Get_Battery_Status ++ * request. ++ */ ++#define BSDO(batt_charge, chg_status, batt_present, invalid_ref) \ ++ ((FIELD_PREP(BSDO_PRESENT_CAPACITY, batt_charge)) | \ ++ (FIELD_PREP(BSDO_CHG_STATUS, chg_status)) | \ ++ ((batt_present) ? BSDO_BATTERY_PRESENT : 0) | \ ++ ((invalid_ref) ? BSDO_INVALID_BATTERY_REFERENCE : 0)) ++ + #endif /* __LINUX_USB_PD_H */ +diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h +--- a/include/linux/usb/typec.h ++++ b/include/linux/usb/typec.h +@@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable); + struct typec_cable *typec_cable_get(struct typec_port *port); + void typec_cable_put(struct typec_cable *cable); + int typec_cable_is_active(struct typec_cable *cable); ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt); + + struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc); +diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h +--- a/include/uapi/linux/usb/functionfs.h ++++ b/include/uapi/linux/usb/functionfs.h +@@ -25,6 +25,7 @@ enum functionfs_flags { + FUNCTIONFS_EVENTFD = 32, + FUNCTIONFS_ALL_CTRL_RECIP = 64, + FUNCTIONFS_CONFIG0_SETUP = 128, ++ FUNCTIONFS_RW_PROXY_EPS = 256, + }; + + /* Descriptor of an non-audio endpoint */ +@@ -414,4 +415,27 @@ struct usb_functionfs_event { + #define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \ + struct usb_ffs_dmabuf_transfer_req) + ++/* ++ * Enable or disable automatic zero-length packet (ZLP) appending for the ++ * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to ++ * enable. ++ * ++ * When enabled, the kernel will automatically append a ZLP at the end of ++ * a transfer if the payload length is an exact multiple of the endpoint's ++ * max packet size. ++ * ++ * This is useful for compatibility with legacy protocols which require ++ * automatic ZLP appending to data written from userspace. ++ * ++ * This ioctl can only be used on IN endpoints. It can be called at any time ++ * after the FunctionFS instance is active, even before the host has connected ++ * or enabled the endpoint. ++ * ++ * Returns zero on success, or a negative errno value on error: ++ * -ENODEV: The FunctionFS instance is not active. ++ * -EINVAL: The endpoint is not an IN endpoint. ++ * -EFAULT: Invalid user space pointer for the argument. ++ */ ++#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32) ++ + #endif /* _UAPI__LINUX_FUNCTIONFS_H__ */ +diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs +--- a/rust/kernel/usb.rs ++++ b/rust/kernel/usb.rs +@@ -24,11 +24,8 @@ + }; + use core::{ + marker::PhantomData, +- mem::{ +- offset_of, +- MaybeUninit, // +- }, +- ptr::NonNull, ++ mem::offset_of, ++ ptr::NonNull, // + }; + + /// An adapter for the registration of USB drivers. +@@ -130,8 +127,7 @@ pub const fn from_id(vendor: u16, product: u16) -> Self { + match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE as u16, + idVendor: vendor, + idProduct: product, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -143,8 +139,7 @@ pub const fn from_device_ver(vendor: u16, product: u16, bcd_lo: u16, bcd_hi: u16 + idProduct: product, + bcdDevice_lo: bcd_lo, + bcdDevice_hi: bcd_hi, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -155,8 +150,7 @@ pub const fn from_device_info(class: u8, subclass: u8, protocol: u8) -> Self { + bDeviceClass: class, + bDeviceSubClass: subclass, + bDeviceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -167,8 +161,7 @@ pub const fn from_interface_info(class: u8, subclass: u8, protocol: u8) -> Self + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -180,8 +173,7 @@ pub const fn from_device_interface_class(vendor: u16, product: u16, class: u8) - + idVendor: vendor, + idProduct: product, + bInterfaceClass: class, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -193,8 +185,7 @@ pub const fn from_device_interface_protocol(vendor: u16, product: u16, protocol: + idVendor: vendor, + idProduct: product, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -206,8 +197,7 @@ pub const fn from_device_interface_number(vendor: u16, product: u16, number: u8) + idVendor: vendor, + idProduct: product, + bInterfaceNumber: number, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -227,8 +217,7 @@ pub const fn from_device_and_interface_info( + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + } +@@ -393,6 +382,7 @@ fn as_ref(&self) -> &Device { + + // SAFETY: Instances of `Interface` are always reference-counted. + unsafe impl AlwaysRefCounted for Interface { ++ #[inline] + fn inc_ref(&self) { + // SAFETY: The invariants of `Interface` guarantee that `self.as_raw()` + // returns a valid `struct usb_interface` pointer, for which we will +@@ -400,6 +390,7 @@ fn inc_ref(&self) { + unsafe { bindings::usb_get_intf(self.as_raw()) }; + } + ++ #[inline] + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_intf(obj.cast().as_ptr()) } +@@ -444,6 +435,7 @@ fn as_raw(&self) -> *mut bindings::usb_device { + + // SAFETY: Instances of `Device` are always reference-counted. + unsafe impl AlwaysRefCounted for Device { ++ #[inline] + fn inc_ref(&self) { + // SAFETY: The invariants of `Device` guarantee that `self.as_raw()` + // returns a valid `struct usb_device` pointer, for which we will +@@ -451,6 +443,7 @@ fn inc_ref(&self) { + unsafe { bindings::usb_get_dev(self.as_raw()) }; + } + ++ #[inline] + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_dev(obj.cast().as_ptr()) } diff --git a/pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..d35c194e13ab82932efe8510fc405c31be439b05 GIT binary patch literal 594 zcmV-Y0c|3Ka?ftnK4F3IYClIqqZ zq3|HKhD%7Q;pqr5EU9$Gs=T=$7LM*PTYXHY-l^*GzpE_UZN>JfQlMGiDB4OqpR(Z8 zT~ysIuN z`rM`k`XsX$O5Z|~G^MvF0JwdXfIW?>01G9HIKeuWdmI;C_cWlhe=Hf>(k3e6yRK(h z#BCiV@J%)syNSgCgPK@;KESuT4N(w_@5SthgND`w);v62$`SVfO#@|c_`4fw8-2h2 zLxV~2#7y!Y;cAsADq}JK+ZNMEXW(G;z?6jL{nm-^BaI0eKJfsAbM&!Q)9pIN-!XgF z$Lq6i<5U(yaSAJ@lkXcl95(MGtAyOWXzC@Lmyn52pY|mnb*HfIdCJ8hYovqu+i^|} zdb$|D(Vh1%4;a|%86bSsHm0wv&9vqK_QA?Y$Ewu}>D+&J=kmkq)Q(g1yIf^Yux^i7b z_O=|lE??240f%jpS9%Bw*~s^e#Ti~7FKWb&LDlN8*M~asRjJ!V_&ulk2$3*dEv_*imEhcs_params2) << 2; ++ max_segs = BIT(FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2)); + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -4328,11 +4328,16 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + check_interval(urb, ep_ctx); + + /* +- * Check if this starts the isoc data flow. Relies on hw setting ep ctx +- * state after doorbell ring. Consider adding list_empty(td_list) check ++ * Schedule the URB discontiguously if all previous URBs have completed. ++ * XXX core can't tell if completions are pending but not running yet. + */ +- if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) ++ if (list_empty(&ep_ring->td_list) && ++ !hcd_periodic_completion_in_progress(xhci_to_hcd(xhci), urb->ep)) { ++ if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) ++ xhci_dbg(xhci, "Unexpected running ring at isoc stream start, uframe: %d\n", ++ xep->next_uframe); + xep->next_uframe = -1; ++ } + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); + } diff --git a/pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..d93226e63ebc3e80e069bde7240df2387403b79a GIT binary patch literal 594 zcmV-Y0^?w z`U)f4r7_8PUN%~9@GnAd7E7p%{kN#8ocr?|J}NCO6_D;hJt3fp@2+Ua{mBP~T;;Y-3Z!Uz1+<&o@W> zb{;St&U?3$XUhPJ2fsvgHJuyW8iOlj9D|)59Yaz_##x~dItgTYQIwN`KmQ1?gR|YS zdpH`O(D`XPonz=c1c7RfLMLbs3m*LxxNWDP1mkYKgI{@~ci6dz-bMUr+#`MgZrlc) z{~g~HLx|3z=j1Vzd9dRuTi9E19+&L5j z#Iq4cycN>qDl_jHjW^|oS_wO$DN-{3YqMi@)IQEN1)4tLBEMooP<1TG}-d*o#}ZBn)8 gb*g2h7$_%Qq8$)8^2k5h7H8{k4 + #include + #include ++#include + #include ++#include + #include + + #include "tb.h" +@@ -18,6 +20,7 @@ + + #define TB_TIMEOUT 100 /* ms */ + #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */ ++#define TB_PCI_RESCAN_DELAY 300 /* ms */ + + /* + * How many time bandwidth allocation request from graphics driver is +@@ -49,6 +52,13 @@ MODULE_PARM_DESC(asym_threshold, + "threshold (Mb/s) when to Gen 4 switch link symmetry. 0 disables. (default: " + __MODULE_STRING(TB_ASYM_THRESHOLD) ")"); + ++struct tb_pci_rescan { ++ struct delayed_work work; ++ struct kref kref; ++ struct pci_bus *bus; ++ bool stopped; ++}; ++ + /** + * struct tb_cm - Simple Thunderbolt connection manager + * @tunnel_list: List of active tunnels +@@ -67,8 +77,11 @@ struct tb_cm { + bool hotplug_active; + struct delayed_work remove_work; + struct tb_bandwidth_group groups[MAX_GROUPS]; ++ struct tb_pci_rescan *pci_rescan; + }; + ++static struct workqueue_struct *tb_pci_rescan_wq; ++ + static inline struct tb *tcm_to_tb(struct tb_cm *tcm) + { + return ((void *)tcm - sizeof(struct tb)); +@@ -2282,6 +2295,82 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw) + return 0; + } + ++static void tb_pci_rescan_release(struct kref *kref) ++{ ++ struct tb_pci_rescan *rescan = container_of(kref, typeof(*rescan), kref); ++ ++ put_device(&rescan->bus->dev); ++ kfree(rescan); ++} ++ ++static void tb_pci_rescan_work_fn(struct work_struct *work) ++{ ++ struct tb_pci_rescan *rescan = ++ container_of(to_delayed_work(work), typeof(*rescan), work); ++ ++ pci_lock_rescan_remove(); ++ if (!READ_ONCE(rescan->stopped)) ++ pci_rescan_bus(rescan->bus); ++ pci_unlock_rescan_remove(); ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++} ++ ++static void tb_pci_rescan_schedule(struct tb *tb) ++{ ++ struct tb_cm *tcm = tb_priv(tb); ++ struct tb_pci_rescan *rescan = tcm->pci_rescan; ++ ++ if (!rescan) { ++ struct pci_bus *bus; ++ ++ if (!dev_is_pci(tb->nhi->dev)) ++ return; ++ bus = to_pci_dev(tb->nhi->dev)->bus; ++ if (bus->parent) ++ bus = bus->parent; ++ ++ rescan = kzalloc_obj(*rescan); ++ if (!rescan) ++ return; ++ INIT_DELAYED_WORK(&rescan->work, tb_pci_rescan_work_fn); ++ kref_init(&rescan->kref); ++ get_device(&bus->dev); ++ rescan->bus = bus; ++ tcm->pci_rescan = rescan; ++ } ++ ++ kref_get(&rescan->kref); ++ if (mod_delayed_work(tb_pci_rescan_wq, &rescan->work, ++ msecs_to_jiffies(TB_PCI_RESCAN_DELAY))) ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++} ++ ++static void tb_pci_rescan_stop(struct tb_cm *tcm) ++{ ++ struct tb_pci_rescan *rescan = tcm->pci_rescan; ++ ++ if (!rescan) ++ return; ++ ++ WRITE_ONCE(rescan->stopped, true); ++ if (cancel_delayed_work(&rescan->work)) ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++ tcm->pci_rescan = NULL; ++} ++ ++int tb_pci_rescan_init(void) ++{ ++ tb_pci_rescan_wq = alloc_workqueue("thunderbolt_pci_rescan", ++ WQ_FREEZABLE | WQ_UNBOUND, 0); ++ return tb_pci_rescan_wq ? 0 : -ENOMEM; ++} ++ ++void tb_pci_rescan_exit(void) ++{ ++ destroy_workqueue(tb_pci_rescan_wq); ++} ++ + static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) + { + struct tb_port *up, *down, *port; +@@ -2323,6 +2412,7 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) + tb_sw_warn(sw, "failed to connect xHCI\n"); + + list_add_tail(&tunnel->list, &tcm->tunnel_list); ++ tb_pci_rescan_schedule(tb); + return 0; + } + +@@ -2957,6 +3047,7 @@ static void tb_stop(struct tb *tb) + struct tb_tunnel *n; + + cancel_delayed_work(&tcm->remove_work); ++ tb_pci_rescan_stop(tcm); + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { + /* +diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -759,6 +759,8 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, + + struct tb *icm_probe(struct tb_nhi *nhi); + struct tb *tb_probe(struct tb_nhi *nhi); ++int tb_pci_rescan_init(void); ++void tb_pci_rescan_exit(void); + + extern const struct device_type tb_domain_type; + extern const struct device_type tb_retimer_type; diff --git a/pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig b/pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..63eb3c6dc5f7dbc8c79a66d604781d4d6cd1ee75 GIT binary patch literal 594 zcmV-Y0;|}^flD)g9 z=46E%6S5N%8^=ia8w`FeWf_2jgDI`>j{I_`s6-yHzS^YRoMm+DMVQ14wEfH_IS~sG z7o?$TzHc#iCZ80aEyT}qY}8+)rxBs|fFFuTk={=$<4%#y*Hmud9&Iib*@aANY}jqc zTwWrwq zG}zdHpX8tADZ9o=YKBMje8s#JJbtHLBl$VOs;&0_(Oi1M%{c*H5L$03D%`$*{v_1@ zEFxp3t+{PB(U$!hx_%picCU7zb|Lgr5*S;dlI%B{LD2vVE93-Bh667>$=G-Oz_+vQ zCiXbAQ6+8w5E7SxV`upNxr=7o6@B&nlws<)e)^y$313#QA&_Mk?m>wlQ9g-TUG~mg z%+@OzrLL8t+4o&uKoGuMoLF=n6mbq`6C=aCy5b#TE(ZTco}1J8h1pi!a=+ z7S}s!qBCo77tQ;~xg;A29^!*;HV8=#w$L@GH)sm0q2oaa6bxZWx_UDnhi; +- +- if (!nhi->ops->reset_interface) +- return; +- +- guard(mutex)(&tb->lock); +- +- /* The reset clears the ring state so stop the control channel */ +- tb_ctl_stop(tb->ctl); +- nhi->ops->reset_interface(nhi); +- tb_ctl_start(tb->ctl); +-} +- + /** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths +@@ -825,20 +810,11 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + int transmit_path, int transmit_ring, + int receive_path, int receive_ring) + { +- int ret; +- + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + +- ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, ++ return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + transmit_ring, receive_path, receive_ring); +- if (ret) +- return ret; +- +- if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) +- tb_domain_reset_interface(tb); +- +- return 0; + } + + static int disconnect_xdomain(struct device *dev, void *data) +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -1180,32 +1180,6 @@ static void nhi_reset(struct tb_nhi *nhi) + dev_warn(nhi->dev, "timeout resetting host router\n"); + } + +-/** +- * nhi_reset_interface() - Reset the host interface +- * @nhi: Host interface to reset +- * +- * Brings the registers in the memory BAR back to their default state and +- * clears the End-to-End Flow Control state. The caller is responsible for +- * stopping the control channel over the reset because it clears the ring +- * state as well. +- */ +-void nhi_reset_interface(struct tb_nhi *nhi) +-{ +- u32 val; +- +- val = ioread32(nhi->iobase + REG_CAPS); +- /* Only v1 host interfaces implement the reset */ +- if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) +- return; +- +- dev_dbg(nhi->dev, "issuing host interface reset\n"); +- +- iowrite32(REG_HOST_INTERFACE_RESET_RST, +- nhi->iobase + REG_HOST_INTERFACE_RESET); +- /* Wait for tHIReset (10 ms) to complete */ +- usleep_range(10000, 20000); +-} +- + static struct tb *nhi_select_cm(struct tb_nhi *nhi) + { + struct tb *tb; +diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h +--- a/drivers/thunderbolt/nhi.h ++++ b/drivers/thunderbolt/nhi.h +@@ -36,8 +36,6 @@ irqreturn_t nhi_msi(int irq, void *data); + irqreturn_t ring_msix(int irq, void *data); + int nhi_probe(struct tb_nhi *nhi); + void nhi_shutdown(struct tb_nhi *nhi); +-void nhi_reset_interface(struct tb_nhi *nhi); +- + extern const struct dev_pm_ops nhi_pm_ops; + + /** +@@ -54,7 +52,6 @@ extern const struct dev_pm_ops nhi_pm_ops; + * @release_ring_irq: NHI specific interrupt release hook + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook +- * @reset_interface: Resets the host interface + */ + struct tb_nhi_ops { + int (*init)(struct tb_nhi *nhi); +@@ -69,7 +66,6 @@ struct tb_nhi_ops { + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); +- void (*reset_interface)(struct tb_nhi *nhi); + }; + + /* +@@ -120,24 +116,11 @@ struct tb_nhi_ops { + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 + +-#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 +-#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 +-#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b +-#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 +-#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c +-#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d +-#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d +-#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e +- + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + + /* Host interface quirks */ +-#define QUIRK_AUTO_CLEAR_INT BIT(0) +-#define QUIRK_E2E BIT(1) +-#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) ++#define QUIRK_AUTO_CLEAR_INT BIT(0) ++#define QUIRK_E2E BIT(1) + + /* + * Minimal number of vectors when we use MSI-X. Two for control channel +diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h +--- a/drivers/thunderbolt/nhi_regs.h ++++ b/drivers/thunderbolt/nhi_regs.h +@@ -115,10 +115,6 @@ struct ring_desc { + #define REG_CAPS_VERSION_MASK GENMASK(23, 16) + #define REG_CAPS_VERSION_2 0x40 + +-/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ +-#define REG_HOST_INTERFACE_RESET 0x39858 +-#define REG_HOST_INTERFACE_RESET_RST BIT(0) +- + #define REG_DMA_MISC 0x39864 + #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) + #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c +--- a/drivers/thunderbolt/pci.c ++++ b/drivers/thunderbolt/pci.c +@@ -62,27 +62,6 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) + nhi->quirks |= QUIRK_E2E; + break; + } +- } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { +- switch (pdev->device) { +- case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: +- case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: +- /* +- * These AMD hosts may hang the Tx ring when the +- * DMA paths are torn down so they need the host +- * interface reset after each teardown. +- */ +- nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; +- break; +- } + } + } + +@@ -279,7 +258,6 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { + .shutdown = nhi_pci_release_irq, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +- .reset_interface = nhi_reset_interface, + }; + + /* Ice Lake specific NHI operations */ +@@ -463,7 +441,6 @@ static const struct tb_nhi_ops icl_nhi_ops = { + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +- .reset_interface = nhi_reset_interface, + }; + + static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c +--- a/drivers/thunderbolt/quirks.c ++++ b/drivers/thunderbolt/quirks.c +@@ -99,6 +99,9 @@ static const struct tb_quirk tb_quirks[] = { + quirk_usb3_maximum_bandwidth }, + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000, + quirk_usb3_maximum_bandwidth }, ++ /* CLx is unstable on the Anker Prime TB5 dock (DROM 01ea:83b5) */ ++ { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x01ea, 0x83b5, ++ quirk_clx_disable }, + /* + * Block Runtime PM in DP redrive mode for Intel Barlow Ridge host + * controllers. +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -980,6 +980,9 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file) + } + + mutex_unlock(&sdev->lock); ++ ++ /* Stream handles IOCB_NOWAIT just fine */ ++ file->f_mode |= FMODE_NOWAIT; + return 0; + + err_unlock: +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -377,6 +377,10 @@ static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, + + len += sizeof(res->hdr.xd_hdr) / 4; + len -= sizeof(*res) / 4; ++ if (len > TB_XDP_PROPERTIES_MAX_DATA_LENGTH) { ++ ret = -EINVAL; ++ goto err; ++ } + + if (res->offset != req.offset) { + ret = -EINVAL; diff --git a/pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..8e90b6c7a8c2f1e709f9aa3b74e39adde77dc29f GIT binary patch literal 594 zcmV-Y0MPP$9*zDM10uNI%W?Gh;Sg67=*}|R27?*=YUf5UiCqp!#iqWb3%jPL4n~gT zq+2_Bf1+wcw$;>xDeAtN4n7aWz9LgM8#<&8ZHtncNC25cx#l5Q$jYSeoNL}Nj`%7y z$GyLgOVM4k<(|TGIf}NXk+c1}tc7d8jhKMqVWuMj3)WpFC9r?ai?~x9(B?`-5UA`& zmDC@MAORS##*xAF`Ccx_s|+4+wYE}xTI+|{ODz|IuqlN|&kO$TVg5*Rp#U5xTA)JN ze=XB3S!Yw%&i!MwA92^KXNz;JdJ{GPspP@9ha$su-T6PW`$m1B_7ye}0^J8@R5kj@49Y`90R>P9*|>@Q&)9?flJRo(>l)zKj`HXNSgUCm;UIEeb9;+z_OW zE1L~i>@3i@IZin)-QPa~P<$*KL5F=yZxjKol(Bf7GQl(b6nh*uFwGX4WaeN9CJ`pw z;AOZB9WIDWa=;xr7agTRGY-^^#zj<*+{ExYZdR7*?;-c0IAzZKgH5SDpsHl3Ly&d< g38)+rt{0`~3!yf&6U<_f#41DmJBgi#ePA4H8#OKxy#N3J literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch new file mode 100644 index 0000000..2e3ea13 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch @@ -0,0 +1,1673 @@ +diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c +--- a/drivers/usb/cdns3/cdns3-gadget.c ++++ b/drivers/usb/cdns3/cdns3-gadget.c +@@ -3269,6 +3269,10 @@ static void cdns3_gadget_exit(struct cdns *cdns) + usb_del_gadget(&priv_dev->gadget); + devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + ++ /* The works can still be queued until the IRQ is freed. */ ++ cancel_work_sync(&priv_dev->pending_status_wq); ++ cancel_work_sync(&priv_dev->aligned_buf_wq); ++ + cdns3_free_all_eps(priv_dev); + + while (!list_empty(&priv_dev->aligned_buf_list)) { +diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c +--- a/drivers/usb/cdns3/cdns3-pci-wrap.c ++++ b/drivers/usb/cdns3/cdns3-pci-wrap.c +@@ -96,6 +96,11 @@ static int cdns3_pci_probe(struct pci_dev *pdev, + + if (pci_is_enabled(func)) { + wrap = pci_get_drvdata(func); ++ if (!wrap) { ++ dev_err(&pdev->dev, ++ "second function not initialized, retrying\n"); ++ return -EPROBE_DEFER; ++ } + } else { + wrap = kzalloc_obj(*wrap); + if (!wrap) +diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c +--- a/drivers/usb/chipidea/ci_hdrc_tegra.c ++++ b/drivers/usb/chipidea/ci_hdrc_tegra.c +@@ -306,8 +306,10 @@ static int tegra_usb_probe(struct platform_device *pdev) + + pm_runtime_enable(&pdev->dev); + err = pm_runtime_resume_and_get(&pdev->dev); +- if (err) ++ if (err) { ++ pm_runtime_disable(&pdev->dev); + return err; ++ } + + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) + usb->needs_double_reset = true; +diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c +--- a/drivers/usb/class/cdc-acm.c ++++ b/drivers/usb/class/cdc-acm.c +@@ -331,7 +331,6 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) + + spin_lock_irqsave(&acm->read_lock, flags); + acm->ctrlin = newctrl; +- acm->oldcount = acm->iocount; + + if (difference & USB_CDC_SERIAL_STATE_DSR) + acm->iocount.dsr++; +@@ -788,6 +787,9 @@ static void acm_port_shutdown(struct tty_port *port) + usb_autopm_put_interface_async(acm->control); + } + ++ if (acm->disconnected) ++ return; ++ + acm_unpoison_urbs(acm); + + if (acm->quirks & ALWAYS_POLL_CTRL) { +@@ -1027,11 +1029,16 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + +- do { ++ spin_lock_irq(&acm->read_lock); ++ old = acm->iocount; ++ spin_unlock_irq(&acm->read_lock); ++ ++ add_wait_queue(&acm->wioctl, &wait); ++ for (;;) { ++ set_current_state(TASK_INTERRUPTIBLE); ++ + spin_lock_irq(&acm->read_lock); +- old = acm->oldcount; + new = acm->iocount; +- acm->oldcount = new; + spin_unlock_irq(&acm->read_lock); + + if ((arg & TIOCM_DSR) && +@@ -1044,22 +1051,20 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) + old.rng != new.rng) + break; + +- add_wait_queue(&acm->wioctl, &wait); +- set_current_state(TASK_INTERRUPTIBLE); +- schedule(); +- remove_wait_queue(&acm->wioctl, &wait); + if (acm->disconnected) { +- if (arg & TIOCM_CD) +- break; +- else +- rv = -ENODEV; +- } else { +- if (signal_pending(current)) +- rv = -ERESTARTSYS; ++ rv = -ENODEV; ++ break; + } +- } while (!rv); + +- ++ schedule(); ++ ++ if (signal_pending(current)) { ++ rv = -ERESTARTSYS; ++ break; ++ } ++ } ++ __set_current_state(TASK_RUNNING); ++ remove_wait_queue(&acm->wioctl, &wait); + + return rv; + } +diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h +--- a/drivers/usb/class/cdc-acm.h ++++ b/drivers/usb/class/cdc-acm.h +@@ -90,7 +90,6 @@ struct acm { + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ +- struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -73,8 +73,11 @@ + /* + * Give SS hubs 200ms time after wake to train downstream links before + * assuming no port activity and allowing hub to runtime suspend back. ++ * Root hubs have no upstream hub whose wake propagation needs to be ++ * accounted for, so they need less time, use 120ms for them. + */ + #define USB_SS_PORT_U0_WAKE_TIME 200 /* ms */ ++#define USB_SS_RH_PORT_U0_WAKE_TIME 120 /* ms */ + + /* Protect struct usb_device->state and ->children members + * Note: Both are also protected by ->dev.sem, except that ->state can +@@ -1358,7 +1361,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) + + queue_delayed_work(system_power_efficient_wq, + &hub->post_resume_work, +- msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); ++ msecs_to_jiffies(hdev->parent ? ++ USB_SS_PORT_U0_WAKE_TIME : ++ USB_SS_RH_PORT_U0_WAKE_TIME)); + return; + } + +diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c +--- a/drivers/usb/core/message.c ++++ b/drivers/usb/core/message.c +@@ -1337,7 +1337,8 @@ static void remove_intf_ep_devs(struct usb_interface *intf) + * + * Disables the endpoint for URB submission and nukes all pending URBs. + * If @reset_hardware is set then also deallocates hcd/hardware state +- * for the endpoint. ++ * for the endpoint (clearing both ep_in and ep_out pointers for ++ * bidirectional non-ep0 control endpoints). + */ + void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + bool reset_hardware) +@@ -1358,6 +1359,11 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + dev->ep_in[epnum] = NULL; + } + if (ep) { ++ if (reset_hardware && epnum != 0 && ++ usb_endpoint_xfer_control(&ep->desc)) { ++ dev->ep_out[epnum] = NULL; ++ dev->ep_in[epnum] = NULL; ++ } + ep->enabled = 0; + usb_hcd_flush_endpoint(dev, ep); + if (reset_hardware) +diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c +--- a/drivers/usb/dwc2/hcd.c ++++ b/drivers/usb/dwc2/hcd.c +@@ -5082,14 +5082,13 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) + } + + cancel_work_sync(&hsotg->phy_reset_work); +- +- timer_delete(&hsotg->wkp_timer); + } + + static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) + { + /* Turn off all host-specific interrupts */ + dwc2_disable_host_interrupts(hsotg); ++ timer_shutdown_sync(&hsotg->wkp_timer); + + dwc2_hcd_free(hsotg); + } +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -49,6 +49,7 @@ + #define DWC3_ENDPOINTS_NUM 32 + #define DWC3_XHCI_RESOURCES_NUM 2 + #define DWC3_ISOC_MAX_RETRIES 5 ++#define DWC3_ERR_RECOVERY_MAX 3 + + #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ + #define DWC3_EVENT_BUFFERS_SIZE 4096 +@@ -841,6 +842,12 @@ enum dwc3_link_state { + DWC3_LINK_STATE_MASK = 0x0f, + }; + ++enum dwc3_err_state { ++ DWC3_ERR_NONE = 0, ++ DWC3_ERR_RECOVERY, ++ DWC3_ERR_UNRECOVERABLE, ++}; ++ + /* TRB Length, PCM and Status */ + #define DWC3_TRB_SIZE_MASK (0x00ffffff) + #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) +@@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { + /** + * struct dwc3 - representation of our controller + * @drd_work: workqueue used for role swapping ++ * @err_recovery_work: workqueue used for controller error recovery + * @ep0_trb: trb which is used for the ctrl_req + * @bounce: address of bounce buffer + * @setup_buf: used while precessing STD USB requests +@@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { + * @ep0_in_setup: one control transfer is completed and enter setup phase + * @lock: for synchronizing + * @mutex: for mode switching ++ * @connect_mutex: for the pull-up and err_recovery_work + * @dev: pointer to our struct device + * @sysdev: pointer to the DMA-capable device + * @xhci: pointer to our xHCI child +@@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { + * @ep0_next_event: hold the next expected event + * @ep0state: state of endpoint zero + * @link_state: link state ++ * @err_state: current error recovery state. ++ * @err_recovery_count: number of consecutive error recovery attempts until ++ * confirmed healthy and reset to 0 on reset event. + * @speed: device speed (super, high, full, low) + * @hwparams: copy of hwparams registers + * @regset: debugfs pointer to regdump file +@@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { + */ + struct dwc3 { + struct work_struct drd_work; ++ struct work_struct err_recovery_work; + struct dwc3_trb *ep0_trb; + void *bounce; + u8 *setup_buf; +@@ -1203,6 +1216,9 @@ struct dwc3 { + /* mode switching lock */ + struct mutex mutex; + ++ /* serializes pull-up run/stop vs error recovery */ ++ struct mutex connect_mutex; ++ + struct device *dev; + struct device *sysdev; + +@@ -1332,6 +1348,9 @@ struct dwc3 { + enum dwc3_ep0_next ep0_next_event; + enum dwc3_ep0_state ep0state; + enum dwc3_link_state link_state; ++ enum dwc3_err_state err_state; ++ ++ u32 err_recovery_count; + + u16 u2sel; + u16 u2pel; +diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c +--- a/drivers/usb/dwc3/dwc3-am62.c ++++ b/drivers/usb/dwc3/dwc3-am62.c +@@ -299,6 +299,7 @@ static int dwc3_ti_probe(struct platform_device *pdev) + + err_pm_disable: + clk_disable_unprepare(am62->usb2_refclk); ++ pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + return ret; +diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c +--- a/drivers/usb/dwc3/ep0.c ++++ b/drivers/usb/dwc3/ep0.c +@@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ ret = -ESHUTDOWN; ++ goto out; ++ } ++ + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { + dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", + dep->name); +@@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep0_set_halt(ep, value); + spin_unlock_irqrestore(&dwc->lock, flags); + +diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c +--- a/drivers/usb/dwc3/gadget.c ++++ b/drivers/usb/dwc3/gadget.c +@@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, + return 0; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_queue(dep, req); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_set_halt(dep, value, false); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + dep->flags |= DWC3_EP_WEDGE; + + if (dep->number == 0 || dep->number == 1) +@@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) + } + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + if (!dwc->gadget->wakeup_armed) { + dev_err(dwc->dev, "not armed for remote wakeup\n"); + spin_unlock_irqrestore(&dwc->lock, flags); +@@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) + } + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + /* + * If the link is in U3, signal for remote wakeup and wait for the + * link to transition to U0 before sending device notification. +@@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) + + synchronize_irq(dwc->irq_gadget); + ++ /* Serialize against dwc3_err_recovery_work() */ ++ mutex_lock(&dwc->connect_mutex); + if (!is_on) + ret = dwc3_gadget_soft_disconnect(dwc); + else + ret = dwc3_gadget_soft_connect(dwc); ++ mutex_unlock(&dwc->connect_mutex); + + pm_runtime_put(dwc->dev); + +@@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) + + dwc->suspended = false; + ++ /* The controller is recovered. */ ++ if (dwc->err_state == DWC3_ERR_NONE) ++ dwc->err_recovery_count = 0; ++ + /* + * Ideally, dwc3_reset_gadget() would trigger the function + * drivers to stop any active transfers through ep disable. +@@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) + return ret; + } + ++static void dwc3_schedule_err_recovery(struct dwc3 *dwc) ++{ ++ dwc->err_state = DWC3_ERR_RECOVERY; ++ schedule_work(&dwc->err_recovery_work); ++} ++ + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) + { + struct dwc3 *dwc = evt->dwc; +@@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) + return IRQ_NONE; + + if (count > evt->length) { +- dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", ++ dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + count, evt->length); +- return IRQ_NONE; ++ /* ++ * This is a fatal error - the driver and controller are out of ++ * sync on which event has been consumed. Reinitializing the ++ * controller is required to recover. Write the bogus count back ++ * to GEVNTCOUNT to clear the IRQ source, consistent with the ++ * stale event clearing in dwc3_event_buffers_setup(), then ++ * schedule error recovery. ++ */ ++ spin_lock(&dwc->lock); ++ dwc3_schedule_err_recovery(dwc); ++ spin_unlock(&dwc->lock); ++ dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); ++ return IRQ_HANDLED; + } + + evt->count = count; +@@ -4735,6 +4784,57 @@ static void dwc_gadget_release(struct device *dev) + kfree(gadget); + } + ++static void dwc3_err_recovery_work(struct work_struct *work) ++{ ++ struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); ++ unsigned long flags; ++ int ret; ++ ++ /* serializes against dwc3_gadget_pullup() */ ++ mutex_lock(&dwc->connect_mutex); ++ ++ ret = dwc3_gadget_soft_disconnect(dwc); ++ ++ dwc3_disconnect_gadget_sleepable(dwc); ++ ++ if (ret) ++ goto err_unrecoverable; ++ ++ if (dwc->softconnect) { ++ u32 count; ++ /* ++ * Wait irq to finish before soft_connect resets evt->lpos and ++ * the event buffer registers to avoid racing with ++ * dwc3_process_event_buf(). ++ */ ++ synchronize_irq(dwc->irq_gadget); ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ count = ++dwc->err_recovery_count; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ ++ if (count > DWC3_ERR_RECOVERY_MAX) ++ goto err_unrecoverable; ++ ++ ret = dwc3_gadget_soft_connect(dwc); ++ if (ret) ++ goto err_unrecoverable; ++ } ++ mutex_unlock(&dwc->connect_mutex); ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ dwc->err_state = DWC3_ERR_NONE; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return; ++ ++err_unrecoverable: ++ dev_err(dwc->dev, "Unable to recover the controller\n"); ++ mutex_unlock(&dwc->connect_mutex); ++ spin_lock_irqsave(&dwc->lock, flags); ++ dwc->err_state = DWC3_ERR_UNRECOVERABLE; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++} ++ + /** + * dwc3_gadget_init - initializes gadget related registers + * @dwc: pointer to our controller context structure +@@ -4778,6 +4878,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) + } + + init_completion(&dwc->ep0_in_setup); ++ INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); ++ mutex_init(&dwc->connect_mutex); + dwc->gadget = kzalloc_obj(struct usb_gadget); + if (!dwc->gadget) { + ret = -ENOMEM; +@@ -4874,6 +4976,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) + if (!dwc->gadget) + return; + ++ cancel_work_sync(&dwc->err_recovery_work); ++ mutex_destroy(&dwc->connect_mutex); + dwc3_enable_susphy(dwc, true); + usb_del_gadget(dwc->gadget); + dwc3_gadget_free_endpoints(dwc); +diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -1877,8 +1877,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + break; + case FUNCTIONFS_ENDPOINT_DESC: + { ++ const struct usb_endpoint_descriptor *desc; ++ struct usb_endpoint_descriptor desc1; + int desc_idx; +- struct usb_endpoint_descriptor desc1, *desc; + + switch (epfile->ffs->gadget->speed) { + case USB_SPEED_SUPER: +@@ -1892,7 +1893,17 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + desc_idx = 0; + } + +- desc = epfile->ep->descs[desc_idx]; ++ do { ++ desc = epfile->ep->descs[desc_idx]; ++ } while (!desc && --desc_idx >= 0); ++ ++ if (!desc) ++ desc = epfile->ep->ep->desc; ++ if (!desc) { ++ ret = -EINVAL; ++ break; ++ } ++ + memcpy(&desc1, desc, desc->bLength); + + spin_unlock_irq(&epfile->ffs->eps_lock); +diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c +--- a/drivers/usb/gadget/function/f_midi2.c ++++ b/drivers/usb/gadget/function/f_midi2.c +@@ -1633,8 +1633,8 @@ struct f_midi2_usb_config { + + /* MIDI 1.0 jacks */ + unsigned char jack_in, jack_out, jack_id; +- struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; +- struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; ++ struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES * 2]; ++ struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES * 2]; + }; + + static int append_config(struct f_midi2_usb_config *config, void *d) +diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c +--- a/drivers/usb/gadget/function/f_uac1_legacy.c ++++ b/drivers/usb/gadget/function/f_uac1_legacy.c +@@ -797,6 +797,9 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) + + static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) + { ++ if (cmd >= ARRAY_SIZE(con->data)) ++ return -EINVAL; ++ + con->data[cmd] = value; + + return 0; +@@ -804,6 +807,9 @@ static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) + + static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) + { ++ if (cmd >= ARRAY_SIZE(con->data)) ++ return -EINVAL; ++ + return con->data[cmd]; + } + +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/core.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c +@@ -267,6 +267,9 @@ static void ast_vhub_remove(struct platform_device *pdev) + for (i = 0; i < vhub->max_ports; i++) + ast_vhub_del_dev(&vhub->ports[i].dev); + ++ /* Final drain; the worker takes vhub->lock, so stay outside of it */ ++ cancel_work_sync(&vhub->wake_work); ++ + spin_lock_irqsave(&vhub->lock, flags); + + /* Mask & ack all interrupts */ +@@ -328,6 +331,7 @@ static int ast_vhub_probe(struct platform_device *pdev) + return -ENOMEM; + + spin_lock_init(&vhub->lock); ++ INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + vhub->pdev = pdev; + vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, + VHUB_IRQ_DEV1_BIT); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +@@ -280,7 +280,7 @@ static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) + int rc = -EINVAL; + + spin_lock_irqsave(&d->vhub->lock, flags); +- if (!d->wakeup_en) ++ if (!d->wakeup_en || !d->registered) + goto err; + + DDBG(d, "Device initiated wakeup\n"); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +@@ -558,7 +558,7 @@ void ast_vhub_device_connect(struct ast_vhub *vhub, + ast_vhub_send_host_wakeup(vhub); + } + +-static void ast_vhub_wake_work(struct work_struct *work) ++void ast_vhub_wake_work(struct work_struct *work) + { + struct ast_vhub *vhub = container_of(work, + struct ast_vhub, +@@ -588,6 +588,8 @@ static void ast_vhub_wake_work(struct work_struct *work) + + void ast_vhub_hub_wake_all(struct ast_vhub *vhub) + { ++ lockdep_assert_held(&vhub->lock); ++ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain +@@ -1076,7 +1078,6 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) + int ast_vhub_init_hub(struct ast_vhub *vhub) + { + vhub->speed = USB_SPEED_UNKNOWN; +- INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + + return ast_vhub_init_desc(vhub); + } +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +--- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h ++++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +@@ -547,6 +547,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub); + void ast_vhub_hub_resume(struct ast_vhub *vhub); + void ast_vhub_hub_reset(struct ast_vhub *vhub); + void ast_vhub_hub_wake_all(struct ast_vhub *vhub); ++void ast_vhub_wake_work(struct work_struct *work); + + /* dev.c */ + int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); +diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c +--- a/drivers/usb/gadget/udc/dummy_hcd.c ++++ b/drivers/usb/gadget/udc/dummy_hcd.c +@@ -343,9 +343,11 @@ static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, + { + bool fifo = req == &dum->fifo_req; + ++ ++dum->callback_usage; + spin_unlock(&dum->lock); + usb_gadget_giveback_request(_ep, &req->req); + spin_lock(&dum->lock); ++ --dum->callback_usage; + if (fifo) + dum->fifo_req_busy = 0; + } +@@ -759,11 +761,13 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, + req->req.complete = fifo_complete; + + list_add_tail(&req->queue, &ep->queue); ++ ++dum->callback_usage; + spin_unlock(&dum->lock); + _req->actual = _req->length; + _req->status = 0; + usb_gadget_giveback_request(_ep, _req); + spin_lock(&dum->lock); ++ --dum->callback_usage; + } else + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&dum->lock, flags); +diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c +--- a/drivers/usb/host/octeon-hcd.c ++++ b/drivers/usb/host/octeon-hcd.c +@@ -378,8 +378,22 @@ struct octeon_hcd { + struct cvmx_usb_transaction *active_split; + struct cvmx_usb_tx_fifo periodic; + struct cvmx_usb_tx_fifo nonperiodic; ++ struct hrtimer sof_timer; + }; + ++static int cvmx_usb_poll(struct octeon_hcd *usb); ++ ++static enum hrtimer_restart octeon_usb_sof_timer(struct hrtimer *t) ++{ ++ struct octeon_hcd *usb = container_of(t, struct octeon_hcd, sof_timer); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&usb->lock, flags); ++ cvmx_usb_poll(usb); ++ spin_unlock_irqrestore(&usb->lock, flags); ++ return HRTIMER_NORESTART; ++} ++ + /* + * This macro logically sets a single field in a CSR. It does the sequence + * read, modify, and write +@@ -578,7 +592,7 @@ static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) + int result; + u64 address = CVMX_USBCX_GRSTCTL(usb->index); + u64 done = cvmx_get_cycle() + 100 * +- (u64)octeon_get_clock_rate / 1000000; ++ (u64)octeon_get_clock_rate() / 1000000; + union cvmx_usbcx_grstctl c; + + while (1) { +@@ -853,6 +867,14 @@ static int cvmx_usb_initialize(struct device *dev, + * USBC_GAHBCFG[PTXFEMPLVL] + * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 + */ ++ usbcx_gahbcfg.u32 = cvmx_usb_read_csr32(usb, ++ CVMX_USBCX_GHWCFG3(usb->index)); ++ if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) { ++ dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n", ++ usbcx_gahbcfg.u32); ++ return -ENODEV; ++ } ++ + usbcx_gahbcfg.u32 = 0; + usbcx_gahbcfg.s.dmaen = !(usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_NO_DMA); +@@ -1900,6 +1922,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) + int channel; + struct cvmx_usb_pipe *pipe; + int need_sof; ++ u64 min_due; + enum cvmx_usb_transfer ttype; + + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { +@@ -1940,15 +1963,33 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) + * future that might need to be scheduled + */ + need_sof = 0; ++ min_due = ~0ull; + for (ttype = CVMX_USB_TRANSFER_CONTROL; + ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { + list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { +- if (pipe->next_tx_frame > usb->frame_number) { +- need_sof = 1; +- break; +- } ++ if (pipe->next_tx_frame > usb->frame_number && ++ pipe->next_tx_frame < min_due) ++ min_due = pipe->next_tx_frame; + } + } ++ if (min_due != ~0ull) { ++ u64 delta = min_due - usb->frame_number; ++ ++ /* ++ * frame_number is resynced from HFNUM on every poll, so a ++ * deadline that is many frames away does not need an ++ * interrupt on every SOF to count them down - sleep on the ++ * timer instead and keep SOF interrupts for deadlines within ++ * a few frames. Stay well below the 16383-frame wrap of ++ * HFNUM. One (micro)frame is 125us in high-speed mode. ++ */ ++ if (delta <= 4 || delta > 8000) ++ need_sof = 1; ++ else ++ hrtimer_start(&usb->sof_timer, ++ ns_to_ktime((delta - 2) * 125000), ++ HRTIMER_MODE_REL); ++ } + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, sofmsk, need_sof); + } +@@ -3638,6 +3679,8 @@ static int octeon_usb_probe(struct platform_device *pdev) + usb = (struct octeon_hcd *)hcd->hcd_priv; + + spin_lock_init(&usb->lock); ++ hrtimer_setup(&usb->sof_timer, octeon_usb_sof_timer, CLOCK_MONOTONIC, ++ HRTIMER_MODE_REL); + + usb->init_flags = initialize_flags; + +@@ -3688,6 +3731,7 @@ static void octeon_usb_remove(struct platform_device *pdev) + unsigned long flags; + + usb_remove_hcd(hcd); ++ hrtimer_cancel(&usb->sof_timer); + spin_lock_irqsave(&usb->lock, flags); + status = cvmx_usb_shutdown(usb); + spin_unlock_irqrestore(&usb->lock, flags); +diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c +--- a/drivers/usb/host/ohci-da8xx.c ++++ b/drivers/usb/host/ohci-da8xx.c +@@ -447,6 +447,7 @@ static int ohci_da8xx_probe(struct platform_device *pdev) + + err_remove_hcd: + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + err: + usb_put_hcd(hcd); + return error; +@@ -457,6 +458,7 @@ static void ohci_da8xx_remove(struct platform_device *pdev) + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + usb_put_hcd(hcd); + } + +diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c +--- a/drivers/usb/host/ohci-s3c2410.c ++++ b/drivers/usb/host/ohci-s3c2410.c +@@ -335,6 +335,7 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) + struct usb_hcd *hcd = platform_get_drvdata(dev); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + s3c2410_stop_hc(dev); + usb_put_hcd(hcd); + } +diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c +--- a/drivers/usb/host/ohci-spear.c ++++ b/drivers/usb/host/ohci-spear.c +@@ -103,6 +103,7 @@ static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) + struct spear_ohci *sohci_p = to_spear_ohci(hcd); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + clk_disable_unprepare(sohci_p->clk); + + usb_put_hcd(hcd); +diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c +--- a/drivers/usb/host/ohci-st.c ++++ b/drivers/usb/host/ohci-st.c +@@ -235,6 +235,7 @@ static void st_ohci_platform_remove(struct platform_device *dev) + int clk; + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + + if (pdata->power_off) + pdata->power_off(dev); +diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c +--- a/drivers/usb/serial/bus.c ++++ b/drivers/usb/serial/bus.c +@@ -165,7 +165,11 @@ int usb_serial_bus_register(struct usb_serial_driver *driver) + + void usb_serial_bus_deregister(struct usb_serial_driver *driver) + { +- free_dynids(driver); + driver_unregister(&driver->driver); ++ free_dynids(driver); + } + ++void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver) ++{ ++ driver_remove_file(&driver->driver, &driver_attr_new_id); ++} +diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c +--- a/drivers/usb/serial/cp210x.c ++++ b/drivers/usb/serial/cp210x.c +@@ -223,6 +223,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */ + { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ + { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ ++ { USB_DEVICE(0x1B1C, 0x1C02) }, /* Corsair AX1500i Power Supply */ + { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */ + { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ + { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ +diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c +--- a/drivers/usb/serial/generic.c ++++ b/drivers/usb/serial/generic.c +@@ -266,6 +266,7 @@ EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer); + void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + unsigned int bps; + unsigned long period; + unsigned long expire; +@@ -285,7 +286,14 @@ void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) + __func__, jiffies_to_msecs(timeout), + jiffies_to_msecs(period)); + expire = jiffies + timeout; +- while (!port->serial->type->tx_empty(port)) { ++ for (;;) { ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty) || port->serial->type->tx_empty(port)) { ++ mutex_unlock(&tport->mutex); ++ break; ++ } ++ mutex_unlock(&tport->mutex); ++ + schedule_timeout_interruptible(period); + if (signal_pending(current)) + break; +diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c +--- a/drivers/usb/serial/option.c ++++ b/drivers/usb/serial/option.c +@@ -260,6 +260,7 @@ static void option_instat_callback(struct urb *urb); + #define QUECTEL_PRODUCT_EM060K_12a 0x012a + #define QUECTEL_PRODUCT_EM060K_12b 0x012b + #define QUECTEL_PRODUCT_EM060K_12c 0x012c ++#define QUECTEL_PRODUCT_RG660QB 0x013d + #define QUECTEL_PRODUCT_EG91 0x0191 + #define QUECTEL_PRODUCT_EG95 0x0195 + #define QUECTEL_PRODUCT_BG96 0x0296 +@@ -280,6 +281,7 @@ static void option_instat_callback(struct urb *urb); + #define QUECTEL_PRODUCT_EC200U 0x0901 + #define QUECTEL_PRODUCT_EG912Y 0x6001 + #define QUECTEL_PRODUCT_EC200S_CN 0x6002 ++#define QUECTEL_PRODUCT_EG060W 0x6004 + #define QUECTEL_PRODUCT_EC200A 0x6005 + #define QUECTEL_PRODUCT_EG916Q 0x6007 + #define QUECTEL_PRODUCT_EM061K_LWW 0x6008 +@@ -1268,12 +1270,15 @@ static const struct usb_device_id option_ids[] = { + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG060W, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0xff, 0x30) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) }, +@@ -2168,6 +2173,8 @@ static const struct usb_device_id option_ids[] = { + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ + .driver_info = RSVD(7) }, ++ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x902b, 0xff), ++ .driver_info = RSVD(4) }, + { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ + .driver_info = RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ +@@ -2429,6 +2436,13 @@ static const struct usb_device_id option_ids[] = { + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ + .driver_info = RSVD(3) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x01) }, /* Compal EXC-T1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x02) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x03) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x04) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, + { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ + .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, + { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ +diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c +--- a/drivers/usb/serial/quatech2.c ++++ b/drivers/usb/serial/quatech2.c +@@ -52,6 +52,7 @@ + #define QT2_XMIT_FLUSH 0x05 /* no following info */ + #define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */ + ++#define MIN_BAUD_RATE 50 + #define MAX_BAUD_RATE 921600 + #define DEFAULT_BAUD_RATE 9600 + +@@ -156,7 +157,7 @@ static inline int calc_baud_divisor(int baudrate) + + static inline int qt2_set_port_config(struct usb_device *dev, + unsigned char port_number, +- u16 baudrate, u16 lcr) ++ speed_t baudrate, u16 lcr) + { + int divisor = calc_baud_divisor(baudrate); + u16 index = ((u16) (lcr << 8) | (u16) (port_number)); +@@ -257,9 +258,9 @@ static void qt2_set_termios(struct tty_struct *tty, + struct usb_device *dev = port->serial->dev; + struct qt2_port_private *port_priv; + struct ktermios *termios = &tty->termios; +- u16 baud; + unsigned int cflag = termios->c_cflag; + u16 new_lcr = 0; ++ speed_t baud; + int status; + + port_priv = usb_get_serial_port_data(port); +@@ -277,6 +278,18 @@ static void qt2_set_termios(struct tty_struct *tty, + if (!baud) + baud = 9600; + ++ if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { ++ if (old_termios) ++ baud = tty_termios_baud_rate(old_termios); ++ else ++ baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); ++ ++ tty_encode_baud_rate(tty, baud, baud); ++ ++ if (!baud) ++ baud = 9600; ++ } ++ + status = qt2_set_port_config(dev, port_priv->device_port, baud, + new_lcr); + if (status < 0) +@@ -373,7 +386,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) + port_priv->device_port = (u8) device_port; + + if (tty) +- qt2_set_termios(tty, port, &tty->termios); ++ qt2_set_termios(tty, port, NULL); + + return 0; + +diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c +--- a/drivers/usb/serial/ssu100.c ++++ b/drivers/usb/serial/ssu100.c +@@ -32,6 +32,7 @@ + + #define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) + ++#define MIN_BAUD_RATE 50 + #define MAX_BAUD_RATE 460800 + + #define ATC_DISABLED 0x00 +@@ -217,9 +218,10 @@ static void ssu100_set_termios(struct tty_struct *tty, + { + struct usb_device *dev = port->serial->dev; + struct ktermios *termios = &tty->termios; +- u16 baud, divisor, remainder; ++ speed_t baud, remainder; + unsigned int cflag = termios->c_cflag; + u16 urb_value = 0; /* will hold the new flags */ ++ u16 divisor; + int result; + + if (cflag & PARENB) { +@@ -235,6 +237,18 @@ static void ssu100_set_termios(struct tty_struct *tty, + if (!baud) + baud = 9600; + ++ if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { ++ if (old_termios) ++ baud = tty_termios_baud_rate(old_termios); ++ else ++ baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); ++ ++ tty_encode_baud_rate(tty, baud, baud); ++ ++ if (!baud) ++ baud = 9600; ++ } ++ + dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud); + + +@@ -310,7 +324,7 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port) + dev_dbg(&port->dev, "%s - set uart failed\n", __func__); + + if (tty) +- ssu100_set_termios(tty, port, &tty->termios); ++ ssu100_set_termios(tty, port, NULL); + + return usb_serial_generic_open(tty, port); + } +diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c +--- a/drivers/usb/serial/usb-serial.c ++++ b/drivers/usb/serial/usb-serial.c +@@ -400,17 +400,13 @@ static unsigned int serial_chars_in_buffer(struct tty_struct *tty) + static void serial_wait_until_sent(struct tty_struct *tty, int timeout) + { + struct usb_serial_port *port = tty->driver_data; +- struct usb_serial *serial = port->serial; + + dev_dbg(&port->dev, "%s\n", __func__); + + if (!port->serial->type->wait_until_sent) + return; + +- mutex_lock(&serial->disc_mutex); +- if (!serial->disconnected) +- port->serial->type->wait_until_sent(tty, timeout); +- mutex_unlock(&serial->disc_mutex); ++ port->serial->type->wait_until_sent(tty, timeout); + } + + static void serial_throttle(struct tty_struct *tty) +@@ -438,8 +434,13 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) + struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + unsigned int close_delay, closing_wait; ++ int ret = 0; + + mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) { ++ ret = -EIO; ++ goto out_unlock; ++ } + + close_delay = jiffies_to_msecs(tport->close_delay) / 10; + closing_wait = tport->closing_wait; +@@ -452,10 +453,10 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) + + if (port->serial->type->get_serial) + port->serial->type->get_serial(tty, ss); +- ++out_unlock: + mutex_unlock(&tport->mutex); + +- return 0; ++ return ret; + } + + static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) +@@ -471,6 +472,10 @@ static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) + closing_wait = msecs_to_jiffies(closing_wait * 10); + + mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) { ++ ret = -EIO; ++ goto out_unlock; ++ } + + if (!capable(CAP_SYS_ADMIN)) { + if (close_delay != tport->close_delay || +@@ -498,6 +503,7 @@ static int serial_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + int retval = -ENOIOCTLCMD; + + dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd); +@@ -508,8 +514,21 @@ static int serial_ioctl(struct tty_struct *tty, + retval = port->serial->type->tiocmiwait(tty, arg); + break; + default: +- if (port->serial->type->ioctl) ++ if (!port->serial->type->ioctl) ++ break; ++ ++ if (cmd == TIOCSRS485) ++ down_write(&tty->termios_rwsem); ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ retval = -EIO; ++ else + retval = port->serial->type->ioctl(tty, cmd, arg); ++ mutex_unlock(&tport->mutex); ++ ++ if (cmd == TIOCSRS485) ++ up_write(&tty->termios_rwsem); + } + + return retval; +@@ -519,25 +538,40 @@ static void serial_set_termios(struct tty_struct *tty, + const struct ktermios *old) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->set_termios) +- port->serial->type->set_termios(tty, port, old); +- else ++ if (!port->serial->type->set_termios) { + tty_termios_copy_hw(&tty->termios, old); ++ return; ++ } ++ ++ mutex_lock(&tport->mutex); ++ if (!tty_io_error(tty)) ++ port->serial->type->set_termios(tty, port, old); ++ mutex_unlock(&tport->mutex); + } + + static int serial_break(struct tty_struct *tty, int break_state) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->break_ctl) +- return port->serial->type->break_ctl(tty, break_state); ++ if (!port->serial->type->break_ctl) ++ return -ENOTTY; + +- return -ENOTTY; ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->break_ctl(tty, break_state); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_proc_show(struct seq_file *m, void *v) +@@ -578,24 +612,44 @@ static int serial_proc_show(struct seq_file *m, void *v) + static int serial_tiocmget(struct tty_struct *tty) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->tiocmget) +- return port->serial->type->tiocmget(tty); +- return -ENOTTY; ++ if (!port->serial->type->tiocmget) ++ return -ENOTTY; ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->tiocmget(tty); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->tiocmset) +- return port->serial->type->tiocmset(tty, set, clear); +- return -ENOTTY; ++ if (!port->serial->type->tiocmset) ++ return -ENOTTY; ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->tiocmset(tty, set, clear); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_get_icount(struct tty_struct *tty, +@@ -1191,11 +1245,16 @@ static void usb_serial_disconnect(struct usb_interface *interface) + usb_serial_port_poison_urbs(port); + wake_up_interruptible(&port->port.delta_msr_wait); + cancel_work_sync(&port->work); ++ } ++ ++ if (serial->type->disconnect) ++ serial->type->disconnect(serial); ++ ++ for (i = 0; i < serial->num_ports; ++i) { ++ port = serial->port[i]; + if (device_is_registered(&port->dev)) + device_del(&port->dev); + } +- if (serial->type->disconnect) +- serial->type->disconnect(serial); + + release_sibling(serial, interface); + +@@ -1464,7 +1523,7 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers + { + int rc; + struct usb_driver *udriver; +- struct usb_serial_driver * const *sd; ++ struct usb_serial_driver * const *sd, * const *s; + + /* + * udriver must be registered before any of the serial drivers, +@@ -1517,9 +1576,11 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers + return 0; + + err_deregister_drivers: ++ for (s = serial_drivers; s < sd; ++s) ++ usb_serial_bus_remove_new_id(*s); ++ usb_deregister(udriver); + while (sd-- > serial_drivers) + usb_serial_deregister(*sd); +- usb_deregister(udriver); + err_free_driver: + kfree(udriver); + return rc; +@@ -1537,10 +1598,23 @@ EXPORT_SYMBOL_GPL(__usb_serial_register_drivers); + void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[]) + { + struct usb_driver *udriver = (*serial_drivers)->usb_driver; ++ struct usb_serial_driver * const *sd; ++ ++ /* ++ * udriver must be deregistered before the serial drivers so that ++ * I/O is stopped before unbinding the ports. ++ * ++ * Remove the new_id attributes to prevent ids from being added and ++ * triggering a probe of udriver after it has been deregistered. ++ */ ++ for (sd = serial_drivers; *sd; ++sd) ++ usb_serial_bus_remove_new_id(*sd); + +- for (; *serial_drivers; ++serial_drivers) +- usb_serial_deregister(*serial_drivers); + usb_deregister(udriver); ++ ++ for (sd = serial_drivers; *sd; ++sd) ++ usb_serial_deregister(*sd); ++ + kfree(udriver); + } + EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers); +diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c +--- a/drivers/usb/serial/xr_serial.c ++++ b/drivers/usb/serial/xr_serial.c +@@ -850,12 +850,9 @@ static int xr_get_rs485_config(struct tty_struct *tty, + struct usb_serial_port *port = tty->driver_data; + struct xr_data *data = usb_get_serial_port_data(port); + +- down_read(&tty->termios_rwsem); +- if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) { +- up_read(&tty->termios_rwsem); ++ /* core holds port mutex */ ++ if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) + return -EFAULT; +- } +- up_read(&tty->termios_rwsem); + + return 0; + } +@@ -871,10 +868,9 @@ static int xr_set_rs485_config(struct tty_struct *tty, + return -EFAULT; + xr_sanitize_serial_rs485(&rs485); + +- down_write(&tty->termios_rwsem); ++ /* core holds termios rwsem and port mutex */ + data->rs485 = rs485; + xr_set_flow_mode(tty, port, NULL); +- up_write(&tty->termios_rwsem); + + if (copy_to_user(argp, &rs485, sizeof(rs485))) + return -EFAULT; +diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c +--- a/drivers/usb/storage/sierra_ms.c ++++ b/drivers/usb/storage/sierra_ms.c +@@ -77,6 +77,13 @@ static int sierra_get_swoc_info(struct usb_device *udev, + sizeof(struct swoc_info), /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + ++ /* ++ * A short IN transfer leaves the tail of swocInfo uninitialized; ++ * only a full transfer is valid. ++ */ ++ if (result != sizeof(struct swoc_info)) ++ return -EIO; ++ + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); + swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); + return result; +diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c +--- a/drivers/usb/typec/anx7411.c ++++ b/drivers/usb/typec/anx7411.c +@@ -1566,8 +1566,11 @@ static void anx7411_i2c_remove(struct i2c_client *client) + anx7411_partner_unregister_altmode(plat); + anx7411_unregister_partner(plat); + +- if (plat->workqueue) ++ if (plat->workqueue) { ++ disable_irq(plat->intp_irq); ++ cancel_work_sync(&plat->work); + destroy_workqueue(plat->workqueue); ++ } + + i2c_unregister_device(plat->spi_client); + +diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c +--- a/drivers/usb/typec/port-mapper.c ++++ b/drivers/usb/typec/port-mapper.c +@@ -42,6 +42,23 @@ static int usb4_port_compare(struct device *dev, void *fwnode) + return usb4_usb3_port_match(dev, fwnode); + } + ++static bool typec_has_usb4_host_interface(const struct fwnode_handle *fwnode) ++{ ++ if (!IS_REACHABLE(CONFIG_USB4)) ++ return false; ++ ++ struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = ++ fwnode_find_reference(fwnode, "usb4-host-interface", 0); ++ ++ /* ++ * The USB4 port can only appear if the host interface is enabled in ++ * the firmware and has been enumerated as a device. The latter is ++ * the same check as in usb_acpi_add_usb4_devlink(). ++ */ ++ return !IS_ERR(nhi_fwnode) && fwnode_device_is_available(nhi_fwnode) && ++ nhi_fwnode->dev; ++} ++ + static int typec_port_compare(struct device *dev, void *fwnode) + { + return device_match_fwnode(dev, fwnode); +@@ -64,11 +81,11 @@ static int typec_port_match(struct device *dev, void *data) + adev_fwnode); + + /* +- * If dev is USB 3.x port, it may have reference to the ++ * If dev is USB 3.x port, it may have reference to an available + * USB4 host interface in which case we can also link the + * Type-C port with the USB4 port. + */ +- if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) ++ if (typec_has_usb4_host_interface(adev_fwnode)) + component_match_add(&arg->port->dev, &arg->match, + usb4_port_compare, adev_fwnode); + } +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -1756,7 +1756,8 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del + static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) + { + hrtimer_cancel(&port->vdm_discovery_timer); +- kthread_cancel_work_sync(&port->vdm_discovery_work); ++ if (port->wq) ++ kthread_cancel_work_sync(&port->vdm_discovery_work); + } + + static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +@@ -7113,7 +7114,7 @@ static void tcpm_pd_event_handler(struct kthread_work *work) + port->upcoming_state = FR_SWAP_SEND; + ret = tcpm_ams_start(port, FAST_ROLE_SWAP); + if (ret == -EAGAIN) +- port->upcoming_state = INVALID_STATE; ++ tcpm_set_state(port, ERROR_RECOVERY, 0); + } else { + tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); + } +@@ -8977,6 +8978,7 @@ void tcpm_unregister_port(struct tcpm_port *port) + + port->registered = false; + kthread_destroy_worker(port->wq); ++ port->wq = NULL; + + hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); +diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c +--- a/drivers/usb/typec/tipd/core.c ++++ b/drivers/usb/typec/tipd/core.c +@@ -343,7 +343,7 @@ static void tps6598x_set_data_role(struct tps6598x *tps, + + static int tps6598x_connect(struct tps6598x *tps, u32 status) + { +- struct typec_partner_desc desc; ++ struct typec_partner_desc desc = { }; + enum typec_pwr_opmode mode; + int ret; + +@@ -354,7 +354,6 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) + + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ +- desc.identity = NULL; + + if (desc.usb_pd) { + ret = tps6598x_read_partner_identity(tps); +@@ -850,11 +849,10 @@ static void cd321x_update_work(struct work_struct *work) + + /* Set up partner if we were previously disconnected (or changed). */ + if (!tps->partner) { +- struct typec_partner_desc desc; ++ struct typec_partner_desc desc = { }; + + desc.usb_pd = is_pd; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ +- desc.identity = NULL; + + if (desc.usb_pd) + desc.identity = &st.partner_identity; +@@ -1792,6 +1790,7 @@ static int tps6598x_probe(struct i2c_client *client) + const struct tipd_data *data; + struct tps6598x *tps; + struct fwnode_handle *fwnode; ++ bool patch_loaded = false; + u32 status; + u32 vid = 0; + int ret; +@@ -1847,6 +1846,7 @@ static int tps6598x_probe(struct i2c_client *client) + return ret; + + if (ret == TPS_MODE_PTCH) { ++ patch_loaded = true; + ret = tps->data->init(tps); + if (ret) + return ret; +@@ -1937,7 +1937,8 @@ static int tps6598x_probe(struct i2c_client *client) + tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); + err_reset_controller: + /* Reset PD controller to remove any applied patch */ +- tps->data->reset(tps); ++ if (patch_loaded) ++ tps->data->reset(tps); + + return ret; + } +diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c +--- a/drivers/usb/typec/ucsi/displayport.c ++++ b/drivers/usb/typec/ucsi/displayport.c +@@ -71,11 +71,14 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) + if (ret < 0) { + if (ucsi->version > 0x0100) + goto err_unlock; +- cur = 0xff; + } + +- if (cur < UCSI_MAX_ALTMODES) { +- ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; ++ if (cur != 0xff) { ++ if (cur < UCSI_MAX_ALTMODES) ++ ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; ++ else ++ ret = -EINVAL; ++ + goto err_unlock; + } + +diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1801,10 +1801,21 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) + { + struct fwnode_handle *fwnode; + int i = 1; ++ int ret; ++ u32 port; + +- device_for_each_child_node(con->ucsi->dev, fwnode) +- if (i++ == con->num) +- return fwnode; ++ device_for_each_child_node(con->ucsi->dev, fwnode) { ++ ret = fwnode_property_read_u32(fwnode, "reg", &port); ++ if (ret < 0) { ++ if (i == con->num) ++ return fwnode; ++ } else { ++ if (port == con->num - 1) ++ return fwnode; ++ } ++ ++ i++; ++ } + return NULL; + } + +diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c +--- a/drivers/usb/typec/ucsi/ucsi_acpi.c ++++ b/drivers/usb/typec/ucsi/ucsi_acpi.c +@@ -121,6 +121,33 @@ static const struct ucsi_operations ucsi_acpi_ops = { + .async_control = ucsi_acpi_async_control + }; + ++static int ucsi_acer_read_version(struct ucsi *ucsi, u16 *version) ++{ ++ struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); ++ int ret; ++ ++ ret = ucsi_acpi_read_version(ucsi, version); ++ if (ret) ++ return ret; ++ ++ if (!*version) { ++ dev_warn(ua->dev, "UCSI version is zero, assuming 1.2\n"); ++ *version = UCSI_VERSION_1_2; ++ } ++ ++ return 0; ++} ++ ++static const struct ucsi_operations ucsi_acer_ops = { ++ .read_version = ucsi_acer_read_version, ++ .read_cci = ucsi_acpi_read_cci, ++ .poll_cci = ucsi_acpi_poll_cci, ++ .read_message_in = ucsi_acpi_read_message_in, ++ .write_message_out = ucsi_acpi_write_message_out, ++ .sync_control = ucsi_sync_control_common, ++ .async_control = ucsi_acpi_async_control ++}; ++ + static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, + void *val, size_t len, void *msg_out, + size_t msg_out_size) +@@ -162,6 +189,13 @@ static const struct ucsi_operations ucsi_gram_ops = { + }; + + static const struct dmi_system_id ucsi_acpi_quirks[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Acer"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), ++ }, ++ .driver_data = (void *)&ucsi_acer_ops, ++ }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"), +diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c +--- a/drivers/usb/typec/ucsi/ucsi_glink.c ++++ b/drivers/usb/typec/ucsi/ucsi_glink.c +@@ -362,6 +362,10 @@ static void pmic_glink_ucsi_destroy(void *data) + { + struct pmic_glink_ucsi *ucsi = data; + ++ /* Drain the work items before ucsi_destroy() frees the ucsi instance */ ++ cancel_work_sync(&ucsi->notify_work); ++ cancel_work_sync(&ucsi->register_work); ++ + /* Protect to make sure we're not in a middle of a transaction from a glink callback */ + mutex_lock(&ucsi->lock); + ucsi_destroy(ucsi->ucsi); +@@ -467,8 +471,15 @@ static void pmic_glink_ucsi_remove(struct auxiliary_device *adev) + { + struct pmic_glink_ucsi *ucsi = dev_get_drvdata(&adev->dev); + +- /* Unregister first to stop having read & writes */ +- ucsi_unregister(ucsi->ucsi); ++ /* Callbacks can queue work until devres releases the client */ ++ disable_work_sync(&ucsi->notify_work); ++ disable_work_sync(&ucsi->register_work); ++ ++ /* register_work may have unregistered the instance already */ ++ if (ucsi->ucsi_registered) { ++ ucsi->ucsi_registered = false; ++ ucsi_unregister(ucsi->ucsi); ++ } + } + + static const struct auxiliary_device_id pmic_glink_ucsi_id_table[] = { +diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h +--- a/include/linux/usb/serial.h ++++ b/include/linux/usb/serial.h +@@ -381,6 +381,7 @@ void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, + + int usb_serial_bus_register(struct usb_serial_driver *device); + void usb_serial_bus_deregister(struct usb_serial_driver *device); ++void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver); + + extern const struct bus_type usb_serial_bus_type; + extern struct tty_driver *usb_serial_tty_driver; diff --git a/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..864678b80ec34eaa17d47457f844c84e61f91846 GIT binary patch literal 594 zcmV-Y0H3uK0euM=$c7LZ zpH+oCzJUgyc?K4kw>mraJYf^j%l6aqy<6iZMh)tqRT}1cq(=l*kdR!g77vJmd{XTz zjc2gve=)PfQE7s5ti9f!GJE49zNc9~LC0B><+}sn*vsmlz)zT*b&&JJ?-uD{M5o&M zu5QyMYMc3x=uElu^$;i4XZggPyl9r|yUlc3^w&0z=wQwF;MndMDpy-#(BN;OqU9On zzLk$GW7sB;bW_`60P+KM3>?{2Zd;t~K}1m7Z7_|$e(0Wv6?Jss30p~mxNBaU{^rRl zU-Edb23hZ;3rnK!$Tvj^$Jek$^w6j1BCE>Acsg2Qa%<`*DF!L-XBaVgsaiWKqp<*v z3g4gz>y%JPfz3)BU~mZU#sM5T1FhOy zY{b(F1JZ)^mU8t>Lu$1+cajSx&n%6!c({fvJp>=W>D1ELZD1$9A+*9ieO#0&2!%5N zGjh)yYR=6Y3u};u$GHCDp~@;@t6t!+s*|1Nr gWt!s@QjhZ=icrD`;at}ffMdS?rft!V4L2=L;xjZIIsgCw literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch b/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch new file mode 100644 index 0000000..d2ac981 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch @@ -0,0 +1,29 @@ +diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c +--- a/drivers/usb/core/message.c ++++ b/drivers/usb/core/message.c +@@ -1052,6 +1052,25 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) + UTF16_LITTLE_ENDIAN, buf, size); + buf[err] = 0; + ++ /* ++ * Some devices report string descriptors with a declared length ++ * greater than the actual serial, leaving uninitialized firmware ++ * memory (often including C0 control characters) appended to the ++ * returned string. Truncate at the first control character so ++ * callers get a clean, well-formed string. ++ */ ++ { ++ int i; ++ for (i = 0; i < err; i++) { ++ unsigned char c = buf[i]; ++ if (c < 0x20 || c == 0x7f) { ++ buf[i] = 0; ++ err = i; ++ break; ++ } ++ } ++ } ++ + if (tbuf[1] != USB_DT_STRING) + dev_dbg(&dev->dev, + "wrong descriptor type %02x for string %d (\"%s\")\n", diff --git a/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch.sig b/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..030b9faa3c4e3b42fe166a6c81a29278b4cda15b GIT binary patch literal 594 zcmV-Y0g7oOs` zV%M!zEm*_ZVWVPly?;(e7k&ec(#UZarVs!6YW`$|a>i+HUq1m=SdKJ zIR;g{Ib5Wh5rv*L^%ScuyBKqPjXET_8c#fQlsyCSi9ZZ=cHjM*nnr6Kmbmq$+iAyW z&29B-?nkw);K8Ez>8BR!jVw#bLQ4qB=rhm(RMp7w)TU<;K`7RneKTYdpAB#_BWAxD zbC%sj0l#oejP$iaxjS|lFY+f$oFD?PzmYfQ7;B^MwU)7BrH z(AeAz*WyS5A3jS^UAZ!QS=|Dl1EaP4_*ulC=$MZOEVCDfg@l4_hash; + u8 sw_hash = skb->sw_hash; + u32 hash = skb->hash; ++ u8 tstamp_type = skb->tstamp_type; + skb_scrub_packet(skb, true); + memset(&skb->headers, 0, sizeof(skb->headers)); + if (encapsulating) { + skb->l4_hash = l4_hash; + skb->sw_hash = sw_hash; + skb->hash = hash; ++ skb->tstamp_type = tstamp_type; + } + skb->queue_mapping = 0; + skb->nohdr = 0; diff --git a/pkgbuilds/linux-omarchy-muqss/0650-wireguard-tstamp-type.patch.sig b/pkgbuilds/linux-omarchy-muqss/0650-wireguard-tstamp-type.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..4dfd43ec5cc05bd364d74db1a3708a39dc7f189b GIT binary patch literal 594 zcmV-Y0aBODasDmJ{Ul`)pb22Mb{^xe2yD(cuZF+KBP%Ta+DEIr}e(0Kkfabi`Py@;ON zRSp^jQRE^WBJRkxQscSAR|+$g(u-oKEVyHVxrJ0f&MtI)+i0jK3Xiaqu!|gc^fdJT zMEOHXDJyTlsiG~L(55ARG6|jvWRxcp<@>Tw@7NXHQC}=}L}d7;>ND?~%wp{pTkZ=&tQ+Rsua}7Aw(g z9c!CwY`D$h9$8zl!1#E`LSD)jn$$m%!SO3a4NPbYUnnNt*<$cKpJE4k4#WO3QhBe8 g7b79i0ns!g!SM7Qk+|vLjh#UK0n;V7G8-V-eDHV})c^nh literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch b/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch new file mode 100644 index 0000000..3b51649 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -819,6 +819,8 @@ static const struct usb_device_id quirks_table[] = { + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3613), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, ++ { USB_DEVICE(0x13d3, 0x3625), .driver_info = BTUSB_MEDIATEK | ++ BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3627), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3628), .driver_info = BTUSB_MEDIATEK | diff --git a/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig b/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f497ab2a9d0c2389173292a9c0773c7da25ca474 GIT binary patch literal 594 zcmV-Y0s=j00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=X?1N{yE{_MuaN)C8#zd2!1tl zP8LSzfBpE8Q^sq_|Dl<-#L==`gg)#J&3@zxtKmlFa zir;Jd)-CveUItY1MPxS=0N^+wR*ux)$RbP^+gvC(R}+>L=`Z)Gv3pu=q~k~2#@zw| zt@pk4Cq`oVhBl?Anzg%0^;>I%Rnj4Nb6t%@_r)HopI?{l<6PqQ-B5bccItUpa<&68 zA0n#W)+DQX+MbkwNmdcV;H#`!6VXSaFqezO_ACim+A5OFK^gFg^O zFkDAVUaEiH4Wai2Qgk&+J*)jhYcRZb=3>sc_D`OrnKSk;Qf$U_N)+d*mMx(-h*S8T zb0n-SNExsFb{$?R@D%3e9XX|{g!hdI8#8*C(`$R0zJ@DJ!TTEyJZB!vzU zl{=0Q!>Aa`6Z4BNd%t)qie9jT_Bo!q@7!<>*TzUh_Wp*3{9%jDFl%q_E;zdI(&##& gNkSw(Ie1_y78paK*?)P`XR=InhK{itBPn7F>YkGsCIA2c literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch b/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch new file mode 100644 index 0000000..d3b514c --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch @@ -0,0 +1,27 @@ +diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c ++++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +@@ -516,6 +516,10 @@ iwl_mld_mac80211_tx(struct ieee80211_hw *hw, + u32 link_id = u32_get_bits(info->control.flags, + IEEE80211_TX_CTRL_MLO_LINK); + ++ if (unlikely(test_bit(STATUS_FW_ERROR, &mld->trans->status))) { ++ ieee80211_free_txskb(hw, skb); ++ return; ++ } + /* In AP mode, mgmt frames are sent on the bcast station, + * so the FW can't translate the MLD addr to the link addr. Do it here + */ +diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c +--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c ++++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c +@@ -987,6 +987,9 @@ void iwl_mld_tx_from_txq(struct iwl_mld *mld, struct ieee80211_txq *txq) + struct sk_buff *skb = NULL; + u8 zero_addr[ETH_ALEN] = {}; + ++ if (unlikely(test_bit(STATUS_FW_ERROR, &mld->trans->status))) ++ return; ++ + /* + * Don't transmit during firmware restart. The firmware is dead, + * so iwl_trans_tx() would return -EIO for each frame. Avoid the diff --git a/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig b/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f2bc1c6f85074dea8ac690ac580e013aaa8694fb GIT binary patch literal 594 zcmV-Y0f>%S_1-hRqX&WUq1-ZMpb^XO`{ap*E3P}{n_118ouI) zn^&$Fw-~z`hN|U371;`!*5V7RBjqOfpSztwX$s}DLZZA6-J% zao9XL9Qg@X5TWdwo}ZFkpGmFjaytbZ>}=z3Xq16U>=$ZP)vM_lZ1JU-_3KAUfOzZs z)7&l_@doFvp(?_Ue)R(>zV-J#D|)tK7gI;|trz6YAoR{{{}HLbHs7eY8%V#;LsSJspMsWkP@)&f^UA&nW*wlS{Z~6oYz&fEZbum(b#Ec8 zsIp0wUsB_mFLy3O=xI`E)`q9;^5reset_type is written by exactly one caller (the ++ * thread that wins test_and_set_bit), eliminating the race where ++ * a second hw_error could clobber an already-scheduled reset's ++ * type; ++ * - the write happens AFTER the bit is set, so reset_work observes ++ * it through schedule_work()'s memory ordering; ++ * - losers return without touching reset_type or scheduling the ++ * work, so concurrent triggers are silently coalesced into the ++ * in-flight one (whose recovery will reinitialize the device ++ * regardless of the dropped trigger's variant). ++ * ++ * The bit is cleared only by .remove() / re-probe via fresh devm ++ * allocation, which is the intended one-shot semantics: a reset ++ * tears down and re-probes 'data', so there is no "in-flight" ++ * reset to follow up after device_reprobe() succeeds. ++ */ ++static void btintel_pcie_request_reset(struct btintel_pcie_data *data, ++ enum btintel_pcie_reset_type type) + { +- struct btintel_pcie_data *data; +- +- data = hci_get_drvdata(hdev); +- + if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags)) + return; + + if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) + return; + ++ data->reset_type = type; ++ + pci_dev_get(data->pdev); + schedule_work(&data->reset_work); + } + ++static void btintel_pcie_hci_reset(struct hci_dev *hdev) ++{ ++ struct btintel_pcie_data *data = hci_get_drvdata(hdev); ++ ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); ++} ++ + static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code) + { +- struct btintel_pcie_dev_recovery *data; ++ struct btintel_pcie_dev_recovery *rec; + struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev); + struct pci_dev *pdev = dev_data->pdev; ++ enum btintel_pcie_reset_type type; + time64_t retry_window; + ++ if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &dev_data->flags)) ++ return; ++ + btintel_pcie_dump_debug_registers(hdev); + +- data = btintel_pcie_get_recovery(pdev, &hdev->dev); +- if (!data) ++ rec = btintel_pcie_get_recovery(pdev, &hdev->dev); ++ if (!rec) + return; + +- if (code == 0x13) +- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR; +- else +- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; ++ type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR ++ : BTINTEL_PCIE_IOSF_PRR_FLR; + + bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code, +- dev_data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); +- retry_window = ktime_get_boottime_seconds() - data->last_error; ++ type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); ++ retry_window = ktime_get_boottime_seconds() - rec->last_error; + + if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS && +- data->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { ++ rec->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { + bt_dev_err(hdev, "Exhausted maximum: %d recovery attempts: %d", +- BTINTEL_PCIE_FLR_MAX_RETRY, data->count); ++ BTINTEL_PCIE_FLR_MAX_RETRY, rec->count); + bt_dev_dbg(hdev, "Boot time: %lld seconds", + ktime_get_boottime_seconds()); + bt_dev_dbg(hdev, "last error at: %lld seconds", +- data->last_error); ++ rec->last_error); + return; + } + btintel_pcie_inc_recovery_count(pdev, &hdev->dev); +- btintel_pcie_reset(hdev); ++ btintel_pcie_request_reset(dev_data, type); + } + + static bool btintel_pcie_wakeup(struct hci_dev *hdev) +@@ -2832,7 +2860,7 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data) + hdev->hw_error = btintel_pcie_hw_error; + hdev->set_diag = btintel_set_diag; + hdev->set_bdaddr = btintel_set_bdaddr; +- hdev->reset = btintel_pcie_reset; ++ hdev->reset = btintel_pcie_hci_reset; + hdev->wakeup = btintel_pcie_wakeup; + hdev->hci_drv = &btintel_pcie_hci_drv; + +@@ -3124,8 +3152,7 @@ static int btintel_pcie_resume(struct device *dev) + if (data->pm_sx_event == PM_EVENT_FREEZE || + data->pm_sx_event == PM_EVENT_HIBERNATE) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); +- data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; +- btintel_pcie_reset(data->hdev); ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + return 0; + } + +@@ -3156,7 +3183,7 @@ static int btintel_pcie_resume(struct device *dev) + queue_work(data->coredump_workqueue, &data->coredump_work); + } + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); +- btintel_pcie_reset(data->hdev); ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + } + return err; + } diff --git a/pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch.sig b/pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..590f6a4025e1c412b7c3cadfe241313c0cb6db6e GIT binary patch literal 594 zcmV-Y0}K9;@eJgQ48-=`|p`8lAcMh>`u46;*{e@DJ_iAF3JXs?Xs>6!OCQ{cxf zUlruaF32Lj{;vmZnP>%=0@)Owun#@=Wur7wp0KFYYvZUzNiI>2@Ue@5^m^4M%AnDu g^oGgAZ$^CzG+uGklwvCH(s!?x5^8?u^(O*7)~+=iqW}N^ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch b/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch new file mode 100644 index 0000000..54219c0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch @@ -0,0 +1,30 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -3069,10 +3069,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, + BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); + +- /* A hardware bug may cause the alive interrupt to be missed. +- * Check if the controller reached the expected state and retry +- * the operation only if it hasn't. ++ /* A hardware bug may cause the alive interrupt to be missed. Refresh ++ * boot_stage_cache from hardware, since only the interrupt handler ++ * updates it. Finally retry only if the state check still fails. + */ ++ data->boot_stage_cache = btintel_pcie_rd_reg32(data, ++ BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { + if (btintel_pcie_in_d0(data)) + return 0; +diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h +--- a/drivers/bluetooth/btintel_pcie.h ++++ b/drivers/bluetooth/btintel_pcie.h +@@ -50,6 +50,7 @@ + #define BTINTEL_PCIE_CSR_BOOT_STAGE_DEVICE_HALTED (BIT(14)) + #define BTINTEL_PCIE_CSR_BOOT_STAGE_MAC_ACCESS_ON (BIT(16)) + #define BTINTEL_PCIE_CSR_BOOT_STAGE_ALIVE (BIT(23)) ++/* Reflects live D-state. Updated by hardware on every D-state transition. */ + #define BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY (BIT(24)) + + /* Registers for MSI-X */ diff --git a/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig b/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..c9f68b6448152f188701c88be05f9471923023e2 GIT binary patch literal 594 zcmV-Y0(w)v9^gQfW(dc+Yfgj*h>neWLI$57RUlS6#GPBi%dBS{A=b!RzgLxxlH zXDsIY_T$f`)jU6Kw(h%DR1bvc7nvg4oV1QA-EU4nGjo0fnuHZ61DV`5Yc4y8qeQMU z7g&M%Hy`@dw^86a1-58(q1X^c!Co1kl`urV;7W)e@o%!amZ#WM4GARWYrbyZYlF)I zgg~p5C=l=P-%*ko|BqP_0wRwN$%;CNba@?!}+58UuG z{`!*syePlXRpZ*Ei=Db|hL%(fKh%zAe>gSY$%l`3vYmyjn1~h;2;t<*RM5o{d0*M` zpCDhDwzdmvso25fzT)}X?5_ZXyP2Mm9Xp-!cKQCWiG*)P5jIP!@5ZVk+{%%S4Qso~ gxr0Bk`WKiZHMPW?lR0-d*wbrM416Q)!^p + #include + #include ++#include + + #include + #include +@@ -3046,7 +3047,8 @@ static void btintel_pcie_coredump(struct device *dev) + + static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + { +- int retry = 0, status; ++ int retry = 0; ++ long status; + u32 dx_intr_timeout_ms = 200; + + do { +@@ -3098,18 +3100,23 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg) + + data = pci_get_drvdata(pdev); + +- dxstate = (mesg.event == PM_EVENT_SUSPEND ? +- BTINTEL_PCIE_STATE_D3_HOT : BTINTEL_PCIE_STATE_D3_COLD); +- +- data->pm_sx_event = mesg.event; ++ /* S0ix (s2idle) uses D3_HOT; S3, freeze and hibernate use D3_COLD. */ ++ if (mesg.event == PM_EVENT_SUSPEND && ++ pm_suspend_target_state == PM_SUSPEND_TO_IDLE) ++ dxstate = BTINTEL_PCIE_STATE_D3_HOT; ++ else ++ dxstate = BTINTEL_PCIE_STATE_D3_COLD; + + start = ktime_get(); + + /* Refer: 6.4.11.7 -> Platform power management */ + err = btintel_pcie_set_dxstate(data, dxstate); + +- if (err) ++ if (err) { ++ bt_dev_err(data->hdev, "Failed to set dxstate:%u (%d)", ++ dxstate, err); + return err; ++ } + + bt_dev_dbg(data->hdev, + "device entered into d3 state from d0 in %lld us", +@@ -3132,7 +3139,7 @@ static int btintel_pcie_freeze(struct device *dev) + return btintel_pcie_suspend_late(dev, PMSG_FREEZE); + } + +-static int btintel_pcie_resume(struct device *dev) ++static int btintel_pcie_resume_event(struct device *dev, pm_message_t mesg) + { + struct pci_dev *pdev = to_pci_dev(dev); + struct btintel_pcie_data *data; +@@ -3140,19 +3147,15 @@ static int btintel_pcie_resume(struct device *dev) + int err; + + data = pci_get_drvdata(pdev); +- data->gp0_received = false; + + start = ktime_get(); + +- /* When the system enters S4 (hibernate) mode, bluetooth device loses +- * power, which results in the erasure of its loaded firmware. +- * Consequently, function level reset (flr) is required on system +- * resume to bring the controller back into an operational state by +- * initiating a new firmware download. ++ /* S3 and S4 may cut power, erasing the firmware. Force FLR to recover ++ * instead of a normal D0 transition. + */ +- +- if (data->pm_sx_event == PM_EVENT_FREEZE || +- data->pm_sx_event == PM_EVENT_HIBERNATE) { ++ if (mesg.event == PM_EVENT_RESTORE || ++ (mesg.event == PM_EVENT_RESUME && ++ pm_suspend_target_state == PM_SUSPEND_MEM)) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + return 0; +@@ -3161,7 +3164,9 @@ static int btintel_pcie_resume(struct device *dev) + /* Refer: 6.4.11.7 -> Platform power management */ + err = btintel_pcie_set_dxstate(data, BTINTEL_PCIE_STATE_D0); + +- if (err == 0) { ++ if (err) { ++ bt_dev_err(data->hdev, "Failed to set D0 state (%d)", err); ++ } else { + bt_dev_dbg(data->hdev, + "device entered into d0 state from d3 in %lld us", + ktime_to_us(ktime_get() - start)); +@@ -3190,13 +3195,28 @@ static int btintel_pcie_resume(struct device *dev) + return err; + } + ++static int btintel_pcie_resume(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_RESUME); ++} ++ ++static int btintel_pcie_restore(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_RESTORE); ++} ++ ++static int btintel_pcie_thaw(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_THAW); ++} ++ + static const struct dev_pm_ops btintel_pcie_pm_ops = { + .suspend = btintel_pcie_suspend, + .resume = btintel_pcie_resume, + .freeze = btintel_pcie_freeze, +- .thaw = btintel_pcie_resume, ++ .thaw = btintel_pcie_thaw, + .poweroff = btintel_pcie_hibernate, +- .restore = btintel_pcie_resume, ++ .restore = btintel_pcie_restore, + }; + + static struct pci_driver btintel_pcie_driver = { +diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h +--- a/drivers/bluetooth/btintel_pcie.h ++++ b/drivers/bluetooth/btintel_pcie.h +@@ -478,7 +478,6 @@ struct btintel_pcie_dump_header { + * @txq: TX Queue struct + * @rxq: RX Queue struct + * @alive_intr_ctxt: Alive interrupt context +- * @pm_sx_event: PM event on which system got suspended + */ + struct btintel_pcie_data { + struct pci_dev *pdev; +@@ -533,7 +532,6 @@ struct btintel_pcie_data { + enum btintel_pcie_reset_type reset_type; + struct btintel_pcie_dbgc dbgc; + struct btintel_pcie_dump_header dmp_hdr; +- u8 pm_sx_event; + u32 debug_evt_addr; + u32 debug_evt_size; + }; diff --git a/pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig b/pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..89af410e5e25e5e558d28cf69e385b4d2d93537e GIT binary patch literal 594 zcmV-Y0Drzu#CMm<~R>N3F4-ob`8?rME6fLr@|* z9vFrdsACplM+#w>RDNdbdU!pU?L6i>bb5hP)1EIyT?mw)KKpVPN z!t*tU3#Lch*%+8s;#l=Gqm6=e(9s{ ze~0OrVvn*7H%c;V3y6-Jm8ZyRdvP(X3ZW&or=5TydfBXnL)!6kn8$ad47><>IuUxO z+9f0UwPy(|o{P4&Wj*(4-;)MmWp`fc#B#$+2pfo&6Rl6BOHkCJrF5Ds86*iNoELzd z^h7d>vUo}AHhJ(p9V5Y#kN#=FTe&{!d*cyR8^Bi6I11R6@u;+i?o>Pk#_|e0;~4wu g`>oY`t(|Y~#Za#avVT(pr1mp*T5CfM=064}Y6iCw8~^|S literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch b/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch new file mode 100644 index 0000000..b1305d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch @@ -0,0 +1,113 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1012,6 +1012,10 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) + submit_rx = false; + signal_waitq = false; + ++ /* Serialize the alive_intr_ctxt claim against set_dxstate()'s ++ * register-based fallback, which can run concurrently. ++ */ ++ spin_lock(&data->irq_lock); + switch (data->alive_intr_ctxt) { + case BTINTEL_PCIE_ROM: + data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL; +@@ -1067,6 +1071,7 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) + data->alive_intr_ctxt); + break; + } ++ spin_unlock(&data->irq_lock); + + if (submit_rx) { + btintel_pcie_reset_ia(data); +@@ -3047,7 +3052,7 @@ static void btintel_pcie_coredump(struct device *dev) + + static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + { +- int retry = 0; ++ int retry = 0, err; + long status; + u32 dx_intr_timeout_ms = 200; + +@@ -3059,30 +3064,66 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + status = wait_event_timeout(data->gp0_wait_q, data->gp0_received, + msecs_to_jiffies(dx_intr_timeout_ms)); + +- if (status) +- return 0; ++ if (!status) { ++ bt_dev_warn(data->hdev, ++ "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", ++ dx_intr_timeout_ms, dxstate, retry); + +- bt_dev_warn(data->hdev, +- "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", +- dx_intr_timeout_ms, dxstate, retry); ++ /* clear gp0 cause */ ++ btintel_pcie_clr_reg_bits(data, ++ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, ++ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); ++ } + +- /* clear gp0 cause */ +- btintel_pcie_clr_reg_bits(data, +- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, +- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); +- +- /* A hardware bug may cause the alive interrupt to be missed. Refresh +- * boot_stage_cache from hardware, since only the interrupt handler +- * updates it. Finally retry only if the state check still fails. ++ /* gp0_received is set at the top of the handler, before the switch on ++ * alive_intr_ctxt. Error and lockdown are filtered out above it, but a ++ * matched case can still complete without doing anything - D3 breaks ++ * unchanged while the controller has not reached D0 - and a hardware ++ * bug may drop the interrupt outright. Either way the flag says a gp0 ++ * was handled, not that the transition completed, and only the register ++ * knows. Refresh the cache here and retry only if the state check still ++ * fails. + */ + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { +- if (btintel_pcie_in_d0(data)) ++ if (btintel_pcie_in_d0(data)) { ++ bool armed; ++ ++ /* Claim the transition under irq_lock so a ++ * concurrent gp0 handler run cannot also arm RX. ++ */ ++ spin_lock(&data->irq_lock); ++ armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0; ++ if (!armed) ++ data->alive_intr_ctxt = BTINTEL_PCIE_D0; ++ spin_unlock(&data->irq_lock); ++ ++ if (armed) ++ return 0; ++ ++ /* Do what the handler's D3 -> D0 branch ++ * would have done, unless it already has. ++ */ ++ btintel_pcie_reset_ia(data); ++ err = btintel_pcie_start_rx(data); ++ if (err) ++ return err; ++ + return 0; ++ } + } else { +- if (btintel_pcie_in_d3(data)) ++ if (btintel_pcie_in_d3(data)) { ++ /* Do what the handler's D0 -> D3 branch ++ * would have done, unless it already has. ++ */ ++ spin_lock(&data->irq_lock); ++ if (data->alive_intr_ctxt != BTINTEL_PCIE_D3) ++ data->alive_intr_ctxt = BTINTEL_PCIE_D3; ++ spin_unlock(&data->irq_lock); ++ + return 0; ++ } + } + + } while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES); diff --git a/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch.sig b/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..8fbfd3564ad1856442f41d3e5005fbf67cf2697e GIT binary patch literal 594 zcmV-Y0#TY@T%>O$^UXE%Tp16p|PqChS6Run5Xd+a!n%_<1$ zj!fkK5FrTBIcHQ))J0FSen4JBhC3WHQxj z3v7~1Z4L_YBu5ORpac~J(&%mttGxZx;ld-Y49d7m+n<5#BJXnUrERSd7z(?TP(@GP z44k(R1V4|V*lD`XVk0g_#dQ4LvDn}9#IG9&VF-S3l@`qE$I~P(L6<6bJ|oG>UbIPj zBiN(3I83^CC(CwW*Kr5%bs#)nN=eqvZogU4BCIupMs1QqIjOiYGTkvjI@dbyz?TBV zG3aOZ!~d_NJE@}57(kNV=vx@@bEUqZoUJyGpme@@TMz(*mC%Yl^S2%)*&Ek?qD+!Y znd3p{f6%7!x`gX4JhTLTJpw*qXt&QxfQ7wEg{K%8fflxKBDSMD7fPGr)qMOO!W|z* gN}q`HcW~;17utEzr8x(us&8+x_+31%Hcrr68SAbTQ~&?~ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch b/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch new file mode 100644 index 0000000..9927412 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch @@ -0,0 +1,20 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -3069,10 +3069,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", + dx_intr_timeout_ms, dxstate, retry); + +- /* clear gp0 cause */ +- btintel_pcie_clr_reg_bits(data, +- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, +- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); ++ /* MSIX_HW_INT_CAUSES is W1C. Write only GP0 so other ++ * pending causes are not acknowledged here. ++ */ ++ btintel_pcie_wr_reg32(data, ++ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, ++ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); + } + + /* gp0_received is set at the top of the handler, before the switch on diff --git a/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig b/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..817ba812e9b7042923bddf34b1954406583e0d22 GIT binary patch literal 594 zcmV-Y0m5MJb8 zU)}ql>!$MD2HGHUbdK;^+j?z-?LQmnF=VFH;V`ylr-vc7pzdsz`FxYFDgSasvj#e)XZKFQTLq84Vfnw z_MXp}n}xJZwM(xo9WFt${#PkPaXZ|C1f780SPx(BSncJ3`2Gujn_y2jz8^mSL!A?6C3Mxw`BvLn z0&wONjbYcMBum7cIIE%lL>58wUop~%P)kM%b-jYRBi8|HD}}CO`OE+ha0yz_RyH=B zmBGN~PQgKAOe#}OiPQyq&@eJT4sv*Y;-^6o@^eB5^S7-~z8vh2yg^uR$#_^Fi=SsD g_ei%!vvZQvB^Wo@6RzpER5A5fGTMr$^5g?=_g2Um4*&oF literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch b/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch new file mode 100644 index 0000000..d25c602 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch @@ -0,0 +1,86 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1016,6 +1016,8 @@ static void btintel_pcie_msix_gp0_handle + * register-based fallback, which can run concurrently. + */ + spin_lock(&data->irq_lock); ++ data->boot_stage_cache = btintel_pcie_rd_reg32(data, ++ BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + switch (data->alive_intr_ctxt) { + case BTINTEL_PCIE_ROM: + data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL; +@@ -1071,12 +1073,12 @@ static void btintel_pcie_msix_gp0_handle + data->alive_intr_ctxt); + break; + } +- spin_unlock(&data->irq_lock); + + if (submit_rx) { + btintel_pcie_reset_ia(data); + btintel_pcie_start_rx(data); + } ++ spin_unlock(&data->irq_lock); + + if (signal_waitq) { + bt_dev_dbg(data->hdev, "wake up gp0 wait_q"); +@@ -3086,40 +3088,34 @@ static int btintel_pcie_set_dxstate(stru + * knows. Refresh the cache here and retry only if the state check still + * fails. + */ ++ spin_lock(&data->irq_lock); + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { + if (btintel_pcie_in_d0(data)) { +- bool armed; ++ err = 0; + + /* Claim the transition under irq_lock so a + * concurrent gp0 handler run cannot also arm RX. + */ +- spin_lock(&data->irq_lock); +- armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0; +- if (!armed) ++ if (data->alive_intr_ctxt != BTINTEL_PCIE_D0) { + data->alive_intr_ctxt = BTINTEL_PCIE_D0; +- spin_unlock(&data->irq_lock); + +- if (armed) +- return 0; +- +- /* Do what the handler's D3 -> D0 branch +- * would have done, unless it already has. +- */ +- btintel_pcie_reset_ia(data); +- err = btintel_pcie_start_rx(data); +- if (err) +- return err; ++ /* Do what the handler's D3 -> D0 branch ++ * would have done, unless it already has. ++ */ ++ btintel_pcie_reset_ia(data); ++ err = btintel_pcie_start_rx(data); ++ } ++ spin_unlock(&data->irq_lock); + +- return 0; ++ return err; + } + } else { + if (btintel_pcie_in_d3(data)) { + /* Do what the handler's D0 -> D3 branch + * would have done, unless it already has. + */ +- spin_lock(&data->irq_lock); + if (data->alive_intr_ctxt != BTINTEL_PCIE_D3) + data->alive_intr_ctxt = BTINTEL_PCIE_D3; + spin_unlock(&data->irq_lock); +@@ -3127,6 +3123,7 @@ static int btintel_pcie_set_dxstate(stru + return 0; + } + } ++ spin_unlock(&data->irq_lock); + + } while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES); + diff --git a/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch.sig b/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..93a7c8a871ff5c5f882558def4637d68b1e42191 GIT binary patch literal 594 zcmV-Y0Uum1Q z=8W%z0tMbKt~rbe3bJ1pcy@$d$nw;3Y?=#~RlQzc=*!=RNAokX)?#bZZG%WiNNCtb z=+iLE41riEnMjp~8cokb$3lfqvlOuQ0oLMNpd9Jrb<& z8c=%c(iHu{EW?jx|ELyIa%?An)dE* z4&U;c)`~tEeQhCkRk%fY=W{=9ZtWj(npg+l4P6SP*1fG&dM6 zfmC44n?F~%9_!x*q+nAI^ZmvG7wTrhX2Gn07JV)pXIA)j=BS#>vygvKp8!o;ZW}1q z7&&S&kkmzqC1I623soJ`KxSP=*B~Cq@AIZ7Q`PJvWXnC%Ik5)Y-TcXG9kE=(<9O3~ zOpV^%G4?JqNFvhX9-{|dg(k|wzV$>ta}p6*GqRHNnM*xTxtPIjBwcF7XOHW;83A^-pY literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch b/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch new file mode 100644 index 0000000..164dbb6 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c +--- a/drivers/bluetooth/btintel.c ++++ b/drivers/bluetooth/btintel.c +@@ -720,6 +720,7 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, + { + struct sk_buff *skb; + const u8 param[1] = { 0xFF }; ++ int err; + + if (!version) + return -EINVAL; +@@ -738,10 +739,10 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, + return -EIO; + } + +- btintel_parse_version_tlv(hdev, version, skb); ++ err = btintel_parse_version_tlv(hdev, version, skb); + + kfree_skb(skb); +- return 0; ++ return err; + } + + /* ------- REGMAP IBT SUPPORT ------- */ diff --git a/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch.sig b/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..caff3f4b70b29b693507a8fef68521d6496feeaa GIT binary patch literal 594 zcmV-Y0!q zr3X?uF-v8VChv8_92TF6fec=8aw=h7d8>FF4b9=uB;$-&PUslE`AiQ3m?ep(nSJ0< zMYd=u*&OvMPXjU0)`&3yv;PK0f$F;q=mj@*CinlX=OorN*TI1@3^jeb`M@fA9YFEj z_+XKj!V04Ib&s~y`q&pzZ{YH0_L%<;eB^yDa~SdHW>0MvA>nLpl?RYyT@Rdknlb~4 z)7bT{{U@1yg0(2E;rG_YtuuQ;k_2M|8R@CU`^dV(8o)RZ0WXr-&mC)9m>*_w;0jN0 z9Lj#g;l8e+s_s+-Sv;v|*=VmLQQ0*A#-{RIVl@J8t4>kOzs?}SKjhy>aft_eEwi1S zAOefgub355N{4-ZIfxr3LNU#lLM^hJU}=`kFg16#jj!Jxz>?bRdwZdO_s5Kx8*^IP zVsAz+&lu6H!*M{MB>*_Ily4b+1g`W(Vcw@zHU#H%`cx5SXesIo+%0wo<0|%PRNAG6 zF`Z7GE8rz=X(kU)2awzm$}gcWq_p)NdN!< literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch new file mode 100644 index 0000000..14623cd --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch @@ -0,0 +1,485 @@ +diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c +--- a/drivers/bluetooth/btintel.c ++++ b/drivers/bluetooth/btintel.c +@@ -288,7 +288,8 @@ void btintel_hw_error(struct hci_dev *hdev, u8 code) + goto unlock; + } + +- bt_dev_err(hdev, "Exception info %s", (char *)(skb->data + 1)); ++ bt_dev_err(hdev, "Exception info %.*s", (int)(skb->len - 1), ++ (char *)(skb->data + 1)); + + kfree_skb(skb); + +@@ -407,8 +408,16 @@ int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name) + /* DDC file contains one or more DDC structure which has + * Length (1 byte), DDC ID (2 bytes), and DDC value (Length - 2). + */ +- while (fw->size > fw_ptr - fw->data) { +- u8 cmd_plen = fw_ptr[0] + sizeof(u8); ++ while (fw->size > (size_t)(fw_ptr - fw->data)) { ++ size_t remaining = fw->size - (fw_ptr - fw->data); ++ unsigned int cmd_plen = fw_ptr[0] + 1U; ++ ++ if (cmd_plen < 3 || cmd_plen > U8_MAX || cmd_plen > remaining) { ++ bt_dev_err(hdev, "Malformed DDC record (plen=%u, remaining=%zu)", ++ cmd_plen, remaining); ++ release_firmware(fw); ++ return -EINVAL; ++ } + + skb = __hci_cmd_sync(hdev, 0xfc8b, cmd_plen, fw_ptr, + HCI_INIT_TIMEOUT); +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1200,7 +1200,7 @@ static int btintel_pcie_recv_frame(struct btintel_pcie_data *data, + { + int ret; + u8 pkt_type; +- u16 plen; ++ u32 plen; + u32 pcie_pkt_type; + void *pdata; + struct hci_dev *hdev = data->hdev; +@@ -2149,6 +2149,16 @@ static int btintel_pcie_send_frame(struct hci_dev *hdev, + if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) + return -ENODEV; + ++ /* Account for the 4-byte PCIe type header prepended before the ++ * DMA copy. Written as a subtraction to avoid wrap-around on ++ * attacker-controlled skb->len. ++ */ ++ if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { ++ bt_dev_err(hdev, "Packet too large: %u > %u", skb->len, ++ BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); ++ return -EMSGSIZE; ++ } ++ + /* Due to the fw limitation, the type header of the packet should be + * 4 bytes unlike 1 byte for UART. In UART, the firmware can read + * the first byte to get the packet type and redirect the rest of data +diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -62,7 +62,7 @@ struct inquiry_entry { + NAME_PENDING, + NAME_KNOWN, + } name_state; +- __u32 timestamp; ++ unsigned long timestamp; + struct inquiry_data data; + }; + +@@ -78,7 +78,7 @@ struct discovery_state { + struct list_head all; /* All devices found during inquiry */ + struct list_head unknown; /* Name state not known */ + struct list_head resolve; /* Name needs to be resolved */ +- __u32 timestamp; ++ unsigned long timestamp; + bdaddr_t last_adv_addr; + u8 last_adv_addr_type; + s8 last_adv_rssi; +@@ -561,6 +561,7 @@ struct hci_dev { + struct list_head link_keys; + struct list_head long_term_keys; + struct list_head identity_resolving_keys; ++ struct mutex remote_oob_lock; + struct list_head remote_oob_data; + struct list_head le_accept_list; + struct list_head le_resolv_list; +diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c +--- a/net/bluetooth/hci_conn.c ++++ b/net/bluetooth/hci_conn.c +@@ -302,11 +302,13 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + cp.tx_bandwidth = cpu_to_le32(0x00001f40); + cp.rx_bandwidth = cpu_to_le32(0x00001f40); + ++ hci_dev_lock(hdev); ++ + switch (conn->codec.id) { + case BT_CODEC_MSBC: + if (!find_next_esco_param(conn, esco_param_msbc, + ARRAY_SIZE(esco_param_msbc))) +- return -EINVAL; ++ goto unlock; + + param = &esco_param_msbc[conn->attempt - 1]; + cp.tx_coding_format.id = 0x05; +@@ -332,7 +334,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + case BT_CODEC_TRANSPARENT: + if (!find_next_esco_param(conn, esco_param_msbc, + ARRAY_SIZE(esco_param_msbc))) +- return -EINVAL; ++ goto unlock; + + param = &esco_param_msbc[conn->attempt - 1]; + cp.tx_coding_format.id = 0x03; +@@ -359,11 +361,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + if (conn->parent && lmp_esco_capable(conn->parent)) { + if (!find_next_esco_param(conn, esco_param_cvsd, + ARRAY_SIZE(esco_param_cvsd))) +- return -EINVAL; ++ goto unlock; + param = &esco_param_cvsd[conn->attempt - 1]; + } else { + if (conn->attempt > ARRAY_SIZE(sco_param_cvsd)) +- return -EINVAL; ++ goto unlock; + param = &sco_param_cvsd[conn->attempt - 1]; + } + cp.tx_coding_format.id = 2; +@@ -386,9 +388,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + cp.out_transport_unit_size = 16; + break; + default: +- return -EINVAL; ++ goto unlock; + } + ++ hci_dev_unlock(hdev); ++ + cp.retrans_effort = param->retrans_effort; + cp.pkt_type = __cpu_to_le16(param->pkt_type); + cp.max_latency = __cpu_to_le16(param->max_latency); +@@ -397,6 +401,10 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + return -EIO; + + return 0; ++ ++unlock: ++ hci_dev_unlock(hdev); ++ return -EINVAL; + } + + static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) +diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c +--- a/net/bluetooth/hci_core.c ++++ b/net/bluetooth/hci_core.c +@@ -1486,8 +1486,10 @@ int hci_remove_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + + BT_DBG("%s removing %pMR (%u)", hdev->name, bdaddr, bdaddr_type); + ++ mutex_lock(&hdev->remote_oob_lock); + list_del(&data->list); + kfree(data); ++ mutex_unlock(&hdev->remote_oob_lock); + + return 0; + } +@@ -1496,10 +1498,12 @@ void hci_remote_oob_data_clear(struct hci_dev *hdev) + { + struct oob_data *data, *n; + ++ mutex_lock(&hdev->remote_oob_lock); + list_for_each_entry_safe(data, n, &hdev->remote_oob_data, list) { + list_del(&data->list); + kfree(data); + } ++ mutex_unlock(&hdev->remote_oob_lock); + } + + int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, +@@ -1508,11 +1512,14 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + { + struct oob_data *data; + ++ mutex_lock(&hdev->remote_oob_lock); + data = hci_find_remote_oob_data(hdev, bdaddr, bdaddr_type); + if (!data) { + data = kmalloc_obj(*data); +- if (!data) ++ if (!data) { ++ mutex_unlock(&hdev->remote_oob_lock); + return -ENOMEM; ++ } + + bacpy(&data->bdaddr, bdaddr); + data->bdaddr_type = bdaddr_type; +@@ -1545,6 +1552,8 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + + BT_DBG("%s for %pMR", hdev->name, bdaddr); + ++ mutex_unlock(&hdev->remote_oob_lock); ++ + return 0; + } + +@@ -2482,6 +2491,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) + mutex_init(&hdev->lock); + mutex_init(&hdev->req_lock); + mutex_init(&hdev->mgmt_pending_lock); ++ mutex_init(&hdev->remote_oob_lock); + + ida_init(&hdev->unset_handle_ida); + +@@ -2554,8 +2564,10 @@ int hci_register_dev(struct hci_dev *hdev) + return id; + + error = dev_set_name(&hdev->dev, "hci%u", id); +- if (error) ++ if (error) { ++ ida_free(&hci_index_ida, id); + return error; ++ } + + hdev->name = dev_name(&hdev->dev); + hdev->id = id; +@@ -3323,6 +3335,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, + + skb_shinfo(skb)->frag_list = NULL; + ++ spin_lock_bh(&queue->lock); ++ + __skb_queue_tail(queue, skb); + + do { +@@ -3337,6 +3351,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, + + __skb_queue_tail(queue, skb); + } while (list); ++ ++ spin_unlock_bh(&queue->lock); + } + + bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(queue)); +@@ -3400,9 +3416,6 @@ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, + struct hci_conn *conn = NULL, *c; + unsigned int num = 0, min = ~0; + +- /* We don't have to lock device here. Connections are always +- * added and removed with TX task disabled. */ +- + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +@@ -3607,13 +3620,15 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt, u8 type) + } + + /* Schedule SCO */ +-static void hci_sched_sco(struct hci_dev *hdev, __u8 type) ++static void __hci_sched_sco(struct hci_dev *hdev, __u8 type) + { + struct hci_conn *conn; + struct sk_buff *skb; + int quote, *cnt; + unsigned int pkts = hdev->sco_pkts; + ++ lockdep_assert_held(&hdev->lock); ++ + bt_dev_dbg(hdev, "type %u", type); + + if (!hci_conn_num(hdev, type) || !pkts) +@@ -3648,6 +3663,13 @@ static void hci_sched_sco(struct hci_dev *hdev, __u8 type) + queue_work(hdev->workqueue, &hdev->tx_work); + } + ++static void hci_sched_sco(struct hci_dev *hdev, __u8 type) ++{ ++ hci_dev_lock(hdev); ++ __hci_sched_sco(hdev, type); ++ hci_dev_unlock(hdev); ++} ++ + static void hci_sched_acl_pkt(struct hci_dev *hdev) + { + unsigned int cnt = hdev->acl_cnt; +@@ -3657,6 +3679,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) + + __check_timeout(hdev, cnt, ACL_LINK); + ++ hci_dev_lock(hdev); ++ + while (hdev->acl_cnt && + (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { + u32 priority = (skb_peek(&chan->data_q))->priority; +@@ -3681,13 +3705,15 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev, SCO_LINK); +- hci_sched_sco(hdev, ESCO_LINK); ++ __hci_sched_sco(hdev, SCO_LINK); ++ __hci_sched_sco(hdev, ESCO_LINK); + } + } + + if (cnt != hdev->acl_cnt) + hci_prio_recalculate(hdev, ACL_LINK); ++ ++ hci_dev_unlock(hdev); + } + + static void hci_sched_acl(struct hci_dev *hdev) +@@ -3716,6 +3742,8 @@ static void hci_sched_le(struct hci_dev *hdev) + + __check_timeout(hdev, *cnt, LE_LINK); + ++ hci_dev_lock(hdev); ++ + tmp = *cnt; + while (*cnt && (chan = hci_chan_sent(hdev, LE_LINK, "e))) { + u32 priority = (skb_peek(&chan->data_q))->priority; +@@ -3737,13 +3765,15 @@ static void hci_sched_le(struct hci_dev *hdev) + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev, SCO_LINK); +- hci_sched_sco(hdev, ESCO_LINK); ++ __hci_sched_sco(hdev, SCO_LINK); ++ __hci_sched_sco(hdev, ESCO_LINK); + } + } + + if (*cnt != tmp) + hci_prio_recalculate(hdev, LE_LINK); ++ ++ hci_dev_unlock(hdev); + } + + /* Schedule iso */ +@@ -3762,6 +3792,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) + + __check_timeout(hdev, *cnt, type); + ++ hci_dev_lock(hdev); ++ + while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); +@@ -3775,6 +3807,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) + (*cnt)--; + } + } ++ ++ hci_dev_unlock(hdev); + } + + static void hci_tx_work(struct work_struct *work) +diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c +--- a/net/bluetooth/hci_debugfs.c ++++ b/net/bluetooth/hci_debugfs.c +@@ -364,7 +364,7 @@ static int inquiry_cache_show(struct seq_file *f, void *p) + + list_for_each_entry(e, &cache->all, all) { + struct inquiry_data *data = &e->data; +- seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %u\n", ++ seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %lu\n", + &data->bdaddr, + data->pscan_rep_mode, data->pscan_period_mode, + data->pscan_mode, data->dev_class[2], +diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -312,6 +312,10 @@ static void hci_cmd_sync_work(struct work_struct *work) + while (1) { + struct hci_cmd_sync_work_entry *entry; + ++ /* Leave the backlog to hci_cmd_sync_clear() */ ++ if (hci_dev_test_flag(hdev, HCI_UNREGISTER)) ++ break; ++ + mutex_lock(&hdev->cmd_sync_work_lock); + entry = list_first_entry_or_null(&hdev->cmd_sync_work_list, + struct hci_cmd_sync_work_entry, +@@ -658,6 +662,8 @@ void hci_cmd_sync_clear(struct hci_dev *hdev) + { + struct hci_cmd_sync_work_entry *entry, *tmp; + ++ /* cmd_work is disabled, the pending request can only time out */ ++ hci_cmd_sync_cancel_sync(hdev, ENODEV); + cancel_work_sync(&hdev->cmd_sync_work); + cancel_work_sync(&hdev->reenable_adv_work); + +@@ -5678,6 +5684,7 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) + { + struct discovery_state *d = &hdev->discovery; + struct inquiry_entry *e; ++ bdaddr_t addr; + int err; + + bt_dev_dbg(hdev, "state %u", hdev->discovery.state); +@@ -5713,15 +5720,21 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) + return 0; + + if (d->state == DISCOVERY_RESOLVING || d->state == DISCOVERY_STOPPING) { ++ hci_dev_lock(hdev); + e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, + NAME_PENDING); +- if (!e) ++ if (!e) { ++ hci_dev_unlock(hdev); + return 0; ++ } ++ ++ bacpy(&addr, &e->data.bdaddr); ++ hci_dev_unlock(hdev); + + /* Ignore cancel errors since it should interfere with stopping + * of the discovery. + */ +- hci_remote_name_cancel_sync(hdev, &e->data.bdaddr); ++ hci_remote_name_cancel_sync(hdev, &addr); + } + + return 0; +@@ -7151,6 +7164,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) + bacpy(&cp.bdaddr, &conn->dst); + cp.pscan_rep_mode = 0x02; + ++ hci_dev_lock(hdev); + ie = hci_inquiry_cache_lookup(hdev, &conn->dst); + if (ie) { + if (inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { +@@ -7162,6 +7176,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) + + memcpy(conn->dev_class, ie->data.dev_class, 3); + } ++ hci_dev_unlock(hdev); + + cp.pkt_type = cpu_to_le16(conn->pkt_type); + if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER)) +diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c +--- a/net/bluetooth/rfcomm/tty.c ++++ b/net/bluetooth/rfcomm/tty.c +@@ -462,7 +462,7 @@ static int __rfcomm_release_dev(void __user *arg) + /* Shut down TTY synchronously before freeing rfcomm_dev */ + tty_port_tty_vhangup(&dev->port); + +- if (!test_bit(RFCOMM_TTY_OWNED, &dev->status)) ++ if (!test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) + tty_port_put(&dev->port); + + tty_port_put(&dev->port); +@@ -724,10 +724,9 @@ static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty) + * when the last process closes the tty. The behaviour is expected by + * userspace. + */ +- if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { +- set_bit(RFCOMM_TTY_OWNED, &dev->status); ++ if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags) && ++ !test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) + tty_port_put(&dev->port); +- } + + return 0; + } +diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c +--- a/net/bluetooth/smp.c ++++ b/net/bluetooth/smp.c +@@ -661,6 +661,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, + else + bdaddr_type = BDADDR_LE_RANDOM; + ++ mutex_lock(&hdev->remote_oob_lock); + oob_data = hci_find_remote_oob_data(hdev, &hcon->dst, + bdaddr_type); + if (oob_data && oob_data->present) { +@@ -671,6 +672,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, + SMP_DBG("OOB Remote Confirmation: %16phN", smp->pcnf); + SMP_DBG("OOB Remote Random: %16phN", smp->rr); + } ++ mutex_unlock(&hdev->remote_oob_lock); + + } else { + authreq &= ~SMP_AUTH_SC; diff --git a/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..cadba43c3f6e60fd301df201d20119fb38b0336d GIT binary patch literal 594 zcmV-Y0zXo;wSP<#Cm%q%fC_2?o6fxRaIog|adsU&uVQLFgW zDBYF-ZPFZk?%VU++S|R~fh8A_!`NMbwyV%*az1KEJn2l9apjzUBzdS@3X17G!#VnR zG68Zh3!!Ei#ni8R1KqewEokK$R>8>bdu!FZ6)cvTQh+09z(bq9;%9mPV2vpg zs(|I3?BI1v4^kodCftFfNxDMXj@L5;oULpg+wsC&k@Cs*n8VlQ9bc3*#4$A^EMqL* zC?7br$r-rbo&jay%mIm!>dKWHz>J$TwD3^uj)&&`#&WkHIr>0IRl4OX7D3ITk=3e% z3&Kpg0p*LZvnf2ME9`?pBgZfer65WvV{PH8j_nh=37RQ#YB6xb;lP!90CUs*F}MRV zF-S5@%Sj|gl#|~O1da0@`aQP6N!h7jg)+WA{fMvnO|mSTg8I1Ns07LqTBZBukg7h& gxB?pkg}DzoR7^Gv5)W8W@Mtz&pJV3^pv?gv5w>0y(f|Me literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch b/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch new file mode 100644 index 0000000..1102b3f --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -108,7 +108,11 @@ int pcie_failed_link_retrain(struct pci_dev *dev) + + pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta); + pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2); +- if (!(lnksta & PCI_EXP_LNKSTA_DLLLA) && pcie_lbms_seen(dev, lnksta)) { ++ if (lnksta & PCI_EXP_LNKSTA_DLLLA) { ++ ; ++ } else if (PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2) == PCIE_SPEED_2_5GT) { ++ return ret; ++ } else if (pcie_lbms_seen(dev, lnksta)) { + pci_info(dev, "broken device, retraining non-functional downstream link at 2.5GT/s\n"); + ret = pcie_set_target_speed(dev, PCIE_SPEED_2_5GT, false); + if (ret) diff --git a/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch.sig b/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..2b1e9cc93e5ad3477090ac0762ef3d21f2dd1db2 GIT binary patch literal 594 zcmV-Y0Q00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=a}LT{Tb@%%y*R0dWV!(kYe&M z7bK9hVjJHRzN;+SoZ(q3|5ERVV@iGo&pbE{I2MKcG%QkxTTZDB{cwrLK!%Z6R05SEx zI zc>jGXfPsA8g;C972%m7vOYpuv4UrmOj&(9TRS4GnGbK$)ZGw4`Eo%Bv(t*4@AggQt zo(`rXVXcGU%0wjbh}ZuV>`y#lTiffck`^rXvdR83-_rv=s_v^UH&~=`IKrdNP)nzr z#RW)-NzWCMwmWBtqf&ZCr>4>jnq}K)yUt3* zu!+wBMa>)`!bf|0fhSFr_odhugv=-yZ`_I literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch b/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch new file mode 100644 index 0000000..c76cb96 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch @@ -0,0 +1,109 @@ +diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c +--- a/arch/x86/pci/fixup.c ++++ b/arch/x86/pci/fixup.c +@@ -886,6 +886,105 @@ static void quirk_clear_strap_no_soft_reset_dev2_f0(struct pci_dev *dev) + } + } + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15b8, quirk_clear_strap_no_soft_reset_dev2_f0); ++ ++/* ++ * Enhanced atomic operations can cause corruption with 64-bit IOVA ++ * on these devices. ++ */ ++#define RX_ENH_ATOMIC_EN BIT(8) ++ ++static const u32 nbio_7_7_pcie_smn_addrs[] = { ++ 0x111401d0, ++ 0x111411d0, ++ 0x111421d0, ++ 0x111431d0, ++ 0x111441d0, ++ 0x112401d0, ++ 0x112411d0, ++ 0x112421d0, ++ 0x112431d0, ++ 0x112441d0, ++ 0x112451d0, ++ 0x113401d0, ++ 0x114401d0, ++}; ++ ++static const u32 nbio_7_11_pcie_smn_addrs[] = { ++ 0x112401d0, ++ 0x112411d0, ++ 0x112421d0, ++ 0x112431d0, ++ 0x112441d0, ++ 0x112451d0, ++ 0x113401d0, ++ 0x113411d0, ++ 0x113421d0, ++ 0x113431d0, ++ 0x113441d0, ++ 0x113451d0, ++}; ++ ++static void quirk_amd_nbio_enhanced_atomic(struct pci_dev *host_bridge, ++ const u32 *smn_addrs, ++ size_t nr_smn_addrs) ++{ ++ bool changed = false; ++ size_t i; ++ u32 data; ++ int ret; ++ ++ for (i = 0; i < nr_smn_addrs; i++) { ++ ret = amd_smn_read(0, smn_addrs[i], &data); ++ if (ret) ++ continue; ++ if (!(data & RX_ENH_ATOMIC_EN)) ++ continue; ++ data = data & ~RX_ENH_ATOMIC_EN; ++ ret = amd_smn_write(0, smn_addrs[i], data); ++ if (ret) ++ continue; ++ if (changed) ++ continue; ++ ret = amd_smn_read(0, smn_addrs[i], &data); ++ if (ret) ++ continue; ++ if (data & RX_ENH_ATOMIC_EN) ++ continue; ++ changed = true; ++ } ++ ++ if (changed) ++ pci_info(host_bridge, "enhanced atomics disabled\n"); ++} ++ ++static void quirk_amd_nbio_7_7_disable_enhanced_atomic(struct pci_dev *dev) ++{ ++ quirk_amd_nbio_enhanced_atomic(dev, nbio_7_7_pcie_smn_addrs, ++ ARRAY_SIZE(nbio_7_7_pcie_smn_addrs)); ++} ++ ++static void quirk_amd_nbio_7_11_disable_enhanced_atomic(struct pci_dev *dev) ++{ ++ quirk_amd_nbio_enhanced_atomic(dev, nbio_7_11_pcie_smn_addrs, ++ ARRAY_SIZE(nbio_7_11_pcie_smn_addrs)); ++} ++ ++/* Phoenix, Hawk Point (NBIO 7.7) */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x14E8, ++ quirk_amd_nbio_7_7_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x14E8, ++ quirk_amd_nbio_7_7_disable_enhanced_atomic); ++ ++/* Strix, Krackan, Strix Halo (NBIO 7.11) */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1507, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1507, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1122, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1122, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++ + #endif + + /* diff --git a/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch.sig b/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..77322198eace3de81e264c1c844e23d03a8e733a GIT binary patch literal 594 zcmV-Y0`t&OVh+|F(5vA=m83WeT29>H+bw@upCZp)`;h z?FxX0%)cD_tc3p=T^$fs_l_T>&$mV;@?MS&-fAi8t-Q5UIhw!1QNic8ltP8|0z)bk z$3o4cO_pb8dXrO&zTc=vCLi@{IY2oR!rB!$r2wpNIpD-Drw`Mj4WfrRxne$qbhNVk zQdmRDDX2s!>C;fDv2+qGZ$TZb+}1R!F*cM;+;3fby#h4!rgiNX`5CFy6xxk1)B!p3 zJL5puZI=`WrBQa?j3D$?XAe!}m@O10eJitL;GU$!D<1+$MT{`{sN0NRFI225p!x5` z_Q1fWO^FIzgHL))IcO(r_TYZBU+ZGTxZZ(BdhY#%#F gXwQYwkZ|r+&PPe$;ASQ0Lf$AAw&;#uQ2CUJ-h5&h5dZ)H literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch b/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch new file mode 100644 index 0000000..9794028 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch @@ -0,0 +1,42 @@ +diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c +--- a/drivers/ata/ahci.c ++++ b/drivers/ata/ahci.c +@@ -68,7 +68,6 @@ enum board_ids { + /* board IDs for specific chipsets in alphabetical order */ + board_ahci_al, + board_ahci_avn, +- board_ahci_jmb585, + board_ahci_mcp65, + board_ahci_mcp77, + board_ahci_mcp89, +@@ -213,15 +212,6 @@ static const struct ata_port_info ahci_port_info[] = { + .udma_mask = ATA_UDMA6, + .port_ops = &ahci_avn_ops, + }, +- /* JMicron JMB582/585: 64-bit DMA is broken, force 32-bit */ +- [board_ahci_jmb585] = { +- AHCI_HFLAGS (AHCI_HFLAG_IGN_IRQ_IF_ERR | +- AHCI_HFLAG_32BIT_ONLY), +- .flags = AHCI_FLAG_COMMON, +- .pio_mask = ATA_PIO4, +- .udma_mask = ATA_UDMA6, +- .port_ops = &ahci_ops, +- }, + [board_ahci_mcp65] = { + AHCI_HFLAGS (AHCI_HFLAG_NO_FPDMA_AA | AHCI_HFLAG_NO_PMP | + AHCI_HFLAG_YES_NCQ), +@@ -954,14 +944,6 @@ static const struct pci_device_id ahci_pci_tbl[] = { + /* Elkhart Lake AHCI */ + PCI_VDEVICE(INTEL, 0x4b63), + .driver_data = board_ahci_pcs_quirk, +- }, { +- /* JMicron JMB582/585: force 32-bit DMA (broken 64-bit implementation) */ +- PCI_VDEVICE(JMICRON, 0x0582), +- .driver_data = board_ahci_jmb585, +- +- }, { +- PCI_VDEVICE(JMICRON, 0x0585), +- .driver_data = board_ahci_jmb585, + }, { + /* JMicron 360/1/3/5/6, match class to avoid IDE function */ + PCI_DEVICE(PCI_VENDOR_ID_JMICRON, PCI_ANY_ID), diff --git a/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch.sig b/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..58c4c7c69a56ef07d99b46399ddd265f4bc2b34c GIT binary patch literal 594 zcmV-Y0Dvg!rwzYEzQHAcnTOCrg{|eFGe2piYm(A+fsCQu5E{Hl^;01h=Tn@SPJ4>s;W3+Y|Kjpu~yDZ}67f$*a~Gl0hUn`)f~b8g(P&L5(V|il^G3i1n-R_#w%e gPI#1Oe?I|gmJ;3ixNyKLj#Jr(#E#|be+Zb#(9hH$jsO4v literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch b/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch new file mode 100644 index 0000000..0fe6c04 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch @@ -0,0 +1,297 @@ +diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -37,6 +37,12 @@ + #define MB2_SHADOW_OFFSET 0x2000 + #define MB2_SHADOW_SIZE 16 + ++/* DMR BAR4 register offsets */ ++#define SHADOW_MEMBAR1_28C1 0x2818 /* MEMBAR1 physical address */ ++#define SHADOW_MEMBAR2_28C1 0x2820 /* MEMBAR2 physical address */ ++#define BASE_ID_REG_28C1 0x2840 ++#define MEMBAR2_OFFSET_28C1 0x30d0 ++ + enum vmd_features { + /* + * Device may contain registers which hint the physical location of the +@@ -77,6 +83,15 @@ enum vmd_features { + * proper power management of the SoC. + */ + VMD_FEAT_BIOS_PM_QUIRK = (1 << 5), ++ ++ /* ++ * Newer VMD with device ID 0x28C1 has unique settings compared to its ++ * predecessor where BIOS enumerates the entire VMD device tree and ++ * stores respective configurations including bus start range and ++ * shadow registers in VMD MMIO space in VMD BAR4/BAR5, otherwise ++ * referred to as MEMBAR2 or MSI-X BAR. ++ */ ++ VMD_FEAT_USE_BIOS_INFO = (1 << 6), + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -142,6 +157,7 @@ struct vmd_dev { + u8 first_vec; + char *name; + int instance; ++ unsigned long features; + }; + + static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus) +@@ -366,6 +382,9 @@ static void vmd_set_msi_remapping(struct vmd_dev *vmd, bool enable) + { + u16 reg; + ++ if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO)) ++ return; ++ + pci_read_config_word(vmd->dev, PCI_REG_VMCONFIG, ®); + reg = enable ? (reg & ~VMCONFIG_MSI_REMAP) : + (reg | VMCONFIG_MSI_REMAP); +@@ -389,11 +408,22 @@ static void vmd_remove_irq_domain(struct vmd_dev *vmd) + } + } + ++static unsigned int vmd_bus_to_ecam(struct vmd_dev *vmd, unsigned int busnr) ++{ ++ if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO)) ++ return busnr; ++ ++ return busnr - vmd->busn_start; ++} ++ + static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, + unsigned int devfn, int reg, int len) + { +- unsigned int busnr_ecam = bus->number - vmd->busn_start; +- u32 offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); ++ unsigned int busnr_ecam; ++ u32 offset; ++ ++ busnr_ecam = vmd_bus_to_ecam(vmd, bus->number); ++ offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); + + if (offset + len >= resource_size(&vmd->dev->resource[VMD_CFGBAR])) + return NULL; +@@ -518,22 +548,37 @@ static inline void vmd_acpi_begin(void) { } + static inline void vmd_acpi_end(void) { } + #endif /* CONFIG_ACPI */ + ++static resource_size_t vmd_cfgbar_ecam_space(struct vmd_dev *vmd) ++{ ++ resource_size_t cfgbar_buses; ++ unsigned int ecam_start; ++ ++ cfgbar_buses = resource_size(&vmd->dev->resource[VMD_CFGBAR]) >> 20; ++ ecam_start = vmd_bus_to_ecam(vmd, vmd->resources[0].start); ++ if (ecam_start >= cfgbar_buses) ++ return 0; ++ ++ return cfgbar_buses - ecam_start; ++} + static void vmd_domain_reset(struct vmd_dev *vmd) + { + u16 bus, max_buses = resource_size(&vmd->resources[0]); + u8 dev, functions, fn, hdr_type; ++ unsigned int ecam_bus; + char __iomem *base; + ++ max_buses = min_t(u16, max_buses, vmd_cfgbar_ecam_space(vmd)); + for (bus = 0; bus < max_buses; bus++) { ++ ecam_bus = vmd_bus_to_ecam(vmd, vmd->resources[0].start + bus); + for (dev = 0; dev < 32; dev++) { +- base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, ++ base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus, + PCI_DEVFN(dev, 0), 0); + + hdr_type = readb(base + PCI_HEADER_TYPE); + + functions = (hdr_type & PCI_HEADER_TYPE_MFD) ? 8 : 1; + for (fn = 0; fn < functions; fn++) { +- base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, ++ base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus, + PCI_DEVFN(dev, fn), 0); + + hdr_type = readb(base + PCI_HEADER_TYPE) & +@@ -661,6 +706,46 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd) + return 0; + } + ++static int vmd_get_bus_info_from_bar4(struct vmd_dev *vmd, ++ resource_size_t *offset1, ++ resource_size_t *offset2) ++{ ++ u64 phys1, phys2, bar4_2840; ++ void __iomem *bar4; ++ u32 base_id; ++ u8 base_bus; ++ ++ bar4 = pci_ioremap_bar(vmd->dev, 4); ++ if (!bar4) ++ return -ENOMEM; ++ ++ /* Read shadow registers for MEMBAR1 and MEMBAR2 physical addresses */ ++ phys1 = readq(bar4 + SHADOW_MEMBAR1_28C1); ++ phys2 = readq(bar4 + SHADOW_MEMBAR2_28C1); ++ ++ /* ++ * Read and set bus start number from Base ID register. 24-bit Base ID ++ * register is part of 64-bit shadowed reqid hide range register and ++ * holds segment, bus, device and function. ++ */ ++ bar4_2840 = readq(bar4 + BASE_ID_REG_28C1); ++ base_id = bar4_2840 & 0xFFFFFF; ++ base_bus = base_id >> 8; ++ vmd->busn_start = base_bus; ++ ++ /* Calculate offsets like vmd_get_phys_offsets() does */ ++ if (phys1) ++ *offset1 = vmd->dev->resource[VMD_MEMBAR1].start - ++ (phys1 & PCI_BASE_ADDRESS_MEM_MASK); ++ if (phys2) ++ *offset2 = vmd->dev->resource[VMD_MEMBAR2].start - ++ (phys2 & PCI_BASE_ADDRESS_MEM_MASK); ++ ++ pci_iounmap(vmd->dev, bar4); ++ ++ return 0; ++} ++ + static irqreturn_t vmd_irq(int irq, void *data) + { + struct vmd_irq_list *irqs = data; +@@ -711,6 +796,52 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) + return 0; + } + ++static int vmd_prepare_offsets_and_bus(struct vmd_dev *vmd, ++ unsigned long features, ++ resource_size_t *membar2_offset, ++ resource_size_t *offset1, ++ resource_size_t *offset2) ++{ ++ int ret; ++ ++ /* ++ * Shadow registers may exist in certain VMD device IDs which allow ++ * guests to correctly assign host physical addresses to the root ports ++ * and child devices. These registers will either return the host value ++ * or 0, depending on an enable bit in the VMD device. ++ * ++ * For certain VMD devices (i.e. 0x28C1), BIOS places device info ++ * in BAR4 shadow registers to determine the base bus number and memory ++ * offsets. ++ */ ++ if (features & VMD_FEAT_USE_BIOS_INFO) { ++ *membar2_offset = MEMBAR2_OFFSET_28C1; ++ ret = vmd_get_bus_info_from_bar4(vmd, offset1, offset2); ++ if (ret) ++ return ret; ++ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) { ++ *membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE; ++ ret = vmd_get_phys_offsets(vmd, true, offset1, offset2); ++ if (ret) ++ return ret; ++ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) { ++ ret = vmd_get_phys_offsets(vmd, false, offset1, offset2); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Certain VMD devices may have a root port configuration option which ++ * limits the bus range to between 0-127, 128-255, or 224-255. ++ */ ++ if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) { ++ ret = vmd_get_bus_number_start(vmd); ++ if (ret) ++ return ret; ++ } ++ return 0; ++} ++ + /* + * Since VMD is an aperture to regular PCIe root ports, only allow it to + * control features that the OS is allowed to control on the physical PCI bus. +@@ -780,42 +911,24 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) + LIST_HEAD(resources); + resource_size_t offset[2] = {0}; + resource_size_t membar2_offset = 0x2000; ++ resource_size_t busn_end; + struct pci_bus *child; + struct pci_dev *dev; + int ret; + +- /* +- * Shadow registers may exist in certain VMD device ids which allow +- * guests to correctly assign host physical addresses to the root ports +- * and child devices. These registers will either return the host value +- * or 0, depending on an enable bit in the VMD device. +- */ +- if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) { +- membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE; +- ret = vmd_get_phys_offsets(vmd, true, &offset[0], &offset[1]); +- if (ret) +- return ret; +- } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) { +- ret = vmd_get_phys_offsets(vmd, false, &offset[0], &offset[1]); +- if (ret) +- return ret; +- } +- +- /* +- * Certain VMD devices may have a root port configuration option which +- * limits the bus range to between 0-127, 128-255, or 224-255 +- */ +- if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) { +- ret = vmd_get_bus_number_start(vmd); +- if (ret) +- return ret; +- } ++ ret = vmd_prepare_offsets_and_bus(vmd, features, &membar2_offset, ++ &offset[0], &offset[1]); ++ if (ret) ++ return ret; + ++ /* Do not let resource[0] end go out of bounds */ + res = &vmd->dev->resource[VMD_CFGBAR]; ++ busn_end = vmd->busn_start + (resource_size(res) >> 20) - 1; ++ busn_end = min_t(resource_size_t, busn_end, 0xff); + vmd->resources[0] = (struct resource) { + .name = "VMD CFGBAR", + .start = vmd->busn_start, +- .end = vmd->busn_start + (resource_size(res) >> 20) - 1, ++ .end = busn_end, + .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, + }; + +@@ -998,6 +1111,7 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) + + vmd->dev = dev; + vmd->sysdata.domain = PCI_DOMAIN_NR_NOT_SET; ++ vmd->features = features; + vmd->instance = ida_alloc(&vmd_instance_ida, GFP_KERNEL); + if (vmd->instance < 0) + return vmd->instance; +@@ -1114,6 +1228,10 @@ static const struct pci_device_id vmd_ids[] = { + .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | + VMD_FEAT_HAS_BUS_RESTRICTIONS | + VMD_FEAT_CAN_BYPASS_MSI_REMAP,}, ++ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_28C1), ++ .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | ++ VMD_FEAT_CAN_BYPASS_MSI_REMAP | ++ VMD_FEAT_USE_BIOS_INFO,}, + {PCI_VDEVICE(INTEL, 0x467f), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, 0x4c3d), +diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h +--- a/include/linux/pci_ids.h ++++ b/include/linux/pci_ids.h +@@ -2892,6 +2892,7 @@ + #define PCI_DEVICE_ID_INTEL_HDA_ICH8 0x284b + #define PCI_DEVICE_ID_INTEL_ICH8_6 0x2850 + #define PCI_DEVICE_ID_INTEL_VMD_28C0 0x28c0 ++#define PCI_DEVICE_ID_INTEL_VMD_28C1 0x28c1 + #define PCI_DEVICE_ID_INTEL_ICH9_0 0x2910 + #define PCI_DEVICE_ID_INTEL_ICH9_2 0x2912 + #define PCI_DEVICE_ID_INTEL_ICH9_3 0x2913 diff --git a/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig b/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..7e9f3d1c08843ae8f7f20fefbc2e12e49de35f1b GIT binary patch literal 594 zcmV-Y0+Q`n0}uu zYbd_~OmK)|X-d-r^=l%D+0BQLPSgzm!@**-++$S82%c2|HfuLGSaYNvOSkUBfeTlS zz&Fs@C!d-EM3oY-AK!LQGk#BiYoT?uv7DpRqLrxI$s??QNxGJLU}J@ zR``Vj}xf$`vq*6f;ULvrD?;xu)hwEiM&tee29kGU& zv$JH(&DlwL1kJNIcQrLpZuB~gn8Y34tQP-m^Z+ls%o}A;;C5~On8eo1Ce#YJ3s!Za gM9hjU)PTZId%obd#Znos?oqWNI;K;IIfw#P(ZRh9H~;_u literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch b/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch new file mode 100644 index 0000000..22be8d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch @@ -0,0 +1,120 @@ +diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -92,6 +92,22 @@ enum vmd_features { + * referred to as MEMBAR2 or MSI-X BAR. + */ + VMD_FEAT_USE_BIOS_INFO = (1 << 6), ++ ++ /* ++ * Meteor Lake VMD (device ID 0x7d0b) is affected by erratum MTL016: ++ * the VMD may signal its MSI before the posted writes that carry the ++ * child device's DMA data have landed in memory. The demuxed handler ++ * then runs against a not-yet-coherent completion queue and misses the ++ * completion entirely, so the I/O is only recovered when the block ++ * layer timeout fires and polls the queue ("timeout, completion ++ * polled"). Intel's documented workaround is to issue a dummy read ++ * to the MSI initiator (the child device) before handling the ++ * interrupt: the read completion cannot pass the device's earlier ++ * posted writes, so it pulls them into memory per PCIe ordering ++ * rules. A read that terminates at the VMD itself does not order ++ * against the child's writes and is not sufficient (measured). ++ */ ++ VMD_FEAT_INTERRUPT_QUIRK = (1 << 7), + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -114,6 +130,9 @@ static DEFINE_RAW_SPINLOCK(list_lock); + * @irq: back pointer to parent. + * @enabled: true if driver enabled IRQ + * @virq: the virtual IRQ value provided to the requesting driver. ++ * @flush_addr: config space address of the initiating device, read before ++ * demuxing to flush its posted writes (MTL016); NULL if the ++ * VMD is not affected. + * + * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to + * a VMD IRQ using this structure. +@@ -123,8 +142,14 @@ struct vmd_irq { + struct vmd_irq_list *irq; + bool enabled; + unsigned int virq; ++ void __iomem *flush_addr; + }; + ++struct vmd_dev; ++ ++static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, ++ unsigned int devfn, int reg, int len); ++ + /** + * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector + * @irq_list: the list of irq's the VMD one demuxes to. +@@ -132,12 +157,15 @@ struct vmd_irq { + * @count: number of child IRQs assigned to this vector; used to track + * sharing. + * @virq: The underlying VMD Linux interrupt number ++ * @vmd: back pointer to the owning VMD device; serializes the MTL016 ++ * flush read against other config space access. + */ + struct vmd_irq_list { + struct list_head irq_list; + struct srcu_struct srcu; + unsigned int count; + unsigned int virq; ++ struct vmd_dev *vmd; + }; + + struct vmd_dev { +@@ -295,6 +323,13 @@ static int vmd_msi_alloc(struct irq_domain *domain, unsigned int virq, + INIT_LIST_HEAD(&vmdirq->node); + vmdirq->irq = vmd_next_irq(vmd, desc); + vmdirq->virq = virq + i; ++ if (vmd->features & VMD_FEAT_INTERRUPT_QUIRK) { ++ struct pci_dev *pdev = msi_desc_to_pci_dev(desc); ++ ++ vmdirq->flush_addr = vmd_cfg_addr(vmd, pdev->bus, ++ pdev->devfn, ++ PCI_VENDOR_ID, 2); ++ } + + irq_domain_set_info(domain, virq + i, vmdirq->irq->virq, + &vmd_msi_controller, vmdirq, +@@ -753,8 +788,20 @@ static irqreturn_t vmd_irq(int irq, void *data) + int idx; + + idx = srcu_read_lock(&irqs->srcu); +- list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) ++ list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) { ++ /* ++ * MTL016: the MSI may have outrun the initiating device's ++ * posted writes (e.g. its NVMe completion entry). A read ++ * that completes at the initiator flushes them, so the ++ * demuxed handler observes a coherent completion queue. ++ * The value is discarded; only the ordering matters. ++ */ ++ if (vmdirq->flush_addr) { ++ guard(raw_spinlock)(&irqs->vmd->cfg_lock); ++ readw(vmdirq->flush_addr); ++ } + generic_handle_irq(vmdirq->virq); ++ } + srcu_read_unlock(&irqs->srcu, idx); + + return IRQ_HANDLED; +@@ -785,6 +832,7 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) + return err; + + INIT_LIST_HEAD(&vmd->irqs[i].irq_list); ++ vmd->irqs[i].vmd = vmd; + vmd->irqs[i].virq = pci_irq_vector(dev, i); + err = devm_request_irq(&dev->dev, vmd->irqs[i].virq, + vmd_irq, IRQF_NO_THREAD, +@@ -1239,7 +1287,7 @@ static const struct pci_device_id vmd_ids[] = { + {PCI_VDEVICE(INTEL, 0xa77f), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, 0x7d0b), +- .driver_data = VMD_FEATS_CLIENT,}, ++ .driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,}, + {PCI_VDEVICE(INTEL, 0xad0b), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), diff --git a/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..69bd67e3d3eed2c4303fe8bf7faeed1d05edcb73 GIT binary patch literal 594 zcmV-Y0b1q3}$*pMSJdOYTdraLv($tlhEa;6naIQ0thor*VQv z-3P_(_lVtl!+{VJOvNM-`5%c`n=%(M0P|k+nGrn8 z{`F;zfM=m$BXH*;9z(62(QGjmc)WugJp7A9+2R!r;pKm1Q0?AXyWK-(<2F-1nuZ=; zmust!lRmhE2R!geph-(DHardl9_Nljwxu<2whfFv*|tzauHQxpOd7R0^1c5MY$h~q gdfPp9e`_GNK+x-1r26t-*9H>>)`A%R&AcHWMx*Hu(*OVf literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch b/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch new file mode 100644 index 0000000..49806cf --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -4370,6 +4370,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_JMICRON, + DECLARE_PCI_FIXUP_HEADER(0x1c28, /* Lite-On */ + 0x0122, /* Plextor M6E (Marvell 88SS9183)*/ + quirk_dma_func1_alias); ++/* Toshiba AHCI SSD controller in MacBookAir6,1 and MacBookAir7,1 */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TOSHIBA, 0x010b, ++ quirk_dma_func1_alias); + + /* + * Some devices DMA with the wrong devfn, not just the wrong function. diff --git a/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig b/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..ab588ee831f4810f3289dfeae94fb1b52457d357 GIT binary patch literal 594 zcmV-Y0_sWI9Lfpe$y1)a{ zgtyNx>G0GRVVk-R2SYjlkzi5N>y6!J5Wn2F+~(Q7v+|~(bo*qN9eL(#xL9_KYz-_3 zs(fi%PGIgH>SI+*?~o^QYNXG>>y4@Y-rR%v;70Lh{)$t2-FWE0jLkFn7QoUR8>8fF z8(55TSbbY4yC~9_HLij8Zoc8#ygm+HsmbD7t_<&aZUfL9J~OzzyP0pEq`E%fLGSY* z)JZrd6aPumnrxd(v#$Wsehc<7l5whS? ziWU)E7__Z04Mz$vJ9e8bmp90D_m6b`$kh|op34b2C|$ zv;$*++U{uZgON#Nr1Oy_d0+sE9<^{dZ#cG6twu_(N%w~qUBd;1obz8$_#ldqpC?4{ z>)2p)h|wj4=$T6zzxUW2Q%=IxyqX796}&-S3esiBH%w<0Jkdf5Msp`})zqc7N(OIH zK>ow81vBy%M-38aCDG?jxT`*zJPq@d{-!%(hFsQYW}|2qqlH8qgCU3}rAXcF5#T&6 zI8Gmcw5T^Bu_-pKp1ARR*2}_3J|-avH2h0)mC3*lJ;KV)rTt9q$s{Kd%9=Ol$_92K gNzv~yzy?S%^$ds)z(JYN54uz_kXHDoL$Q}Y$i6BX(f|Me literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch b/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch new file mode 100644 index 0000000..4b13fed --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch @@ -0,0 +1,413 @@ +diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h +--- a/drivers/iommu/amd/amd_iommu.h ++++ b/drivers/iommu/amd/amd_iommu.h +@@ -48,6 +48,9 @@ extern u8 amd_iommu_hpt_vasize; + extern unsigned long amd_iommu_pgsize_bitmap; + extern bool amd_iommu_hatdis; + ++int amd_iommu_perfopt_clear(struct amd_iommu *iommu); ++int amd_iommu_perfopt_restore(struct amd_iommu *iommu); ++ + /* Protection domain ops */ + void amd_iommu_init_identity_domain(void); + struct protection_domain *protection_domain_alloc(void); +diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h +--- a/drivers/iommu/amd/amd_iommu_types.h ++++ b/drivers/iommu/amd/amd_iommu_types.h +@@ -65,6 +65,7 @@ + #define MMIO_MSI_ADDR_LO_OFFSET 0x015C + #define MMIO_MSI_ADDR_HI_OFFSET 0x0160 + #define MMIO_MSI_DATA_OFFSET 0x0164 ++#define MMIO_PERF_OPT_OFFSET 0x016C + #define MMIO_INTCAPXT_EVT_OFFSET 0x0170 + #define MMIO_INTCAPXT_PPR_OFFSET 0x0178 + #define MMIO_INTCAPXT_GALOG_OFFSET 0x0180 +@@ -99,6 +100,8 @@ + #define FEATURE_GLX GENMASK_ULL(15, 14) + #define FEATURE_GAM_VAPIC BIT_ULL(21) + #define FEATURE_PASMAX GENMASK_ULL(36, 32) ++#define FEATURE_PERF_OPT BIT_ULL(45) ++#define PERF_OPT_EN BIT(13) + #define FEATURE_GIOSUP BIT_ULL(48) + #define FEATURE_HASUP BIT_ULL(49) + #define FEATURE_EPHSUP BIT_ULL(50) +@@ -663,6 +666,9 @@ struct amd_iommu { + /* Extended features 2 */ + u64 features2; + ++ /* Devices requesting PerfOpt; the shared PERF_OPT_EN bit is on while >0. Protected by @lock. */ ++ int perfopt_refcount; ++ + /* PCI device id of the IOMMU device */ + u16 devid; + +@@ -824,6 +830,7 @@ struct iommu_dev_data { + u8 ppr :1; /* Enable device PPR support */ + bool use_vapic; /* Enable device to use vapic mode */ + bool defer_attach; ++ bool perfopt; + + struct ratelimit_state rs; /* Ratelimit IOPF messages */ + }; +diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c +--- a/drivers/iommu/amd/init.c ++++ b/drivers/iommu/amd/init.c +@@ -1945,6 +1945,9 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h, + if (!iommu->mmio_base) + return -ENOMEM; + ++ if (amd_iommu_perfopt_clear(iommu)) ++ pr_err("IOMMU%d: failed to clear PerfOpt\n", iommu->index); ++ + return init_iommu_from_acpi(iommu, h); + } + +@@ -3035,10 +3038,46 @@ static void enable_iommus_vapic(void) + #endif + } + ++static int clear_perfopt_all(void) ++{ ++ struct amd_iommu *iommu; ++ int err, ret = 0; ++ ++ for_each_iommu(iommu) { ++ err = amd_iommu_perfopt_clear(iommu); ++ if (err) ++ ret = err; ++ } ++ ++ return ret; ++} ++ ++static int restore_perfopt_all(void) ++{ ++ struct amd_iommu *iommu; ++ int err, ret = 0; ++ ++ for_each_iommu(iommu) { ++ err = amd_iommu_perfopt_restore(iommu); ++ if (err) ++ ret = err; ++ } ++ ++ return ret; ++} ++ + static void disable_iommus(void) + { + struct amd_iommu *iommu; + ++ /* ++ * PerfOpt is an optional performance bit, so a failure to clear it must ++ * not skip the mandatory disable below. This also runs from the void ++ * amd_iommu_disable() shutdown/kexec path, which cannot report an error. ++ */ ++ if (clear_perfopt_all()) ++ pr_err("Failed to clear PerfOpt while disabling IOMMUs\n"); ++ + for_each_iommu(iommu) + iommu_disable(iommu); + +@@ -3064,6 +3103,10 @@ static void amd_iommu_resume(void *data) + for_each_iommu(iommu) + early_enable_iommu(iommu); + ++ /* early_enable_iommu() cleared PERF_OPT_EN; re-assert it from the refcount. */ ++ if (restore_perfopt_all()) ++ pr_err("Failed to restore PerfOpt after IOMMU resume\n"); ++ + iommu_enable_event_buffer(); + amd_iommu_enable_interrupts(); + } +diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c +--- a/drivers/iommu/amd/iommu.c ++++ b/drivers/iommu/amd/iommu.c +@@ -2352,6 +2352,9 @@ static int attach_device(struct device *dev, + if (ret) + goto out; + ++ if (dev_data->perfopt) ++ goto skip_caps; ++ + /* Setup GCR3 table */ + if (pdom_is_sva_capable(domain)) { + ret = init_gcr3_table(dev_data, domain); +@@ -2376,6 +2379,7 @@ static int attach_device(struct device *dev, + pdev_enable_cap_ats(pdev); + } + ++skip_caps: + /* Update data structures */ + dev_data->domain = domain; + spin_lock_irqsave(&domain->lock, flags); +@@ -2444,6 +2448,192 @@ static void detach_device(struct device *dev) + mutex_unlock(&dev_data->mutex); + } + ++/* Program the per-IOMMU PerfOpt enable bit. Caller must hold iommu->lock. */ ++static int __perfopt_write(struct amd_iommu *iommu, bool enable) ++{ ++ u32 old, val, readback; ++ ++ if (!(readq(iommu->mmio_base + MMIO_EXT_FEATURES) & FEATURE_PERF_OPT)) ++ return enable ? -ENODEV : 0; ++ ++ old = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ if (old == U32_MAX) ++ return -EIO; ++ ++ val = enable ? old | PERF_OPT_EN : old & ~PERF_OPT_EN; ++ if (val != old) ++ writel(val, iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ readback = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ if (readback == U32_MAX || ++ (readback & PERF_OPT_EN) != (val & PERF_OPT_EN)) ++ return -EIO; ++ return 0; ++} ++ ++/* ++ * PERF_OPT_EN is a single bit shared by every device behind @iommu, so it is ++ * reference counted: armed on the first requesting device, cleared on the last. ++ */ ++static int perfopt_get(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ if (iommu->perfopt_refcount == 0) { ++ ret = __perfopt_write(iommu, true); ++ if (ret) ++ goto out; ++ } ++ iommu->perfopt_refcount++; ++out: ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++static int perfopt_put(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ if (iommu->perfopt_refcount > 0 && --iommu->perfopt_refcount == 0) ++ ret = __perfopt_write(iommu, false); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++/* ++ * Force PERF_OPT_EN off without touching the refcount (used on init, shutdown, ++ * and suspend). The count is preserved so amd_iommu_perfopt_restore() can ++ * re-arm on resume. ++ */ ++int amd_iommu_perfopt_clear(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ ret = __perfopt_write(iommu, false); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++/* ++ * Re-assert PERF_OPT_EN from the refcount after the hardware was reprogrammed on ++ * resume, so devices armed before suspend keep the optimization without each ++ * consumer driver re-arming. ++ */ ++int amd_iommu_perfopt_restore(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ ret = __perfopt_write(iommu, iommu->perfopt_refcount > 0); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++int amd_iommu_enable_perfopt(struct pci_dev *pdev) ++{ ++ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev); ++ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev); ++ struct protection_domain *domain; ++ int ret; ++ ++ if (!iommu || !dev_data) ++ return -ENODEV; ++ ++ if (!(iommu->features & FEATURE_PERF_OPT)) ++ return -ENODEV; ++ ++ domain = dev_data->domain; ++ if (!domain) ++ return -ENODEV; ++ ++ /* Already armed for this device (e.g. re-entry on resume). */ ++ if (dev_data->perfopt) ++ return 0; ++ ++ /* ++ * The bit is only architecturally valid while the device is untranslated: ++ * identity domain with ATS/PRI/PASID off. The identity domain is ++ * SVA-capable so attach_device() enabled ATS/PRI/PASID and built a GCR3 ++ * table. Re-home the device onto the same identity domain with ++ * perfopt set, so the attach_device() skip_caps path leaves ++ * ATS/PRI/PASID off and no GCR3 table. This follows the detach/attach ++ * pattern used by amd_iommu_attach_device(). ++ * ++ * Locking: this and amd_iommu_disable_perfopt() run only from the ++ * consumer driver's bind/unbind path. group->mutex is not exposed to ++ * drivers, but a device bound to its native driver cannot have its domain ++ * changed concurrently by the core (VFIO ownership is mutually exclusive; ++ * sysfs domain changes require an unused group), so the detach/attach pair ++ * is serialized without it. ++ */ ++ dev_data->perfopt = true; ++ detach_device(&pdev->dev); ++ ret = attach_device(&pdev->dev, domain); ++ if (ret) ++ goto err_restore; ++ ++ ret = perfopt_get(iommu); ++ if (ret) ++ goto err_rearm; ++ ++ dev_info_once(&pdev->dev, "PerfOpt armed on IOMMU%d\n", iommu->index); ++ return 0; ++ ++err_rearm: ++ detach_device(&pdev->dev); ++err_restore: ++ dev_data->perfopt = false; ++ if (attach_device(&pdev->dev, domain)) ++ pci_err(pdev, "failed to restore state after PerfOpt setup; device left detached\n"); ++ dev_err_once(&pdev->dev, "PerfOpt failed to arm on IOMMU%d (%d)\n", ++ iommu->index, ret); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(amd_iommu_enable_perfopt); ++ ++void amd_iommu_disable_perfopt(struct pci_dev *pdev) ++{ ++ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev); ++ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev); ++ struct protection_domain *domain; ++ ++ if (!iommu || !dev_data || !dev_data->perfopt || !dev_data->domain) ++ return; ++ ++ if (WARN_ON(perfopt_put(iommu))) ++ pci_err(pdev, "failed to clear PerfOpt\n"); ++ ++ /* ++ * Restore ATS/PRI/PASID (and thus SVA) by re-homing the device onto its ++ * identity domain with the flag cleared, so a later bind without PerfOpt ++ * sees a normally-capable device. See the locking note in ++ * amd_iommu_enable_perfopt(). ++ */ ++ domain = dev_data->domain; ++ dev_data->perfopt = false; ++ detach_device(&pdev->dev); ++ if (attach_device(&pdev->dev, domain)) ++ pci_err(pdev, "failed to restore caps after PerfOpt disable\n"); ++} ++EXPORT_SYMBOL_GPL(amd_iommu_disable_perfopt); + static struct iommu_device *amd_iommu_probe_device(struct device *dev) + { + struct iommu_device *iommu_dev; +@@ -2511,6 +2701,14 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) + static void amd_iommu_release_device(struct device *dev) + { + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); ++ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); ++ ++ if (dev_data->perfopt) { ++ if (WARN_ON(perfopt_put(iommu))) ++ dev_err(dev, "IOMMU%d: failed to clear PerfOpt on release\n", ++ iommu->index); ++ dev_data->perfopt = false; ++ } + + WARN_ON(dev_data->domain); + +@@ -2885,6 +3083,19 @@ static int blocked_domain_attach_device(struct iommu_domain *domain, + struct iommu_domain *old) + { + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); ++ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); ++ ++ /* ++ * blocked_domain is also the .release_domain, so this is the normal ++ * teardown path: drop the reference and clear the flag here too, and ++ * don't fail teardown if the WARN-guarded write doesn't stick. ++ */ ++ if (dev_data->perfopt) { ++ if (WARN_ON(perfopt_put(iommu))) ++ dev_err(dev, "IOMMU%d: failed to clear PerfOpt for blocked domain\n", ++ iommu->index); ++ dev_data->perfopt = false; ++ } + + if (dev_data->domain) + detach_device(dev); +@@ -2953,6 +3164,9 @@ static int amd_iommu_attach_device(struct iommu_domain *dom, struct device *dev, + struct amd_iommu *iommu = get_amd_iommu_from_dev(dev); + int ret; + ++ if (dev_data->perfopt && !pdom_is_in_pt_mode(domain)) ++ return -EBUSY; ++ + /* + * Skip attach device to domain if new domain is same as + * devices current domain +diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h +--- a/include/linux/amd-iommu.h ++++ b/include/linux/amd-iommu.h +@@ -11,11 +11,11 @@ + #include + + struct amd_iommu; ++struct pci_dev; + + #ifdef CONFIG_AMD_IOMMU + + struct task_struct; +-struct pci_dev; + + extern void amd_iommu_detect(void); + +@@ -76,4 +76,15 @@ static inline int amd_iommu_snp_disable(void) { return 0; } + static inline bool amd_iommu_sev_tio_supported(void) { return false; } + #endif + ++#ifdef CONFIG_AMD_IOMMU ++int amd_iommu_enable_perfopt(struct pci_dev *pdev); ++void amd_iommu_disable_perfopt(struct pci_dev *pdev); ++#else ++static inline int amd_iommu_enable_perfopt(struct pci_dev *pdev) ++{ ++ return 0; ++} ++static inline void amd_iommu_disable_perfopt(struct pci_dev *pdev) { } ++#endif ++ + #endif /* _ASM_X86_AMD_IOMMU_H */ diff --git a/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch.sig b/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..cd3768f3964eb9d635f3cfba88dcd0830f3b7ded GIT binary patch literal 594 zcmV-Y0ZA6Xs*)jkLq74SGniWGHf|Jed81S4hnr8R&%W< zSTdLUVa^<(@VoEBGYOf(@Gbq+<1B%-Q(MK?(QHCyr%DD0jbh1Wy<0>6KL_kr44D#u z{VdiBhFnu2*H1?`em0M&y-6Wm;^Yf4$a4I=BuI#u!){|N(*X_cw2~}2A8^L%hu|O{ zupy+7CxepBZupUANNgI5aOcDe9lX z3B)tCMr*?zhrER<$CtV$IEP*8_ZV~h0 + #include + #include ++#include + #include + #include + #include +@@ -3842,6 +3843,28 @@ amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px) + apple_gmux_detect(NULL, NULL))); + } + ++static inline bool amdgpu_device_identity(struct amdgpu_device *adev) ++{ ++ struct pci_dev *pdev = adev->pdev; ++ struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev); ++ ++ if (!domain) ++ return false; ++ ++ return domain->type == IOMMU_DOMAIN_IDENTITY; ++} ++ ++static bool amdgpu_device_use_perfopt(struct amdgpu_device *adev) ++{ ++ if (amdgpu_iommu_perfopt == 0) ++ return false; ++ ++ if (!(adev->flags & AMD_IS_APU)) ++ return false; ++ ++ return amdgpu_device_identity(adev); ++} ++ + /** + * amdgpu_device_init - initialize the driver + * +@@ -4048,6 +4071,16 @@ int amdgpu_device_init(struct amdgpu_device *adev, + if (r) + return r; + ++ if (amdgpu_device_use_perfopt(adev)) { ++ int perfopt_ret = amd_iommu_enable_perfopt(pdev); ++ ++ /* Optional optimization; a failure to arm it must not abort probe. */ ++ if (perfopt_ret) ++ dev_warn(adev->dev, ++ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n", ++ perfopt_ret); ++ } ++ + /* + * No need to remove conflicting FBs for non-display class devices. + * This prevents the sysfb from being freed accidently. +@@ -4416,6 +4449,9 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) + + amdgpu_gart_dummy_page_fini(adev); + ++ if (amdgpu_device_use_perfopt(adev)) ++ amd_iommu_disable_perfopt(adev->pdev); ++ + if (pci_dev_is_disconnected(adev->pdev)) + amdgpu_device_unmap_mmio(adev); + +@@ -4779,6 +4815,20 @@ int amdgpu_device_resume(struct drm_device *dev, bool notify_clients) + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + ++ if (amdgpu_device_use_perfopt(adev)) { ++ int perfopt_ret = amd_iommu_enable_perfopt(adev->pdev); ++ ++ /* ++ * Must not return on failure: a bare return would leak the ++ * SR-IOV VF exclusive-mode acquisition taken above (released ++ * via the exit: path). ++ */ ++ if (perfopt_ret) ++ dev_warn(adev->dev, ++ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n", ++ perfopt_ret); ++ } ++ + if (adev->in_s0ix) + amdgpu_dpm_gfx_state_change(adev, sGpuChangeState_D0Entry); + +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -185,6 +185,7 @@ char *amdgpu_disable_cu; + char *amdgpu_virtual_display; + int amdgpu_enforce_isolation = -1; + int amdgpu_modeset = -1; ++int amdgpu_iommu_perfopt = -1; + + /* Specifies the default granularity for SVM, used in buffer + * migration and restoration of backing memory when handling +@@ -393,6 +394,17 @@ module_param_named(fw_load_type, amdgpu_fw_load_type, int, 0444); + MODULE_PARM_DESC(aspm, "ASPM support (1 = enable, 0 = disable, -1 = auto)"); + module_param_named(aspm, amdgpu_aspm, int, 0444); + ++/** ++ * DOC: iommu_perfopt (int) ++ * Control the AMD IOMMU PerfOpt DMA-latency optimization ++ * (0 = disable; -1 = enable on supported devices). ++ * This arms the IOMMU PerfOpt control (IOMMU spec, MMIO Offset 016Ch, EFR PerfOptSup / PerfOptEn) ++ * Arming it disables ATS, PRI, PASID and SVA for the GPU and removes IOMMU DMA containment for it, ++ * trading isolation for lower DMA latency. ++ */ ++MODULE_PARM_DESC(iommu_perfopt, "Control IOMMU PerfOpt DMA-latency optimization (-1 = enable on supported devices, 0 = disable)"); ++module_param_named(iommu_perfopt, amdgpu_iommu_perfopt, int, 0444); ++ + /** + * DOC: runpm (int) + * Override for runtime power management control for dGPUs. The amdgpu driver can dynamically power down diff --git a/pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig b/pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..59f21e54b3e0c170feff3cf18c0bdc4a8cf7bb18 GIT binary patch literal 594 zcmV-Y0+_5g}wVpl;2s;oCcL(MhcqKU?DId2U+Vryqu=qh;ZOawV%({_`Eg zxP@d!7xc^Fc>pvKw-`k7&eKwHNU-yrBdwv*YS^2$7dz@^+?T8*0l1o;WY4jLx%=o` z+4~us;{d^@rP-S)4a8TL{G`RqJkTn|cRkOkQTSSh`K67RIBDBw`uU%x#W6kCdx=st zLKtEEqC1?)B+P#~o9)xYy2*!ruYl{RYaDZJo~I?z9PY5avNjL+3eS$v&M!lLq}pLt za)lrX$o{!6u085&9p(8E3 z{b^=TQ(&%;>1iVg?FHnJ(X>5KC!eT$E{@OaNNbZteW{hnz_~*b1zRAz9_rYe_RJI^ zpt>s%&Hsb}r>2Sf{67xy9-SPK478d7uD*6*0;u}X3m+Wxr83v%{D!E_`z159@)GC8 gRmxbEysC`m9%n(6yORx%MB + + #define ACPI_POWER_METER_NAME "power_meter" +-#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" +-#define ACPI_POWER_METER_CLASS "pwr_meter_resource" + + #define NUM_SENSORS 17 + +@@ -877,7 +875,7 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) + break; + } + +- acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, ++ acpi_bus_generate_netlink_event("pwr_meter_resource", + dev_name(&resource->acpi_dev->dev), + event, 0); + } +@@ -899,8 +897,6 @@ static int acpi_power_meter_probe(struct platform_device *pdev) + resource->sensors_valid = 0; + resource->acpi_dev = device; + mutex_init(&resource->lock); +- strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); + + platform_set_drvdata(pdev, resource); + +diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c +--- a/drivers/hwmon/adt7470.c ++++ b/drivers/hwmon/adt7470.c +@@ -22,6 +22,9 @@ + #include + #include + #include ++#include ++#include ++#include + + /* Addresses to scan */ + static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; +@@ -100,6 +103,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; + #define ADT7470_REG_FAN_MAX(x) (ADT7470_REG_FAN_MAX_BASE_ADDR + ((x) * 2)) + + #define ADT7470_PWM_COUNT 4 ++#define ADT7470_PWM_MAX 255 + #define ADT7470_REG_PWM(x) (ADT7470_REG_PWM_BASE_ADDR + (x)) + #define ADT7470_REG_PWM_MAX(x) (ADT7470_REG_PWM_MAX_BASE_ADDR + (x)) + #define ADT7470_REG_PWM_MIN(x) (ADT7470_REG_PWM_MIN_BASE_ADDR + (x)) +@@ -163,6 +167,7 @@ struct adt7470_data { + char limits_valid; + unsigned long sensors_last_updated; /* In jiffies */ + unsigned long limits_last_updated; /* In jiffies */ ++ bool use_pwm_framework; + + int num_temp_sensors; /* -1 = probe */ + int temperatures_probed; +@@ -850,7 +855,7 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val + + switch (attr) { + case hwmon_pwm_input: +- val = clamp_val(val, 0, 255); ++ val = clamp_val(val, 0, ADT7470_PWM_MAX); + mutex_lock(&data->lock); + err = regmap_write(data->regmap, ADT7470_REG_PWM(channel), + val); +@@ -886,6 +891,133 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val + return err; + } + ++struct adt7470_pwm_wfhw { ++ u8 val; ++}; ++ ++static int adt7470_pwm_round_waveform_tohw(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const struct pwm_waveform *wf, ++ void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ u64 actual_period; ++ ++ if (wf->duty_length_ns == 0) { ++ wfhw->val = 0; ++ return 0; ++ } ++ ++ /* ++ * The PWM frequency (period) is a single chip-wide setting shared by ++ * all 4 channels, so it cannot be changed on a per-pwm_device basis ++ * through this API. The duty cycle is rounded against the currently ++ * configured hardware period rather than the period requested in ++ * @wf; round_waveform_fromhw() reports the actual resulting ++ * waveform back so the core/consumer can detect a mismatch. ++ */ ++ actual_period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); ++ ++ if (actual_period > wf->period_length_ns) ++ /* period too short */ ++ return 1; ++ ++ if (wf->duty_length_ns >= actual_period) { ++ wfhw->val = ADT7470_PWM_MAX; ++ } else { ++ wfhw->val = mul_u64_u64_div_u64(wf->duty_length_ns, ++ ADT7470_PWM_MAX, ++ actual_period); ++ } ++ ++ return 0; ++} ++ ++static int adt7470_pwm_round_waveform_fromhw(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const void *_wfhw, ++ struct pwm_waveform *wf) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ const struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ ++ wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); ++ wf->duty_offset_ns = 0; ++ wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->val * wf->period_length_ns, ++ ADT7470_PWM_MAX); ++ return 0; ++} ++ ++static int adt7470_pwm_read_waveform(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ struct device *dev = regmap_get_device(data->regmap); ++ struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ ++ data = adt7470_update_device(dev); ++ if (IS_ERR(data)) ++ return PTR_ERR(data); ++ ++ /* ++ * No lock needed: like the other hwmon_ops read callbacks in this ++ * driver (e.g. adt7470_pwm_read()), this only does a single byte ++ * read from the cache populated by adt7470_update_device(). ++ */ ++ wfhw->val = data->pwm[pwm->hwpwm]; ++ ++ return 0; ++} ++ ++static int adt7470_pwm_write_waveform(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ const struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ unsigned int pwm_auto_reg_mask; ++ int err; ++ ++ if (pwm->hwpwm % 2) ++ pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; ++ else ++ pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; ++ ++ guard(mutex)(&data->lock); ++ ++ if (data->pwm[pwm->hwpwm] == wfhw->val && ++ data->pwm_automatic[pwm->hwpwm] == 0) ++ return 0; ++ ++ /* Put the PWM channel in manual mode before updating it. */ ++ err = regmap_update_bits(data->regmap, ++ ADT7470_REG_PWM_CFG(pwm->hwpwm), ++ pwm_auto_reg_mask, 0); ++ if (err < 0) ++ return err; ++ ++ data->pwm_automatic[pwm->hwpwm] = 0; ++ ++ err = regmap_write(data->regmap, ++ ADT7470_REG_PWM(pwm->hwpwm), wfhw->val); ++ if (err < 0) ++ return err; ++ ++ data->pwm[pwm->hwpwm] = wfhw->val; ++ ++ return 0; ++} ++ ++static const struct pwm_ops adt7470_pwm_ops = { ++ .sizeof_wfhw = sizeof(struct adt7470_pwm_wfhw), ++ .round_waveform_tohw = adt7470_pwm_round_waveform_tohw, ++ .round_waveform_fromhw = adt7470_pwm_round_waveform_fromhw, ++ .read_waveform = adt7470_pwm_read_waveform, ++ .write_waveform = adt7470_pwm_write_waveform, ++}; ++ + static ssize_t pwm_max_show(struct device *dev, + struct device_attribute *devattr, char *buf) + { +@@ -910,7 +1042,7 @@ static ssize_t pwm_max_store(struct device *dev, + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + +- temp = clamp_val(temp, 0, 255); ++ temp = clamp_val(temp, 0, ADT7470_PWM_MAX); + + mutex_lock(&data->lock); + data->pwm_max[attr->index] = temp; +@@ -945,7 +1077,7 @@ static ssize_t pwm_min_store(struct device *dev, + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + +- temp = clamp_val(temp, 0, 255); ++ temp = clamp_val(temp, 0, ADT7470_PWM_MAX); + + mutex_lock(&data->lock); + data->pwm_min[attr->index] = temp; +@@ -1106,6 +1238,10 @@ static struct attribute *adt7470_attrs[] = { + &dev_attr_alarm_mask.attr, + &dev_attr_num_temp_sensors.attr, + &dev_attr_auto_update_interval.attr, ++ NULL ++}; ++ ++static struct attribute *adt7470_pwm_attrs[] = { + &sensor_dev_attr_force_pwm_max.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, +@@ -1130,7 +1266,32 @@ static struct attribute *adt7470_attrs[] = { + NULL + }; + +-ATTRIBUTE_GROUPS(adt7470); ++static const struct attribute_group adt7470_group = { ++ .attrs = adt7470_attrs, ++}; ++ ++static umode_t adt7470_pwm_is_visible(struct kobject *kobj, ++ struct attribute *attr, int index) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct adt7470_data *data = dev_get_drvdata(dev); ++ ++ if (data->use_pwm_framework) ++ return 0; ++ ++ return attr->mode; ++} ++ ++static const struct attribute_group adt7470_pwm_group = { ++ .attrs = adt7470_pwm_attrs, ++ .is_visible = adt7470_pwm_is_visible, ++}; ++ ++static const struct attribute_group *adt7470_groups[] = { ++ &adt7470_group, ++ &adt7470_pwm_group, ++ NULL, ++}; + + static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +@@ -1165,6 +1326,7 @@ static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 a + static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) + { ++ const struct adt7470_data *data = _data; + umode_t mode = 0; + + switch (type) { +@@ -1197,6 +1359,14 @@ static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types typ + } + break; + case hwmon_pwm: ++ /* Hide all pwm attributes if this device is exposed to the PWM ++ * framework ++ */ ++ if (data->use_pwm_framework) { ++ mode = 0; ++ break; ++ } ++ + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: +@@ -1226,6 +1396,8 @@ static const struct hwmon_ops adt7470_hwmon_ops = { + }; + + static const struct hwmon_channel_info * const adt7470_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, +@@ -1327,6 +1499,29 @@ static int adt7470_probe(struct i2c_client *client) + + data->pwm_freq = (u32)freq_val; + ++ data->use_pwm_framework = false; ++ ++ if (device_property_present(dev, "#pwm-cells")) { ++ if (IS_REACHABLE(CONFIG_PWM)) { ++ struct pwm_chip *chip; ++ ++ chip = devm_pwmchip_alloc(dev, ADT7470_PWM_COUNT, 0); ++ if (IS_ERR(chip)) ++ return PTR_ERR(chip); ++ ++ chip->ops = &adt7470_pwm_ops; ++ pwmchip_set_drvdata(chip, data); ++ ++ err = devm_pwmchip_add(dev, chip); ++ if (err) ++ return dev_err_probe(dev, err, "failed to register PWM chip\n"); ++ ++ data->use_pwm_framework = true; ++ } else { ++ dev_warn(dev, "#pwm-cells present but CONFIG_PWM disabled. HWMON PWM attributes not hidden.\n"); ++ } ++ } ++ + /* Register sysfs hooks */ + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &adt7470_chip_info, +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + + /* data port used by Apple SMC */ + #define APPLESMC_DATA_PORT 0x300 +@@ -133,6 +134,7 @@ static struct applesmc_registers { + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ ++ char fan_positions[10][17]; /* cached fan position labels */ + } smcreg = { + .mutex = __MUTEX_INITIALIZER(smcreg.mutex), + }; +@@ -143,8 +145,8 @@ static s16 rest_x; + static s16 rest_y; + static u8 backlight_state[2]; + +-static struct device *hwmon_dev; + static struct input_dev *applesmc_idev; ++static struct device *hwmon_dev; + + /* + * Last index written to key_at_index sysfs file, and value to use for all other +@@ -372,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + __be32 be; + int ret = 0; + +- if (cache->valid) ++ /* Pairs with smp_store_release() to ensure cache contents are visible */ ++ if (smp_load_acquire(&cache->valid)) + return cache; + + mutex_lock(&smcreg.mutex); +@@ -391,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + cache->len = info[0]; + memcpy(cache->type, &info[1], 4); + cache->flags = info[5]; +- cache->valid = true; ++ /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ ++ smp_store_release(&cache->valid, true); + + out: + mutex_unlock(&smcreg.mutex); +@@ -566,7 +570,7 @@ static int applesmc_init_smcreg_try(void) + { + struct applesmc_registers *s = &smcreg; + bool left_light_sensor = false, right_light_sensor = false; +- unsigned int count; ++ unsigned int count, i; + u8 tmp[1]; + int ret; + +@@ -597,6 +601,16 @@ static int applesmc_init_smcreg_try(void) + if (s->fan_count > 10) + s->fan_count = 10; + ++ for (i = 0; i < s->fan_count; i++) { ++ char newkey[5]; ++ ++ scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); ++ ret = applesmc_read_key(newkey, s->fan_positions[i], 16); ++ s->fan_positions[i][16] = 0; ++ if (ret) ++ scnprintf(s->fan_positions[i], 17, " Fan %d", i); ++ } ++ + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); + if (ret) + return ret; +@@ -808,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev, + return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); + } + +-/* Displays sensor key as label */ +-static ssize_t applesmc_show_sensor_label(struct device *dev, +- struct device_attribute *devattr, char *sysfsbuf) +-{ +- const char *key = smcreg.index[to_index(devattr)]; +- +- return sysfs_emit(sysfsbuf, "%s\n", key); +-} +- +-/* Displays degree Celsius * 1000 */ +-static ssize_t applesmc_show_temperature(struct device *dev, +- struct device_attribute *devattr, char *sysfsbuf) +-{ +- const char *key = smcreg.index[to_index(devattr)]; +- int ret; +- s16 value; +- int temp; +- +- ret = applesmc_read_s16(key, &value); +- if (ret) +- return ret; +- +- temp = 250 * (value >> 6); +- +- return sysfs_emit(sysfsbuf, "%d\n", temp); +-} +- + static ssize_t applesmc_show_fan_speed(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) + { +@@ -854,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, + return sysfs_emit(sysfsbuf, "%u\n", speed); + } + +-static ssize_t applesmc_store_fan_speed(struct device *dev, +- struct device_attribute *attr, +- const char *sysfsbuf, size_t count) +-{ +- int ret; +- unsigned long speed; +- char newkey[5]; +- u8 buffer[2]; +- +- if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) +- return -EINVAL; /* Bigger than a 14-bit value */ +- +- scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], +- to_index(attr)); +- +- buffer[0] = (speed >> 6) & 0xff; +- buffer[1] = (speed << 2) & 0xff; +- ret = applesmc_write_key(newkey, buffer, 2); +- +- if (ret) +- return ret; +- else +- return count; +-} +- +-static ssize_t applesmc_show_fan_manual(struct device *dev, +- struct device_attribute *attr, char *sysfsbuf) +-{ +- int ret; +- u16 manual = 0; +- u8 buffer[2]; +- +- ret = applesmc_read_key(FANS_MANUAL, buffer, 2); +- if (ret) +- return ret; +- +- manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; +- return sysfs_emit(sysfsbuf, "%d\n", manual); +-} +- +-static ssize_t applesmc_store_fan_manual(struct device *dev, +- struct device_attribute *attr, +- const char *sysfsbuf, size_t count) +-{ +- int ret; +- u8 buffer[2]; +- unsigned long input; +- u16 val; +- +- if (kstrtoul(sysfsbuf, 10, &input) < 0) +- return -EINVAL; +- +- ret = applesmc_read_key(FANS_MANUAL, buffer, 2); +- if (ret) +- goto out; +- +- val = (buffer[0] << 8 | buffer[1]); +- +- if (input) +- val = val | (0x01 << to_index(attr)); +- else +- val = val & ~(0x01 << to_index(attr)); +- +- buffer[0] = (val >> 8) & 0xFF; +- buffer[1] = val & 0xFF; +- +- ret = applesmc_write_key(FANS_MANUAL, buffer, 2); +- +-out: +- if (ret) +- return ret; +- else +- return count; +-} +- +-static ssize_t applesmc_show_fan_position(struct device *dev, +- struct device_attribute *attr, char *sysfsbuf) +-{ +- int ret; +- char newkey[5]; +- u8 buffer[17]; +- +- scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); +- +- ret = applesmc_read_key(newkey, buffer, 16); +- buffer[16] = 0; +- +- if (ret) +- return ret; +- +- return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); +-} +- + static ssize_t applesmc_calibrate_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) + { +@@ -1094,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = { + { } + }; + +-static struct applesmc_node_group fan_group[] = { +- { "fan%d_label", applesmc_show_fan_position }, +- { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, +- { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, +- { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, +- { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, +- { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, +- { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, +- { } +-}; + +-static struct applesmc_node_group temp_group[] = { +- { "temp%d_label", applesmc_show_sensor_label }, +- { "temp%d_input", applesmc_show_temperature }, +- { } +-}; + + /* Module stuff */ + +@@ -1312,8 +1191,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { + }; + MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); + ++static struct applesmc_dev_attr *fan_safe_attrs; ++static struct attribute **fan_safe_attr_list; ++static struct attribute_group fan_safe_group; ++static const struct attribute_group *applesmc_extra_groups[2]; ++ ++static u32 *applesmc_temp_config; ++static u32 *applesmc_fan_config; ++static u32 *applesmc_pwm_config; ++static struct hwmon_channel_info *applesmc_info_temp; ++static struct hwmon_channel_info *applesmc_info_fan; ++static struct hwmon_channel_info *applesmc_info_pwm; ++static const struct hwmon_channel_info **applesmc_info_arr; ++static struct hwmon_chip_info *applesmc_chip; ++ ++static void applesmc_free_hwmon(void) ++{ ++ kfree(applesmc_temp_config); ++ kfree(applesmc_fan_config); ++ kfree(applesmc_pwm_config); ++ kfree(applesmc_info_temp); ++ kfree(applesmc_info_fan); ++ kfree(applesmc_info_pwm); ++ kfree(applesmc_info_arr); ++ kfree(applesmc_chip); ++ kfree(fan_safe_attrs); ++ kfree(fan_safe_attr_list); ++} ++ ++static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_input || attr == hwmon_temp_label) ++ return 0444; ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: ++ case hwmon_fan_label: ++ case hwmon_fan_max: ++ return 0444; ++ case hwmon_fan_min: ++ case hwmon_fan_target: ++ return 0644; ++ default: ++ break; ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) ++ return 0644; ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ int ret; ++ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_input) { ++ const char *key = smcreg.index[channel]; ++ s16 value; ++ ++ ret = applesmc_read_s16(key, &value); ++ if (ret) ++ return ret; ++ *val = 250 * (value >> 6); ++ return 0; ++ } ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dAc", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_min: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dMn", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_max: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dMx", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_target: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dTg", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ default: ++ break; ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) { ++ u8 buffer[2]; ++ ++ ret = applesmc_read_key(FANS_MANUAL, buffer, 2); ++ if (ret) ++ return ret; ++ *val = (((buffer[0] << 8 | buffer[1]) >> channel) & 0x01) ? 1 : 2; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ int ret; ++ ++ switch (type) { ++ case hwmon_fan: ++ if (attr == hwmon_fan_min || attr == hwmon_fan_target) { ++ char key[5]; ++ u8 buffer[2]; ++ const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; ++ ++ if (val < 0 || val >= 0x4000) ++ return -EINVAL; ++ scnprintf(key, sizeof(key), fmt, channel); ++ buffer[0] = (val >> 6) & 0xff; ++ buffer[1] = (val << 2) & 0xff; ++ return applesmc_write_key(key, buffer, 2); ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) { ++ u8 buffer[2]; ++ u16 manual_val; ++ const struct applesmc_entry *entry; ++ ++ if (val != 1 && val != 2) ++ return -EINVAL; ++ ++ entry = applesmc_get_entry_by_key(FANS_MANUAL); ++ if (IS_ERR(entry)) ++ return PTR_ERR(entry); ++ ++ mutex_lock(&smcreg.mutex); ++ ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); ++ if (ret) ++ goto out_unlock; ++ manual_val = (buffer[0] << 8 | buffer[1]); ++ if (val == 1) ++ manual_val |= (0x01 << channel); ++ else ++ manual_val &= ~(0x01 << channel); ++ buffer[0] = (manual_val >> 8) & 0xff; ++ buffer[1] = manual_val & 0xff; ++ ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); ++out_unlock: ++ mutex_unlock(&smcreg.mutex); ++ return ret; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_label) { ++ *str = smcreg.index[channel]; ++ return 0; ++ } ++ break; ++ case hwmon_fan: ++ if (attr == hwmon_fan_label) { ++ *str = smcreg.fan_positions[channel] + 4; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops applesmc_hwmon_ops = { ++ .is_visible = applesmc_hwmon_is_visible, ++ .read = applesmc_hwmon_read, ++ .write = applesmc_hwmon_write, ++ .read_string = applesmc_hwmon_read_string, ++}; ++ + static int __init applesmc_init(void) + { ++ int i; + int ret; + + if (!dmi_check_system(applesmc_whitelist)) { +@@ -1348,17 +1457,97 @@ static int __init applesmc_init(void) + if (ret) + goto out_smcreg; + +- ret = applesmc_create_nodes(fan_group, smcreg.fan_count); +- if (ret) ++ /* allocate hwmon channel configs */ ++ applesmc_temp_config = kcalloc(smcreg.index_count + 1, ++ sizeof(*applesmc_temp_config), GFP_KERNEL); ++ applesmc_fan_config = kcalloc(smcreg.fan_count + 1, ++ sizeof(*applesmc_fan_config), GFP_KERNEL); ++ applesmc_pwm_config = kcalloc(smcreg.fan_count + 1, ++ sizeof(*applesmc_pwm_config), GFP_KERNEL); ++ if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) { ++ ret = -ENOMEM; + goto out_info; ++ } + +- ret = applesmc_create_nodes(temp_group, smcreg.index_count); +- if (ret) +- goto out_fans; ++ for (i = 0; i < smcreg.index_count; i++) ++ applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; ++ applesmc_temp_config[smcreg.index_count] = 0; ++ ++ for (i = 0; i < smcreg.fan_count; i++) { ++ applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | ++ HWMON_F_MAX | HWMON_F_TARGET; ++ applesmc_pwm_config[i] = HWMON_PWM_ENABLE; ++ } ++ applesmc_fan_config[smcreg.fan_count] = 0; ++ applesmc_pwm_config[smcreg.fan_count] = 0; ++ ++ applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); ++ applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); ++ applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); ++ if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_info_temp->type = hwmon_temp; ++ applesmc_info_temp->config = applesmc_temp_config; ++ ++ applesmc_info_fan->type = hwmon_fan; ++ applesmc_info_fan->config = applesmc_fan_config; ++ ++ applesmc_info_pwm->type = hwmon_pwm; ++ applesmc_info_pwm->config = applesmc_pwm_config; ++ ++ applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); ++ if (!applesmc_info_arr) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_info_arr[0] = applesmc_info_temp; ++ applesmc_info_arr[1] = applesmc_info_fan; ++ applesmc_info_arr[2] = applesmc_info_pwm; ++ applesmc_info_arr[3] = NULL; ++ ++ applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); ++ if (!applesmc_chip) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_chip->ops = &applesmc_hwmon_ops; ++ applesmc_chip->info = applesmc_info_arr; ++ ++ /* Create non-standard fanX_safe attributes group */ ++ fan_safe_attrs = kcalloc(smcreg.fan_count, ++ sizeof(*fan_safe_attrs), GFP_KERNEL); ++ fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, ++ sizeof(*fan_safe_attr_list), GFP_KERNEL); ++ if (!fan_safe_attrs || !fan_safe_attr_list) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ for (i = 0; i < smcreg.fan_count; i++) { ++ struct applesmc_dev_attr *node = &fan_safe_attrs[i]; ++ ++ scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); ++ node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ ++ node->sda.dev_attr.show = applesmc_show_fan_speed; ++ node->sda.dev_attr.store = NULL; ++ sysfs_attr_init(&node->sda.dev_attr.attr); ++ node->sda.dev_attr.attr.name = node->name; ++ node->sda.dev_attr.attr.mode = 0444; ++ fan_safe_attr_list[i] = &node->sda.dev_attr.attr; ++ } ++ fan_safe_attr_list[smcreg.fan_count] = NULL; ++ fan_safe_group.attrs = fan_safe_attr_list; ++ applesmc_extra_groups[0] = &fan_safe_group; ++ applesmc_extra_groups[1] = NULL; + + ret = applesmc_create_accelerometer(); + if (ret) +- goto out_temperature; ++ goto out_info; + + ret = applesmc_create_light_sensor(); + if (ret) +@@ -1368,7 +1557,8 @@ static int __init applesmc_init(void) + if (ret) + goto out_light_sysfs; + +- hwmon_dev = hwmon_device_register(&pdev->dev); ++ hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, ++ applesmc_chip, applesmc_extra_groups); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + goto out_light_ledclass; +@@ -1382,11 +1572,8 @@ static int __init applesmc_init(void) + applesmc_release_light_sensor(); + out_accelerometer: + applesmc_release_accelerometer(); +-out_temperature: +- applesmc_destroy_nodes(temp_group); +-out_fans: +- applesmc_destroy_nodes(fan_group); + out_info: ++ applesmc_free_hwmon(); + applesmc_destroy_nodes(info_group); + out_smcreg: + applesmc_destroy_smcreg(); +@@ -1407,13 +1594,12 @@ static void __exit applesmc_exit(void) + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); + applesmc_release_accelerometer(); +- applesmc_destroy_nodes(temp_group); +- applesmc_destroy_nodes(fan_group); + applesmc_destroy_nodes(info_group); + applesmc_destroy_smcreg(); + platform_device_unregister(pdev); + platform_driver_unregister(&applesmc_driver); + release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); ++ applesmc_free_hwmon(); + } + + module_init(applesmc_init); +diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c +--- a/drivers/hwmon/asus-ec-sensors.c ++++ b/drivers/hwmon/asus-ec-sensors.c +@@ -185,6 +185,20 @@ enum ec_sensors { + #define SENSOR_TEMP_SENSOR_EXTRA_2 BIT(ec_sensor_temp_sensor_extra_2) + #define SENSOR_TEMP_SENSOR_EXTRA_3 BIT(ec_sensor_temp_sensor_extra_3) + ++/* ++ * The values for temperature sensor readings without physical sensors connected. ++ * The value varies across generations and is seemingly defined by the EC chip ++ * used in the given board. ++ */ ++static const s32 temperature_blank_values[] = {-62, -60, -40}; ++ ++static const s32 environment_temp_sensors = ++ SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_ALT1 | ++ SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT | ++ SENSOR_TEMP_WATER_BLOCK_IN | SENSOR_TEMP_WATER_BLOCK_OUT | ++ SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_SENSOR_EXTRA_1 | ++ SENSOR_TEMP_SENSOR_EXTRA_2 | SENSOR_TEMP_SENSOR_EXTRA_3; ++ + enum board_family { + family_unknown, + family_amd_400_series, +@@ -386,6 +400,8 @@ static const struct ec_sensor_info sensors_family_intel_600[] = { + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), ++ [ec_sensor_fan_cpu_opt] = ++ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [ec_sensor_fan_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe), + [ec_sensor_temp_water_in] = +@@ -487,6 +503,14 @@ static const struct ec_board_info board_info_crosshair_x670e_hero = { + .family = family_amd_600_series, + }; + ++static const struct ec_board_info board_info_crosshair_x870e_hero = { ++ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | ++ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | ++ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, ++ .family = family_amd_800_series, ++}; ++ + static const struct ec_board_info board_info_maximus_vi_hero = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | +@@ -527,6 +551,14 @@ static const struct ec_board_info board_info_maximus_z790_extreme = { + .family = family_intel_700_series, + }; + ++static const struct ec_board_info board_info_maximus_z790_hero = { ++ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | ++ SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW | ++ SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, ++ .family = family_intel_700_series, ++}; ++ + static const struct ec_board_info board_info_prime_x470_pro = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | +@@ -590,6 +622,13 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = { + .family = family_amd_800_series, + }; + ++static const struct ec_board_info board_info_pro_art_z690_creator_wifi = { ++ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | ++ SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, ++ .family = family_intel_600_series, ++}; ++ + static const struct ec_board_info board_info_pro_ws_trx50_sage_wifi = { + /* Board also has a nct6798 */ + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_VRME | +@@ -743,7 +782,7 @@ static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = { + + static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | +- SENSOR_TEMP_MB | SENSOR_TEMP_VRM | ++ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_800_series, +@@ -859,6 +898,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_pro_art_x670E_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI", + &board_info_pro_art_x870E_creator_wifi), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt Z690-CREATOR WIFI", ++ &board_info_pro_art_z690_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI", + &board_info_pro_ws_trx50_sage_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI A", +@@ -883,6 +924,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_crosshair_x670e_gene), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", + &board_info_crosshair_x670e_hero), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X870E HERO", ++ &board_info_crosshair_x870e_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO", + &board_info_maximus_xi_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)", +@@ -893,6 +936,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_maximus_z690_formula), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 EXTREME", + &board_info_maximus_z790_extreme), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 HERO", ++ &board_info_maximus_z790_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", + &board_info_strix_b550_e_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", +@@ -927,8 +972,12 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_strix_x870_i_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI", + &board_info_strix_x870e_e_gaming_wifi), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI7 R2", ++ &board_info_strix_x870e_e_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7", + &board_info_strix_x870e_h_gaming_wifi7), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-E GAMING", ++ &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", + &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", +@@ -955,6 +1004,7 @@ static const struct dmi_system_id dmi_table[] = { + }; + + struct ec_sensor { ++ /* this is ec_sensors enum value */ + unsigned int info_index; + s32 cached_value; + }; +@@ -1047,6 +1097,12 @@ get_sensor_info(const struct ec_sensors_data *state, int index) + return state->sensors_info + state->sensors[index].info_index; + } + ++static enum ec_sensors ++get_ec_sensor_type(const struct ec_sensors_data *state, int index) ++{ ++ return state->sensors[index].info_index; ++} ++ + static int find_ec_sensor_index(const struct ec_sensors_data *ec, + enum hwmon_sensor_types type, int channel) + { +@@ -1290,6 +1346,17 @@ static int get_cached_value_or_update(const struct device *dev, + return 0; + } + ++static bool is_blank_temperature_value(s32 value) ++{ ++ size_t i; ++ ++ for (i = 0; i < ARRAY_SIZE(temperature_blank_values); ++i) { ++ if (value == temperature_blank_values[i]) ++ return true; ++ } ++ return false; ++} ++ + /* + * Now follow the functions that implement the hwmon interface + */ +@@ -1297,6 +1364,8 @@ static int get_cached_value_or_update(const struct device *dev, + static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { ++ const struct ec_sensor_info *sensor_info; ++ enum ec_sensors ec_sensor; + int ret; + s32 value = 0; + +@@ -1308,12 +1377,19 @@ static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + } + + ret = get_cached_value_or_update(dev, sidx, state, &value); +- if (!ret) { +- *val = scale_sensor_value(value, +- get_sensor_info(state, sidx)->type); +- } ++ if (ret) ++ return ret; + +- return ret; ++ sensor_info = get_sensor_info(state, sidx); ++ if (sensor_info->type == hwmon_temp) { ++ ec_sensor = get_ec_sensor_type(state, sidx); ++ if ((environment_temp_sensors & BIT(ec_sensor)) && ++ is_blank_temperature_value(value)) ++ return -ENODATA; ++ } ++ *val = scale_sensor_value(value, sensor_info->type); ++ ++ return 0; + } + + static int asus_ec_hwmon_read_string(struct device *dev, +diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c +--- a/drivers/hwmon/asus_rog_ryujin.c ++++ b/drivers/hwmon/asus_rog_ryujin.c +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0+ + /* +- * hwmon driver for Asus ROG Ryujin II 360 AIO cooler. ++ * hwmon driver for Asus ROG Ryujin AIO coolers. + * + * Copyright 2024 Aleksa Savic + */ +@@ -17,6 +17,9 @@ + + #define USB_VENDOR_ID_ASUS_ROG 0x0b05 + #define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ ++#define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb ++#define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade ++#define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada + + struct rog_ryujin_device_info { + u8 temp_offset; +@@ -34,6 +37,14 @@ static const struct rog_ryujin_device_info rog_ryujin_ii_360_info = { + .has_controller = true, + }; + ++static const struct rog_ryujin_device_info rog_ryujin_iii_info = { ++ .temp_offset = 5, ++ .pump_speed_offset = 7, ++ .fan_speed_offset = 10, ++ .duty_channel = 1, ++ .has_controller = false, ++}; ++ + #define STATUS_VALIDITY 1500 /* ms */ + #define MAX_REPORT_LENGTH 65 + +@@ -173,7 +184,6 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd + static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) + { +- unsigned long flags; + int ret; + + /* +@@ -181,9 +191,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ +- spin_lock_irqsave(&priv->status_report_request_lock, flags); ++ spin_lock_bh(&priv->status_report_request_lock); + reinit_completion(status_completion); +- spin_unlock_irqrestore(&priv->status_report_request_lock, flags); ++ spin_unlock_bh(&priv->status_report_request_lock); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); +@@ -411,18 +421,15 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + int size) + { + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); +- unsigned long flags; + + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) + return 0; + +- spin_lock_irqsave(&priv->status_report_request_lock, flags); +- + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) +- goto unlock; ++ return 0; + + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + +@@ -436,7 +443,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) +- goto unlock; ++ return 0; + + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); +@@ -448,7 +455,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) +- goto unlock; ++ return 0; + + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { +@@ -467,7 +474,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; +- goto unlock; ++ return 0; + } + read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); +@@ -477,7 +484,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) +- goto unlock; ++ return 0; + + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { +@@ -496,7 +503,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_controller_duty; +- goto unlock; ++ return 0; + } + read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; +@@ -505,8 +512,6 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_duty_received); + } + +-unlock: +- spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return 0; + } + +@@ -596,6 +601,12 @@ static void rog_ryujin_remove(struct hid_device *hdev) + static const struct hid_device_id rog_ryujin_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO), + .driver_data = (kernel_ulong_t)&rog_ryujin_ii_360_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EXTREME), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EVA), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { } + }; + +@@ -625,4 +636,4 @@ module_exit(rog_ryujin_exit); + + MODULE_LICENSE("GPL"); + MODULE_AUTHOR("Aleksa Savic "); +-MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler"); ++MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin AIO coolers"); +diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c +--- a/drivers/hwmon/axi-fan-control.c ++++ b/drivers/hwmon/axi-fan-control.c +@@ -508,8 +508,7 @@ static int axi_fan_control_probe(struct platform_device *pdev) + IRQF_ONESHOT | IRQF_TRIGGER_HIGH, + NULL, ctl); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "failed to request an irq\n"); ++ return ret; + + return 0; + } +diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c +--- a/drivers/hwmon/chipcap2.c ++++ b/drivers/hwmon/chipcap2.c +@@ -22,6 +22,8 @@ + #include + #include + #include ++#include ++#include + + #define CC2_START_CM 0xA0 + #define CC2_START_NOM 0x80 +@@ -83,6 +85,7 @@ struct cc2_data { + struct i2c_client *client; + struct regulator *regulator; + const char *name; ++ const char *label; + int irq_ready; + int irq_low; + int irq_high; +@@ -444,6 +447,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + switch (attr) { + case hwmon_humidity_input: + return 0444; ++ case hwmon_humidity_label: ++ return cc2->label ? 0444 : 0; + case hwmon_humidity_min_alarm: + return cc2->rh_alarm.low_alarm_visible ? 0444 : 0; + case hwmon_humidity_max_alarm: +@@ -461,6 +466,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + switch (attr) { + case hwmon_temp_input: + return 0444; ++ case hwmon_temp_label: ++ return cc2->label ? 0444 : 0; + default: + return 0; + } +@@ -547,6 +554,16 @@ static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val) + return 0; + } + ++static int cc2_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ struct cc2_data *data = dev_get_drvdata(dev); ++ ++ *str = data->label; ++ ++ return 0; ++} ++ + static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) + { +@@ -665,8 +682,9 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) + } + + static const struct hwmon_channel_info *cc2_info[] = { +- HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), +- HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX | ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_LABEL | ++ HWMON_H_MIN | HWMON_H_MAX | + HWMON_H_MIN_HYST | HWMON_H_MAX_HYST | + HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM), + NULL +@@ -675,6 +693,7 @@ static const struct hwmon_channel_info *cc2_info[] = { + static const struct hwmon_ops cc2_hwmon_ops = { + .is_visible = cc2_is_visible, + .read = cc2_read, ++ .read_string = cc2_read_string, + .write = cc2_write, + }; + +@@ -705,6 +724,8 @@ static int cc2_probe(struct i2c_client *client) + return dev_err_probe(dev, PTR_ERR(data->regulator), + "Failed to get regulator\n"); + ++ device_property_read_string(dev, "label", &data->label); ++ + ret = cc2_request_ready_irq(data, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request ready irq\n"); +diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c +--- a/drivers/hwmon/coretemp.c ++++ b/drivers/hwmon/coretemp.c +@@ -68,7 +68,7 @@ enum coretemp_attr_index { + * This value is passed as "id" field to rdmsr/wrmsr functions. + * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, + * from where the temperature values should be read. +- * @attr_size: Total number of pre-core attrs displayed in the sysfs. ++ * @attr_size: Number of sysfs attributes in this temperature data group. + */ + struct temp_data { + int temp; +diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c +--- a/drivers/hwmon/corsair-psu.c ++++ b/drivers/hwmon/corsair-psu.c +@@ -884,7 +884,7 @@ static const struct hid_device_id corsairpsu_idtable[] = { + { HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */ + { HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */ + { HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsair HX1000i Series 2023 */ +- { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy and Series 2023 */ ++ { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy, Series 2023 and 2025 */ + { HID_USB_DEVICE(0x1b1c, 0x1c23) }, /* Corsair HX1200i Series 2023 */ + { HID_USB_DEVICE(0x1b1c, 0x1c27) }, /* Corsair HX1200i Series 2025 */ + { }, +diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c +--- a/drivers/hwmon/cros_ec_hwmon.c ++++ b/drivers/hwmon/cros_ec_hwmon.c +@@ -148,9 +148,17 @@ static bool cros_ec_hwmon_is_error_temp(u8 temp) + temp == EC_TEMP_SENSOR_NOT_CALIBRATED; + } + ++/* This differs slightly from the variant in units.h to avoid rounding inconsistencies. */ ++#define CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS (-273000) ++ ++static long cros_ec_hwmon_kelvin_to_millicelsius(long t) ++{ ++ return t * MILLIDEGREE_PER_DEGREE + CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS; ++} ++ + static long cros_ec_hwmon_temp_to_millicelsius(u8 temp) + { +- return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); ++ return cros_ec_hwmon_kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); + } + + static bool cros_ec_hwmon_attr_is_temp_threshold(u32 attr) +@@ -228,8 +236,13 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + ret = cros_ec_hwmon_read_temp_threshold(priv->cros_ec, channel, + cros_ec_hwmon_attr_to_thres(attr), + &threshold); +- if (ret == 0) +- *val = kelvin_to_millicelsius(threshold); ++ if (ret == 0) { ++ /* Limit to sensible, non-overflowing values. */ ++ if (threshold > 255 + 273) ++ *val = 255000; ++ else ++ *val = cros_ec_hwmon_kelvin_to_millicelsius(threshold); ++ } + } + } + +diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c +--- a/drivers/hwmon/da9055-hwmon.c ++++ b/drivers/hwmon/da9055-hwmon.c +@@ -256,11 +256,8 @@ static int da9055_hwmon_probe(struct platform_device *pdev) + NULL, da9055_auxadc_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "adc-irq", hwmon); +- if (ret != 0) { +- dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n", +- ret); ++ if (ret != 0) + return ret; +- } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", + hwmon, +diff --git a/drivers/hwmon/eic7700-pvt.c b/drivers/hwmon/eic7700-pvt.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/eic7700-pvt.c +@@ -0,0 +1,509 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ESWIN EIC7700 Voltage, Temperature sensor driver ++ * ++ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. ++ * ++ * Authors: ++ * Yulin Lu ++ * Huan He ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "eic7700-pvt.h" ++ ++static const struct pvt_sensor_info pvt_info[] = { ++ PVT_SENSOR_INFO(0, "Temperature", hwmon_temp, TEMP), ++ PVT_SENSOR_INFO(0, "Voltage", hwmon_in, VOLT), ++}; ++ ++static const char * const pvt_clk_names[PVT_CLK_NUM] = {"enable", "apb"}; ++ ++/* ++ * The original translation formulae of the temperature (in degrees of Celsius) ++ * to PVT data and vice-versa are following: ++ * N = 6.0818e-8*(T^4) +1.2873e-5*(T^3) + 7.2244e-3*(T^2) + 3.6484*(T^1) + ++ * 1.6198e2, ++ * T = -1.8439e-11*(N^4) + 8.0705e-8*(N^3) + -1.8501e-4*(N^2) + ++ * 3.2843e-1*(N^1) - 4.8690e1, ++ * where T = [-40, 125]C and N = [27, 771]. ++ * They must be accordingly altered to be suitable for the integer arithmetics. ++ * The technique is called 'factor redistribution', which just makes sure the ++ * multiplications and divisions are made so to have a result of the operations ++ * within the integer numbers limit. In addition we need to translate the ++ * formulae to accept millidegrees of Celsius. Here what they look like after ++ * the alterations: ++ * N = (60818e-20*(T^4) + 12873e-14*(T^3) + 72244e-9*(T^2) + 36484e-3*T + ++ * 16198e2) / 1e4, ++ * T = -18439e-12*(N^4) + 80705e-9*(N^3) - 185010e-6*(N^2) + 328430e-3*N - ++ * 48690, ++ * where T = [-40000, 125000] mC and N = [27, 771]. ++ */ ++static const struct polynomial poly_N_to_temp = { ++ .total_divider = 1, ++ .terms = { ++ {4, -18439, 1000, 1}, ++ {3, 80705, 1000, 1}, ++ {2, -185010, 1000, 1}, ++ {1, 328430, 1000, 1}, ++ {0, -48690, 1, 1} ++ } ++}; ++ ++/* ++ * Similar alterations are performed for the voltage conversion equations. ++ * The original formulae are: ++ * N = 1.3905e3*V - 5.7685e2, ++ * V = (N + 5.7685e2) / 1.3905e3, ++ * where V = [0.72, 0.88] V and N = [424, 646]. ++ * After the optimization they looks as follows: ++ * N = (13905e-3*V - 5768.5) / 10, ++ * V = (N * 10^5 / 13905 + 57685 * 10^3 / 13905) / 10. ++ * where V = [720, 880] mV and N = [424, 646]. ++ */ ++static const struct polynomial poly_N_to_volt = { ++ .total_divider = 10, ++ .terms = { ++ {1, 100000, 13905, 1}, ++ {0, 57685000, 1, 13905} ++ } ++}; ++ ++static inline u32 eic7700_pvt_update(void __iomem *reg, u32 mask, u32 data) ++{ ++ u32 old; ++ ++ old = readl_relaxed(reg); ++ writel((old & ~mask) | (data & mask), reg); ++ ++ return old & mask; ++} ++ ++static inline void eic7700_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) ++{ ++ u32 old; ++ ++ mode = FIELD_PREP(PVT_MODE_MASK, mode); ++ ++ old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); ++} ++ ++static inline void eic7700_pvt_set_trim(struct pvt_hwmon *pvt, u32 val) ++{ ++ u32 old; ++ ++ old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ writel(val, pvt->regs + PVT_TRIM); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); ++} ++ ++static irqreturn_t eic7700_pvt_hard_isr(int irq, void *data) ++{ ++ struct pvt_hwmon *pvt = data; ++ u32 stat, val; ++ int active; ++ ++ if (IS_ENABLED(CONFIG_PM)) { ++ active = pm_runtime_get_if_active(pvt->dev); ++ if (active <= 0) ++ return IRQ_NONE; ++ } ++ ++ stat = readl(pvt->regs + PVT_INT); ++ if (!(stat & PVT_INT_STAT)) { ++ if (IS_ENABLED(CONFIG_PM)) ++ pm_runtime_put(pvt->dev); ++ return IRQ_NONE; ++ } ++ ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ /* ++ * Read the data, update the cache and notify a waiter of this event. ++ */ ++ val = readl(pvt->regs + PVT_DATA); ++ WRITE_ONCE(pvt->data_cache, FIELD_GET(PVT_DATA_OUT, val)); ++ complete(&pvt->conversion); ++ ++ if (IS_ENABLED(CONFIG_PM)) ++ pm_runtime_put(pvt->dev); ++ ++ return IRQ_HANDLED; ++} ++ ++static int eic7700_pvt_read_data(struct pvt_hwmon *pvt, ++ enum pvt_sensor_type type, long *val) ++{ ++ unsigned long timeout; ++ u32 data; ++ int ret; ++ ++ /* ++ * Wait for PVT conversion to complete and update the data cache. The ++ * data read procedure is following: set the requested PVT sensor mode, ++ * enable conversion, wait until conversion is finished, then disable ++ * conversion and IRQ, and read the cached data. ++ */ ++ reinit_completion(&pvt->conversion); ++ ++ eic7700_pvt_set_mode(pvt, pvt_info[type].mode); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN); ++ ++ /* ++ * Wait with timeout since in case if the sensor is suddenly powered ++ * down the request won't be completed and the caller will hang up on ++ * this procedure until the power is back up again. Multiply the ++ * timeout by the factor of two to prevent a false timeout. ++ */ ++ timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); ++ ret = wait_for_completion_timeout(&pvt->conversion, timeout); ++ ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ ++ if (!ret) ++ synchronize_irq(pvt->irq); ++ ++ data = READ_ONCE(pvt->data_cache); ++ ++ if (!ret) ++ return -ETIMEDOUT; ++ ++ if (type == PVT_TEMP) ++ *val = polynomial_calc(&poly_N_to_temp, data); ++ else ++ *val = polynomial_calc(&poly_N_to_volt, data); ++ ++ return 0; ++} ++ ++static const struct hwmon_channel_info *pvt_channel_info[] = { ++ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), ++ NULL ++}; ++ ++static umode_t eic7700_pvt_hwmon_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int ch) ++{ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ return 0444; ++ } ++ break; ++ case hwmon_in: ++ switch (attr) { ++ case hwmon_in_input: ++ case hwmon_in_label: ++ return 0444; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++static int eic7700_pvt_hwmon_read(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int ch, long *val) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = pm_runtime_get_sync(pvt->dev); ++ if (ret < 0) { ++ dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret); ++ pm_runtime_put_noidle(pvt->dev); ++ return ret; ++ } ++ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ ret = eic7700_pvt_read_data(pvt, ch, val); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ break; ++ case hwmon_in: ++ if (attr == hwmon_in_input) ++ ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val); ++ else ++ ret = -EOPNOTSUPP; ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ ++ pm_runtime_mark_last_busy(pvt->dev); ++ pm_runtime_put_autosuspend(pvt->dev); ++ return ret; ++} ++ ++static int eic7700_pvt_hwmon_read_string(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int ch, const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_label) { ++ *str = pvt_info[ch].label; ++ return 0; ++ } ++ break; ++ case hwmon_in: ++ if (attr == hwmon_in_label) { ++ *str = pvt_info[PVT_VOLT + ch].label; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops pvt_hwmon_ops = { ++ .is_visible = eic7700_pvt_hwmon_is_visible, ++ .read = eic7700_pvt_hwmon_read, ++ .read_string = eic7700_pvt_hwmon_read_string ++}; ++ ++static const struct hwmon_chip_info pvt_hwmon_info = { ++ .ops = &pvt_hwmon_ops, ++ .info = pvt_channel_info ++}; ++ ++static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct pvt_hwmon *pvt; ++ ++ pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); ++ if (!pvt) ++ return ERR_PTR(-ENOMEM); ++ ++ pvt->dev = dev; ++ init_completion(&pvt->conversion); ++ ++ return pvt; ++} ++ ++static int eic7700_pvt_init_iface(struct pvt_hwmon *pvt) ++{ ++ /* ++ * Make sure controller are disabled so not to accidentally have ISR ++ * executed before the driver data is fully initialized. Clear the IRQ ++ * status as well. ++ */ ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ readl(pvt->regs + PVT_INT); ++ readl(pvt->regs + PVT_DATA); ++ ++ /* Setup default sensor mode and temperature trim. */ ++ eic7700_pvt_set_mode(pvt, pvt_info[PVT_TEMP].mode); ++ ++ /* ++ * Max conversion latency (~333 ”s) derived from PVT spec: ++ * maximum sampling rate = 3000 samples/sec. ++ */ ++ pvt->timeout = ns_to_ktime(PVT_TOUT_MIN); ++ ++ eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); ++ ++ return 0; ++} ++ ++static int eic7700_pvt_request_irq(struct pvt_hwmon *pvt) ++{ ++ struct platform_device *pdev = to_platform_device(pvt->dev); ++ int ret; ++ ++ pvt->irq = platform_get_irq(pdev, 0); ++ if (pvt->irq < 0) ++ return pvt->irq; ++ ++ ret = devm_request_threaded_irq(pvt->dev, pvt->irq, ++ eic7700_pvt_hard_isr, NULL, ++ IRQF_TRIGGER_HIGH, "pvt", pvt); ++ if (ret) { ++ dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int eic7700_pvt_create_hwmon(struct pvt_hwmon *pvt) ++{ ++ pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", ++ pvt, &pvt_hwmon_info, ++ NULL); ++ if (IS_ERR(pvt->hwmon)) { ++ dev_err(pvt->dev, "Couldn't create hwmon device\n"); ++ return PTR_ERR(pvt->hwmon); ++ } ++ ++ return 0; ++} ++ ++static void eic7700_pvt_disable_pm_runtime(void *data) ++{ ++ struct pvt_hwmon *pvt = data; ++ ++ pm_runtime_dont_use_autosuspend(pvt->dev); ++ pm_runtime_disable(pvt->dev); ++ ++ if (!pm_runtime_status_suspended(pvt->dev)) { ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ pm_runtime_set_suspended(pvt->dev); ++ } ++} ++ ++static int eic7700_pvt_probe(struct platform_device *pdev) ++{ ++ struct reset_control *rst; ++ struct pvt_hwmon *pvt; ++ int i, ret; ++ ++ pvt = eic7700_pvt_create_data(pdev); ++ if (IS_ERR(pvt)) ++ return PTR_ERR(pvt); ++ ++ platform_set_drvdata(pdev, pvt); ++ ++ pvt->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(pvt->regs)) ++ return PTR_ERR(pvt->regs); ++ ++ for (i = 0; i < PVT_CLK_NUM; i++) ++ pvt->clks[i].id = pvt_clk_names[i]; ++ ++ ret = devm_clk_bulk_get(&pdev->dev, PVT_CLK_NUM, pvt->clks); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Couldn't get clock descriptors\n"); ++ ++ rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); ++ if (IS_ERR(rst)) ++ return dev_err_probe(pvt->dev, PTR_ERR(rst), ++ "Couldn't get reset control\n"); ++ ++ ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); ++ if (ret) ++ return dev_err_probe(pvt->dev, ret, ++ "Failed to enable clocks\n"); ++ ++ ret = eic7700_pvt_init_iface(pvt); ++ if (ret) { ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ return ret; ++ } ++ ++ if (IS_ENABLED(CONFIG_PM)) ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ ++ pm_runtime_enable(&pdev->dev); ++ pm_runtime_set_autosuspend_delay(&pdev->dev, 3000); ++ pm_runtime_use_autosuspend(&pdev->dev); ++ pm_runtime_get_noresume(&pdev->dev); ++ ++ ret = devm_add_action_or_reset(pvt->dev, eic7700_pvt_disable_pm_runtime, ++ pvt); ++ if (ret) { ++ pm_runtime_put_noidle(&pdev->dev); ++ return dev_err_probe(&pdev->dev, ret, ++ "Can't register PM cleanup\n"); ++ } ++ ++ ret = eic7700_pvt_request_irq(pvt); ++ if (ret) ++ goto err_put_pm_runtime; ++ ++ ret = eic7700_pvt_create_hwmon(pvt); ++ if (ret) ++ goto err_put_pm_runtime; ++ ++ pm_runtime_put_autosuspend(&pdev->dev); ++ ++ return 0; ++ ++err_put_pm_runtime: ++ pm_runtime_put_noidle(&pdev->dev); ++ return ret; ++} ++ ++static int __maybe_unused eic7700_pvt_runtime_resume(struct device *dev) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); ++ if (ret) { ++ dev_err(dev, "Failed to enable clocks: %d\n", ret); ++ return ret; ++ } ++ ++ eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); ++ ++ return 0; ++} ++ ++static int __maybe_unused eic7700_pvt_runtime_suspend(struct device *dev) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops eic7700_pvt_pm_ops = { ++ SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) ++ RUNTIME_PM_OPS(eic7700_pvt_runtime_suspend, eic7700_pvt_runtime_resume, ++ NULL) ++}; ++ ++static const struct of_device_id pvt_of_match[] = { ++ { .compatible = "eswin,eic7700-pvt"}, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, pvt_of_match); ++ ++static struct platform_driver pvt_driver = { ++ .probe = eic7700_pvt_probe, ++ .driver = { ++ .name = "eic7700-pvt", ++ .of_match_table = pvt_of_match, ++ .pm = pm_ptr(&eic7700_pvt_pm_ops), ++ }, ++}; ++module_platform_driver(pvt_driver); ++ ++MODULE_AUTHOR("Yulin Lu "); ++MODULE_AUTHOR("Huan He "); ++MODULE_DESCRIPTION("Eswin eic7700 PVT driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/eic7700-pvt.h b/drivers/hwmon/eic7700-pvt.h +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/eic7700-pvt.h +@@ -0,0 +1,99 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * ESWIN EIC7700 Voltage, Temperature sensor driver ++ * ++ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. ++ */ ++#ifndef __HWMON_EIC7700_PVT_H__ ++#define __HWMON_EIC7700_PVT_H__ ++ ++#include ++#include ++#include ++#include ++ ++/* ESWIN EIC7700 PVT registers and their bitfields */ ++#define PVT_TRIM 0x04 ++#define PVT_MODE 0x08 ++#define PVT_MODE_MASK GENMASK(2, 0) ++#define PVT_CTRL_MODE_TEMP 0x0 ++#define PVT_CTRL_MODE_VOLT 0x4 ++#define PVT_ENA 0x0c ++#define PVT_ENA_EN BIT(0) ++#define PVT_INT 0x10 ++#define PVT_INT_STAT BIT(0) ++#define PVT_INT_CLR BIT(1) ++#define PVT_DATA 0x14 ++#define PVT_DATA_OUT GENMASK(9, 0) ++ ++/* ++ * PVT sensors-related limits and default values ++ * @PVT_TEMP_CHS: Number of temperature hwmon channels. ++ * @PVT_VOLT_CHS: Number of voltage hwmon channels. ++ * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value ++ * when one is determined for ESWIN EIC7700 SoC). ++ * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. ++ */ ++#define PVT_TEMP_CHS 1 ++#define PVT_VOLT_CHS 1 ++#define PVT_TRIM_DEF 0 ++#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) ++ ++/* ++ * enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT ++ * sampling mode) ++ * @PVT_TEMP: PVT Temperature sensor. ++ * @PVT_VOLT: PVT Voltage sensor. ++ */ ++enum pvt_sensor_type { ++ PVT_TEMP = 0, ++ PVT_VOLT ++}; ++ ++#define PVT_CLK_NUM 2 ++ ++/* ++ * struct pvt_sensor_info - ESWIN EIC7700 PVT sensor informational structure ++ * @channel: Sensor channel ID. ++ * @label: hwmon sensor label. ++ * @mode: PVT mode corresponding to the channel. ++ * @type: Sensor type. ++ */ ++struct pvt_sensor_info { ++ int channel; ++ const char *label; ++ u32 mode; ++ enum hwmon_sensor_types type; ++}; ++ ++#define PVT_SENSOR_INFO(_ch, _label, _type, _mode) \ ++ { \ ++ .channel = _ch, \ ++ .label = _label, \ ++ .mode = PVT_CTRL_MODE_ ##_mode, \ ++ .type = _type, \ ++ } ++ ++/* ++ * struct pvt_hwmon - Eswin EIC7700 PVT private data ++ * @dev: device structure of the PVT platform device. ++ * @hwmon: hwmon device structure. ++ * @regs: pointer to the Eswin EIC7700 PVT registers region. ++ * @irq: PVT events IRQ number. ++ * @clks: PVT clock descriptors. ++ * @data_cache: data cache in raw format. ++ * @conversion: data conversion completion. ++ * @timeout: conversion timeout. ++ */ ++struct pvt_hwmon { ++ struct device *dev; ++ struct device *hwmon; ++ void __iomem *regs; ++ int irq; ++ struct clk_bulk_data clks[PVT_CLK_NUM]; ++ u32 data_cache; ++ struct completion conversion; ++ ktime_t timeout; ++}; ++ ++#endif /* __HWMON_EIC7700_PVT_H__ */ +diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c +--- a/drivers/hwmon/emc1403.c ++++ b/drivers/hwmon/emc1403.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + + #define THERMAL_PID_REG 0xfd +@@ -654,14 +655,20 @@ static int emc1403_probe(struct i2c_client *client) + { + struct thermal_data *data; + struct device *hwmon_dev; +- const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client); ++ int ret; ++ ++ ret = devm_regulator_get_enable(&client->dev, "vdd"); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to enable regulator\n"); + + data = devm_kzalloc(&client->dev, sizeof(struct thermal_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + +- data->chip = id->driver_data; ++ data->chip = (uintptr_t)i2c_get_match_data(client); ++ + data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); +@@ -677,10 +684,20 @@ static const unsigned short emc1403_address_list[] = { + 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END + }; + ++static const struct of_device_id emc1403_of_match[] = { ++ { .compatible = "smsc,emc1402", .data = (void *)emc1402 }, ++ { .compatible = "smsc,emc1403", .data = (void *)emc1403 }, ++ { .compatible = "smsc,emc1404", .data = (void *)emc1404 }, ++ { .compatible = "smsc,emc1428", .data = (void *)emc1428 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, emc1403_of_match); ++ + static struct i2c_driver sensor_emc1403 = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc1403", ++ .of_match_table = emc1403_of_match, + }, + .detect = emc1403_detect, + .probe = emc1403_probe, +diff --git a/drivers/hwmon/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c +--- a/drivers/hwmon/gxp-fan-ctrl.c ++++ b/drivers/hwmon/gxp-fan-ctrl.c +@@ -1,59 +1,16 @@ + // SPDX-License-Identifier: GPL-2.0-only +-/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ ++/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */ + +-#include + #include + #include + #include + #include + #include + +-#define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */ +-#define OFS_FAN_FAIL 2 /* Is 2 bytes after base */ +-#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */ +-#define POWER_BIT 24 +- + struct gxp_fan_ctrl_drvdata { + void __iomem *base; +- void __iomem *plreg; +- void __iomem *fn2; + }; + +-static bool fan_installed(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u8 val; +- +- val = readb(drvdata->plreg + OFS_FAN_INST); +- +- return !!(val & BIT(fan)); +-} +- +-static long fan_failed(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u8 val; +- +- val = readb(drvdata->plreg + OFS_FAN_FAIL); +- +- return !!(val & BIT(fan)); +-} +- +-static long fan_enabled(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u32 val; +- +- /* +- * Check the power status as if the platform is off the value +- * reported for the PWM will be incorrect. Report fan as +- * disabled. +- */ +- val = readl(drvdata->fn2 + OFS_SEVSTAT); +- +- return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan)); +-} +- + static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val) + { + struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +@@ -80,37 +37,11 @@ static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type, + } + } + +-static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val) +-{ +- switch (attr) { +- case hwmon_fan_enable: +- *val = fan_enabled(dev, channel); +- return 0; +- case hwmon_fan_fault: +- *val = fan_failed(dev, channel); +- return 0; +- default: +- return -EOPNOTSUPP; +- } +-} +- + static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val) + { + struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u32 reg; + +- /* +- * Check the power status of the platform. If the platform is off +- * the value reported for the PWM will be incorrect. In this case +- * report a PWM of zero. +- */ +- +- reg = readl(drvdata->fn2 + OFS_SEVSTAT); +- +- if (reg & BIT(POWER_BIT)) +- *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0; +- else +- *val = 0; ++ *val = readb(drvdata->base + channel); + + return 0; + } +@@ -119,8 +50,6 @@ static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { + switch (type) { +- case hwmon_fan: +- return gxp_fan_read(dev, attr, channel, val); + case hwmon_pwm: + return gxp_pwm_read(dev, attr, channel, val); + default: +@@ -135,16 +64,6 @@ static umode_t gxp_fan_ctrl_is_visible(const void *_data, + umode_t mode = 0; + + switch (type) { +- case hwmon_fan: +- switch (attr) { +- case hwmon_fan_enable: +- case hwmon_fan_fault: +- mode = 0444; +- break; +- default: +- break; +- } +- break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: +@@ -168,15 +87,6 @@ static const struct hwmon_ops gxp_fan_ctrl_ops = { + }; + + static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { +- HWMON_CHANNEL_INFO(fan, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, +@@ -211,18 +121,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(drvdata->base), + "failed to map base\n"); + +- drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, +- "pl"); +- if (IS_ERR(drvdata->plreg)) +- return dev_err_probe(dev, PTR_ERR(drvdata->plreg), +- "failed to map plreg\n"); +- +- drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, +- "fn2"); +- if (IS_ERR(drvdata->fn2)) +- return dev_err_probe(dev, PTR_ERR(drvdata->fn2), +- "failed to map fn2\n"); +- + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "hpe_gxp_fan_ctrl", + drvdata, +diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c +--- a/drivers/hwmon/hwmon.c ++++ b/drivers/hwmon/hwmon.c +@@ -57,8 +57,8 @@ struct hwmon_device_attribute { + }; + + #define to_hwmon_attr(d) \ +- container_of(d, struct hwmon_device_attribute, dev_attr) +-#define to_dev_attr(a) container_of(a, struct device_attribute, attr) ++ container_of_const(d, struct hwmon_device_attribute, dev_attr) ++#define to_dev_attr(a) container_of_const(a, struct device_attribute, attr) + + /* + * Thermal zone information +@@ -71,27 +71,27 @@ struct hwmon_thermal_data { + }; + + static ssize_t +-name_show(struct device *dev, struct device_attribute *attr, char *buf) ++name_show(struct device *dev, const struct device_attribute *attr, char *buf) + { + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->name); + } +-static DEVICE_ATTR_RO(name); ++static const DEVICE_ATTR_RO(name); + + static ssize_t +-label_show(struct device *dev, struct device_attribute *attr, char *buf) ++label_show(struct device *dev, const struct device_attribute *attr, char *buf) + { + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label); + } +-static DEVICE_ATTR_RO(label); ++static const DEVICE_ATTR_RO(label); + +-static struct attribute *hwmon_dev_attrs[] = { ++static const struct attribute *const hwmon_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_label.attr, + NULL + }; + + static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, +- struct attribute *attr, int n) ++ const struct attribute *attr, int n) + { + struct device *dev = kobj_to_dev(kobj); + struct hwmon_device *hdev = to_hwmon_device(dev); +@@ -106,8 +106,8 @@ static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, + } + + static const struct attribute_group hwmon_dev_attr_group = { +- .attrs = hwmon_dev_attrs, +- .is_visible = hwmon_dev_attr_is_visible, ++ .attrs_const = hwmon_dev_attrs, ++ .is_visible_const = hwmon_dev_attr_is_visible, + }; + + static const struct attribute_group *hwmon_dev_attr_groups[] = { +@@ -115,13 +115,13 @@ static const struct attribute_group *hwmon_dev_attr_groups[] = { + NULL + }; + +-static void hwmon_free_attrs(struct attribute **attrs) ++static void hwmon_free_attrs(const struct attribute *const *attrs) + { + int i; + + for (i = 0; attrs[i]; i++) { +- struct device_attribute *dattr = to_dev_attr(attrs[i]); +- struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); ++ const struct device_attribute *dattr = to_dev_attr(attrs[i]); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); + + kfree(hattr); + } +@@ -132,8 +132,8 @@ static void hwmon_dev_release(struct device *dev) + { + struct hwmon_device *hwdev = to_hwmon_device(dev); + +- if (hwdev->group.attrs) +- hwmon_free_attrs(hwdev->group.attrs); ++ if (hwdev->group.attrs_const) ++ hwmon_free_attrs(hwdev->group.attrs_const); + kfree(hwdev->groups); + kfree(hwdev->label); + kfree(hwdev); +@@ -341,7 +341,7 @@ static int hwmon_match_device(struct device *dev, const void *data) + return dev->class == &hwmon_class; + } + +-static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, ++static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, + char *buf) + { + struct i2c_client *client = to_i2c_client(dev); +@@ -349,7 +349,7 @@ static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, + return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC)); + } + +-static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, ++static ssize_t pec_store(struct device *dev, const struct device_attribute *devattr, + const char *buf, size_t count) + { + struct i2c_client *client = to_i2c_client(dev); +@@ -389,7 +389,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, + return err; + } + +-static DEVICE_ATTR_RW(pec); ++static const DEVICE_ATTR_RW(pec); + + static void hwmon_remove_pec(void *dev) + { +@@ -424,9 +424,9 @@ static int hwmon_pec_register(struct device *hdev) + /* sysfs attribute management */ + + static ssize_t hwmon_attr_show(struct device *dev, +- struct device_attribute *devattr, char *buf) ++ const struct device_attribute *devattr, char *buf) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + s64 val64; + long val; +@@ -449,10 +449,10 @@ static ssize_t hwmon_attr_show(struct device *dev, + } + + static ssize_t hwmon_attr_show_string(struct device *dev, +- struct device_attribute *devattr, ++ const struct device_attribute *devattr, + char *buf) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + enum hwmon_sensor_types type = hattr->type; + const char *s; +@@ -472,10 +472,10 @@ static ssize_t hwmon_attr_show_string(struct device *dev, + } + + static ssize_t hwmon_attr_store(struct device *dev, +- struct device_attribute *devattr, ++ const struct device_attribute *devattr, + const char *buf, size_t count) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + long val; + int ret; +@@ -509,12 +509,12 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) + (type == hwmon_fan && attr == hwmon_fan_label); + } + +-static struct attribute *hwmon_genattr(const void *drvdata, +- enum hwmon_sensor_types type, +- u32 attr, +- int index, +- const char *template, +- const struct hwmon_ops *ops) ++static const struct attribute *hwmon_genattr(const void *drvdata, ++ enum hwmon_sensor_types type, ++ u32 attr, ++ int index, ++ const char *template, ++ const struct hwmon_ops *ops) + { + struct hwmon_device_attribute *hattr; + struct device_attribute *dattr; +@@ -551,8 +551,8 @@ static struct attribute *hwmon_genattr(const void *drvdata, + hattr->ops = ops; + + dattr = &hattr->dev_attr; +- dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show; +- dattr->store = hwmon_attr_store; ++ dattr->show_const = is_string ? hwmon_attr_show_string : hwmon_attr_show; ++ dattr->store_const = hwmon_attr_store; + + a = &dattr->attr; + sysfs_attr_init(a); +@@ -830,7 +830,7 @@ static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) + } + + static int hwmon_genattrs(const void *drvdata, +- struct attribute **attrs, ++ const struct attribute **attrs, + const struct hwmon_ops *ops, + const struct hwmon_channel_info *info) + { +@@ -849,7 +849,7 @@ static int hwmon_genattrs(const void *drvdata, + u32 attr; + + while (attr_mask) { +- struct attribute *a; ++ const struct attribute *a; + + attr = __ffs(attr_mask); + attr_mask &= ~BIT(attr); +@@ -868,11 +868,11 @@ static int hwmon_genattrs(const void *drvdata, + return aindex; + } + +-static struct attribute ** ++static const struct attribute ** + __hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip) + { + int ret, i, aindex = 0, nattrs = 0; +- struct attribute **attrs; ++ const struct attribute **attrs; + + for (i = 0; chip->info[i]; i++) + nattrs += hwmon_num_channel_attrs(chip->info[i]); +@@ -927,7 +927,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, + hdev = &hwdev->dev; + + if (chip) { +- struct attribute **attrs; ++ const struct attribute **attrs; + int ngroups = 2; /* terminating NULL plus &hwdev->groups */ + + if (groups) +@@ -946,7 +946,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, + goto free_hwmon; + } + +- hwdev->group.attrs = attrs; ++ hwdev->group.attrs_const = attrs; + ngroups = 0; + hwdev->groups[ngroups++] = &hwdev->group; + +diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c +--- a/drivers/hwmon/ina2xx.c ++++ b/drivers/hwmon/ina2xx.c +@@ -124,6 +124,7 @@ static const struct regmap_config ina2xx_regmap_config = { + enum ina2xx_ids { + ina219, + ina226, ++ ina232, + ina234, + ina260, + sy24655 +@@ -210,6 +211,20 @@ static const struct ina2xx_config ina2xx_config[] = { + .current_shift = 4, + .has_update_interval = true, + }, ++ [ina232] = { ++ .config_default = INA226_CONFIG_DEFAULT, ++ .calibration_value = 2048, ++ .shunt_div = 400, ++ .shunt_voltage_shift = 0, ++ .bus_voltage_shift = 0, ++ .bus_voltage_lsb = 1600, ++ .power_lsb_factor = 32, ++ .has_alerts = true, ++ .has_ishunt = false, ++ .has_power_average = false, ++ .current_shift = 0, ++ .has_update_interval = true, ++ }, + [ina260] = { + .config_default = INA260_CONFIG_DEFAULT, + .shunt_div = 400, +@@ -1123,6 +1138,7 @@ static const struct i2c_device_id ina2xx_id[] = { + { .name = "ina226", .driver_data = ina226 }, + { .name = "ina230", .driver_data = ina226 }, + { .name = "ina231", .driver_data = ina226 }, ++ { .name = "ina232", .driver_data = ina232 }, + { .name = "ina234", .driver_data = ina234 }, + { .name = "ina260", .driver_data = ina260 }, + { .name = "sy24655", .driver_data = sy24655 }, +@@ -1155,6 +1171,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = { + .compatible = "ti,ina231", + .data = (void *)ina226 + }, ++ { ++ .compatible = "ti,ina232", ++ .data = (void *)ina232 ++ }, + { + .compatible = "ti,ina234", + .data = (void *)ina234 +diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c +--- a/drivers/hwmon/k10temp.c ++++ b/drivers/hwmon/k10temp.c +@@ -207,6 +207,10 @@ static const char *k10temp_temp_label[] = { + "Tccd10", + "Tccd11", + "Tccd12", ++ "Tccd13", ++ "Tccd14", ++ "Tccd15", ++ "Tccd16", + }; + + static int k10temp_read_labels(struct device *dev, +@@ -243,7 +247,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, + if (*val < 0 && !data->disp_negative) + *val = 0; + break; +- case 2 ... 13: /* Tccd{1-12} */ ++ case 2 ... 17: /* Tccd{1-16} */ + ret = read_ccd_temp_reg(data, channel - 2, ®val); + + if (ret) +@@ -384,6 +388,10 @@ static const struct hwmon_channel_info * const k10temp_info[] = { + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL + }; +@@ -515,6 +523,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) + } + } else if (boot_cpu_data.x86 == 0x1a) { + switch (boot_cpu_data.x86_model) { ++ case 0x00 ... 0x2f: /* Zen5 Turin */ ++ data->ccd_offset = 0x1F0; ++ k10temp_get_ccd_support(data, 16); ++ break; + case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */ + data->ccd_offset = 0x308; + k10temp_get_ccd_support(data, 8); +diff --git a/drivers/hwmon/kb9002.c b/drivers/hwmon/kb9002.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/kb9002.c +@@ -0,0 +1,460 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Kandou KB9002 PCIe 5.0 retimer hwmon driver. ++ * ++ * The retimer exposes a system management bus (SMBus 3.0 with PEC) ++ * target for firmware-managed status registers. This driver assumes ++ * the chip is strapped to SMBus mode and exports the aggregated ++ * maximum die temperature as hwmon temp1_input (millidegrees Celsius) ++ * plus the firmware version and boot status under debugfs. ++ * ++ * The raw-I2C path (kb9002_i2c_read/write, used only at probe to switch ++ * the host interface) carries the 32-bit register address and data ++ * big-endian. The SMBus path (kb9002_fw_read/kb9002_smbus_hw_read) ++ * carries the register address and returned data little-endian. ++ * ++ * Datasheet: Kandou KB9002 PCIe retimer (KA-015171-PD). ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define KB9002_DEV_NAME "kb9002" ++ ++/* ++ * SMBus read command codes. Each read is a two-phase PEC-protected ++ * transaction: prime writes the target address, data reads it back with ++ * the register contents. FW reads use a 16-bit address, HW reads 32-bit. ++ */ ++#define KB9002_CC_FW_READ_PRIME 0x82 ++#define KB9002_CC_FW_READ_DATA 0x81 ++#define KB9002_CC_HW_READ_PRIME 0x8a ++#define KB9002_CC_HW_READ_DATA 0x89 ++ ++/* Firmware register offsets (16-bit). */ ++#define KB9002_FW_REG_VID 0x0004 ++#define KB9002_FW_REG_FW_VERSION 0x0500 ++#define KB9002_FW_REG_TEMP_MAXIMUM 0x0550 ++ ++#define KB9002_VID_MASK GENMASK(31, 16) ++#define KB9002_VID_KANDOU 0x1e6f ++ ++/* Firmware boot status: 0xe8 in the top byte means init completed OK. */ ++#define KB9002_HW_REG_FW_BOOT_STATUS 0xe0090008 ++#define KB9002_FW_BOOT_STATUS_OK_MSB 0xe8 ++ ++/* ++ * Hardware registers reached over raw I2C (32-bit addressing). The ++ * host-interface bit selects SMBus (set) vs raw-I2C target; parts ++ * strapped to raw I2C need it set before SMBus access works. ++ */ ++#define KB9002_HW_REG_REVID 0x00480004 ++#define KB9002_HW_REG_HOST_IF 0x00480008 ++#define KB9002_HOST_IF_SMBUS BIT(1) ++ ++#define KB9002_REVID_MASK GENMASK(7, 0) ++#define KB9002_REVID_B0 0x10 ++#define KB9002_REVID_B1 0x11 ++ ++/* Retries to drain a stray leading 0xff from the raw-I2C FIFO. */ ++#define KB9002_REVID_READ_RETRIES 16 ++ ++/* Temperature: 32-bit Q16.16 absolute Kelvin. */ ++#define KB9002_TEMP_FRAC_BITS 16 ++#define KB9002_ABS_ZERO_MILLI_C (-273150) ++ ++/* Firmware takes up to ~2s to respond after a host-interface change. */ ++#define KB9002_FW_READY_POLL_US (25 * USEC_PER_MSEC) ++#define KB9002_FW_READY_TIMEOUT_US (2 * USEC_PER_SEC) ++ ++struct kb9002_data { ++ struct i2c_client *client; ++ struct device *hwmon_dev; ++}; ++ ++/* Raw-I2C read: write the 32-bit BE address, then read 4 BE data bytes. */ ++static int kb9002_i2c_read(struct i2c_client *client, u32 reg, u32 *val) ++{ ++ u8 addr[4]; ++ u8 rbuf[4]; ++ struct i2c_msg msgs[2] = { ++ { ++ .addr = client->addr, ++ .flags = 0, ++ .len = sizeof(addr), ++ .buf = addr, ++ }, ++ { ++ .addr = client->addr, ++ .flags = I2C_M_RD, ++ .len = sizeof(rbuf), ++ .buf = rbuf, ++ }, ++ }; ++ int ret; ++ ++ put_unaligned_be32(reg, addr); ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) ++ return ret; ++ if (ret != ARRAY_SIZE(msgs)) ++ return -EIO; ++ ++ *val = get_unaligned_be32(rbuf); ++ return 0; ++} ++ ++/* Raw-I2C write: 4 BE address bytes followed by 4 BE data bytes. */ ++static int kb9002_i2c_write(struct i2c_client *client, u32 reg, u32 val) ++{ ++ u8 buf[8]; ++ struct i2c_msg msg = { ++ .addr = client->addr, ++ .flags = 0, ++ .len = sizeof(buf), ++ .buf = buf, ++ }; ++ int ret; ++ ++ put_unaligned_be32(reg, &buf[0]); ++ put_unaligned_be32(val, &buf[4]); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) ++ return ret; ++ if (ret != 1) ++ return -EIO; ++ ++ return 0; ++} ++ ++/* ++ * Read the silicon revision ID. A fresh FIFO may start with a stray ++ * 0xff that shifts the result, so drain one byte between retries until ++ * the top byte is no longer 0xff. ++ */ ++static int kb9002_read_revid(struct i2c_client *client, u32 *revid) ++{ ++ u8 dummy; ++ int ret; ++ int i; ++ ++ for (i = 0; i < KB9002_REVID_READ_RETRIES; i++) { ++ ret = kb9002_i2c_read(client, KB9002_HW_REG_REVID, revid); ++ if (ret) ++ return ret; ++ if ((*revid >> 24) != 0xff) ++ return 0; ++ /* Drain one byte from the chip to re-align the I2C FIFO. */ ++ i2c_master_recv(client, &dummy, 1); ++ } ++ ++ return -EIO; ++} ++ ++/* ++ * Read a 32-bit firmware register over SMBus: block-write the 16-bit LE ++ * address, then block-read the echoed address plus 4 LE data bytes. ++ */ ++static int kb9002_fw_read(struct kb9002_data *data, u16 reg, u32 *val) ++{ ++ struct i2c_client *client = data->client; ++ u8 addr[2]; ++ u8 rbuf[I2C_SMBUS_BLOCK_MAX]; ++ int ret; ++ ++ put_unaligned_le16(reg, addr); ++ ++ ret = i2c_smbus_write_block_data(client, KB9002_CC_FW_READ_PRIME, ++ sizeof(addr), addr); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_block_data(client, KB9002_CC_FW_READ_DATA, rbuf); ++ if (ret < 0) ++ return ret; ++ if (ret < (int)(sizeof(addr) + sizeof(*val))) ++ return -EIO; ++ ++ *val = get_unaligned_le32(&rbuf[sizeof(addr)]); ++ return 0; ++} ++ ++/* Like kb9002_fw_read but for a hardware register (32-bit LE address). */ ++static int kb9002_smbus_hw_read(struct kb9002_data *data, u32 reg, u32 *val) ++{ ++ struct i2c_client *client = data->client; ++ u8 addr[4]; ++ u8 rbuf[I2C_SMBUS_BLOCK_MAX]; ++ int ret; ++ ++ put_unaligned_le32(reg, addr); ++ ++ ret = i2c_smbus_write_block_data(client, KB9002_CC_HW_READ_PRIME, ++ sizeof(addr), addr); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_block_data(client, KB9002_CC_HW_READ_DATA, rbuf); ++ if (ret < 0) ++ return ret; ++ if (ret < (int)(sizeof(addr) + sizeof(*val))) ++ return -EIO; ++ ++ *val = get_unaligned_le32(&rbuf[sizeof(addr)]); ++ return 0; ++} ++ ++/* ++ * Switch the host interface from raw-I2C to SMBus and wait for firmware ++ * to come back up. Called only when SMBus access failed in probe, i.e. ++ * the chip is strapped to raw-I2C mode. Confirms the revision, sets the ++ * SMBus-mode bit, then polls until firmware responds again. ++ */ ++static int kb9002_enable_smbus_target(struct kb9002_data *data) ++{ ++ struct i2c_client *client = data->client; ++ u32 revid; ++ u32 val; ++ int op_ret; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) ++ return dev_err_probe(&client->dev, -ENODEV, ++ "raw I2C required to switch to SMBus mode\n"); ++ ++ ret = kb9002_read_revid(client, &revid); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "revision ID read failed\n"); ++ ++ switch (FIELD_GET(KB9002_REVID_MASK, revid)) { ++ case KB9002_REVID_B0: ++ case KB9002_REVID_B1: ++ break; ++ default: ++ return dev_err_probe(&client->dev, -ENODEV, ++ "unsupported revision ID 0x%08x\n", revid); ++ } ++ ++ ret = kb9002_i2c_read(client, KB9002_HW_REG_HOST_IF, &val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "host interface read failed\n"); ++ ++ val |= KB9002_HOST_IF_SMBUS; ++ ++ ret = kb9002_i2c_write(client, KB9002_HW_REG_HOST_IF, val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "host interface write failed\n"); ++ ++ /* Wait until firmware re-initialisation completes. */ ++ ret = read_poll_timeout(kb9002_fw_read, op_ret, op_ret == 0, ++ KB9002_FW_READY_POLL_US, ++ KB9002_FW_READY_TIMEOUT_US, true, ++ data, KB9002_FW_REG_VID, &val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "firmware not responding over SMBus\n"); ++ ++ return 0; ++} ++ ++/* Convert Q16.16 absolute Kelvin to millidegrees Celsius. */ ++static long kb9002_temp_to_milli_c(u32 raw) ++{ ++ s64 milli_k = ((s64)raw * 1000) >> KB9002_TEMP_FRAC_BITS; ++ ++ return (long)milli_k + KB9002_ABS_ZERO_MILLI_C; ++} ++ ++static int kb9002_read_temp(struct kb9002_data *data, long *val) ++{ ++ u32 raw; ++ int ret; ++ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_TEMP_MAXIMUM, &raw); ++ if (ret) ++ return ret; ++ ++ *val = kb9002_temp_to_milli_c(raw); ++ return 0; ++} ++ ++static umode_t kb9002_is_visible(const void *drvdata, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ return 0444; ++} ++ ++static int kb9002_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct kb9002_data *data = dev_get_drvdata(dev); ++ ++ if (type == hwmon_temp && attr == hwmon_temp_input) ++ return kb9002_read_temp(data, val); ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops kb9002_hwmon_ops = { ++ .is_visible = kb9002_is_visible, ++ .read = kb9002_read, ++}; ++ ++static const struct hwmon_channel_info * const kb9002_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), ++ NULL, ++}; ++ ++static const struct hwmon_chip_info kb9002_chip_info = { ++ .ops = &kb9002_hwmon_ops, ++ .info = kb9002_hwmon_info, ++}; ++ ++static int kb9002_fw_version_show(struct seq_file *s, void *unused) ++{ ++ struct kb9002_data *data = s->private; ++ u32 ver; ++ int ret; ++ ++ guard(hwmon_lock)(data->hwmon_dev); ++ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_FW_VERSION, &ver); ++ if (ret) ++ return ret; ++ ++ seq_printf(s, "%u.%02u.%02u.%u\n", ++ (ver >> 24) & 0xff, (ver >> 16) & 0xff, ++ (ver >> 8) & 0xff, (ver >> 0) & 0xff); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(kb9002_fw_version); ++ ++static int kb9002_fw_load_status_show(struct seq_file *s, void *unused) ++{ ++ struct kb9002_data *data = s->private; ++ u32 status; ++ int ret; ++ ++ guard(hwmon_lock)(data->hwmon_dev); ++ ++ ret = kb9002_smbus_hw_read(data, KB9002_HW_REG_FW_BOOT_STATUS, &status); ++ if (ret) ++ return ret; ++ ++ seq_printf(s, "%s\n", ++ (status >> 24) == KB9002_FW_BOOT_STATUS_OK_MSB ? ++ "normal" : "abnormal"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(kb9002_fw_load_status); ++ ++static void kb9002_debugfs_init(struct kb9002_data *data) ++{ ++ struct dentry *dir = data->client->debugfs; ++ ++ debugfs_create_file("fw_ver", 0444, dir, data, ++ &kb9002_fw_version_fops); ++ debugfs_create_file("fw_load_status", 0444, dir, data, ++ &kb9002_fw_load_status_fops); ++} ++ ++static int kb9002_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct kb9002_data *data; ++ struct device *hwmon_dev; ++ u32 vid; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_BLOCK_DATA | ++ I2C_FUNC_SMBUS_PEC)) ++ return -ENODEV; ++ ++ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->client = client; ++ ++ /* All firmware register accesses are PEC-protected. */ ++ client->flags |= I2C_CLIENT_PEC; ++ ++ i2c_set_clientdata(client, data); ++ ++ /* ++ * Try SMBus first. If the chip is strapped to raw-I2C mode it ++ * will not respond to SMBus framing, so fall back to switching ++ * the host interface over raw I2C and retry. ++ */ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); ++ if (ret) { ++ dev_dbg(dev, "SMBus probe failed (%d), trying raw-I2C host-interface switch\n", ++ ret); ++ ret = kb9002_enable_smbus_target(data); ++ if (ret) ++ return ret; ++ ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "VID read failed after host-interface switch\n"); ++ } ++ if (FIELD_GET(KB9002_VID_MASK, vid) != KB9002_VID_KANDOU) ++ return dev_err_probe(dev, -ENODEV, ++ "unexpected VID 0x%08x\n", vid); ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(dev, KB9002_DEV_NAME, ++ data, ++ &kb9002_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return PTR_ERR(hwmon_dev); ++ ++ data->hwmon_dev = hwmon_dev; ++ kb9002_debugfs_init(data); ++ return 0; ++} ++ ++static const struct i2c_device_id kb9002_id[] = { ++ { .name = KB9002_DEV_NAME }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, kb9002_id); ++ ++static const struct of_device_id kb9002_of_match[] = { ++ { .compatible = "kandou,kb9002" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, kb9002_of_match); ++ ++static struct i2c_driver kb9002_driver = { ++ .driver = { ++ .name = KB9002_DEV_NAME, ++ .of_match_table = kb9002_of_match, ++ }, ++ .probe = kb9002_probe, ++ .id_table = kb9002_id, ++}; ++module_i2c_driver(kb9002_driver); ++ ++MODULE_AUTHOR("Andy Chung "); ++MODULE_DESCRIPTION("Kandou KB9002 PCIe retimer hwmon driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c +--- a/drivers/hwmon/lm90.c ++++ b/drivers/hwmon/lm90.c +@@ -101,6 +101,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -108,7 +109,7 @@ + #include + #include + #include +-#include ++#include + #include + #include + #include +@@ -295,7 +296,7 @@ static const struct i2c_device_id lm90_id[] = { + }; + MODULE_DEVICE_TABLE(i2c, lm90_id); + +-static const struct of_device_id __maybe_unused lm90_of_match[] = { ++static const struct of_device_id lm90_of_match[] = { + { + .compatible = "adi,adm1032", + .data = (void *)adm1032 +@@ -2602,7 +2603,6 @@ static void lm90_stop_work(void *_data) + + static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) + { +- struct device_node *np = client->dev.of_node; + int config, convrate; + + if (data->flags & LM90_HAVE_CONVRATE) { +@@ -2626,7 +2626,7 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) + + /* Check Temperature Range Select */ + if (data->flags & LM90_HAVE_EXTENDED_TEMP) { +- if (of_property_read_bool(np, "ti,extended-range-enable")) ++ if (device_property_read_bool(&client->dev, "ti,extended-range-enable")) + config |= 0x04; + if (!(config & 0x04)) + data->flags &= ~LM90_HAVE_EXTENDED_TEMP; +@@ -2692,36 +2692,41 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id) + return IRQ_NONE; + } + +-static int lm90_probe_channel_from_dt(struct i2c_client *client, +- struct device_node *child, +- struct lm90_data *data) ++static int lm90_probe_channel(struct i2c_client *client, ++ struct fwnode_handle *child, ++ struct lm90_data *data) + { + u32 id; + s32 val; + int err; + struct device *dev = &client->dev; + +- err = of_property_read_u32(child, "reg", &id); ++ err = fwnode_property_read_u32(child, "reg", &id); + if (err) { +- dev_err(dev, "missing reg property of %pOFn\n", child); ++ dev_err(dev, "missing reg property of %pfw\n", child); + return err; + } + + if (id >= MAX_CHANNELS) { +- dev_err(dev, "invalid reg property value %d in %pOFn\n", id, child); ++ dev_err(dev, "invalid reg property value %d in %pfw\n", id, child); + return -EINVAL; + } + +- err = of_property_read_string(child, "label", &data->channel_label[id]); ++ err = fwnode_property_read_string(child, "label", &data->channel_label[id]); + if (err == -ENODATA || err == -EILSEQ) { +- dev_err(dev, "invalid label property in %pOFn\n", child); ++ dev_err(dev, "invalid label property in %pfw\n", child); + return err; + } + + if (data->channel_label[id]) + data->channel_config[id] |= HWMON_T_LABEL; + +- err = of_property_read_s32(child, "temperature-offset-millicelsius", &val); ++ /* ++ * fwnode_property_read_u32() has no signed equivalent. ++ * temperature-offset-millicelsius is signed, so read and reinterpret it as s32 to ++ * preserve negative offsets values (same behavior as the old of_property_read_s32()). ++ */ ++ err = fwnode_property_read_u32(child, "temperature-offset-millicelsius", (u32 *)&val); + if (!err) { + if (id == 0) { + dev_err(dev, "temperature-offset-millicelsius can't be set for internal channel\n"); +@@ -2739,18 +2744,17 @@ static int lm90_probe_channel_from_dt(struct i2c_client *client, + return 0; + } + +-static int lm90_parse_dt_channel_info(struct i2c_client *client, +- struct lm90_data *data) ++static int lm90_parse_channel_info(struct i2c_client *client, ++ struct lm90_data *data) + { + int err; + struct device *dev = &client->dev; +- const struct device_node *np = dev->of_node; + +- for_each_child_of_node_scoped(np, child) { +- if (strcmp(child->name, "channel")) ++ device_for_each_child_node_scoped(dev, child) { ++ if (!fwnode_name_eq(child, "channel")) + continue; + +- err = lm90_probe_channel_from_dt(client, child, data); ++ err = lm90_probe_channel(client, child, data); + if (err) + return err; + } +@@ -2887,12 +2891,10 @@ static int lm90_probe(struct i2c_client *client) + /* Set maximum conversion rate */ + data->max_convrate = lm90_params[data->kind].max_convrate; + +- /* Parse device-tree channel information */ +- if (client->dev.of_node) { +- err = lm90_parse_dt_channel_info(client, data); +- if (err) +- return err; +- } ++ /* Parse channel information */ ++ err = lm90_parse_channel_info(client, data); ++ if (err) ++ return err; + + /* Initialize the LM90 chip */ + err = lm90_init_client(client, data); +@@ -2918,10 +2920,8 @@ static int lm90_probe(struct i2c_client *client) + err = devm_request_threaded_irq(dev, client->irq, + NULL, lm90_irq_thread, + IRQF_ONESHOT, "lm90", client); +- if (err < 0) { +- dev_err(dev, "cannot request IRQ %d\n", client->irq); ++ if (err < 0) + return err; +- } + } + + return 0; +@@ -2985,7 +2985,7 @@ static struct i2c_driver lm90_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm90", +- .of_match_table = of_match_ptr(lm90_of_match), ++ .of_match_table = lm90_of_match, + .pm = pm_sleep_ptr(&lm90_pm_ops), + }, + .probe = lm90_probe, +diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c +--- a/drivers/hwmon/nct6775-core.c ++++ b/drivers/hwmon/nct6775-core.c +@@ -33,6 +33,7 @@ + * (0xd451) + * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * (0xd429) ++ * nct5585d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * nct6796d-s 18 7 7 6+2 0xd801 0xc1 0x5ca3 + * nct6799d-r 18 7 7 6+2 0xd802 0xc1 0x5ca3 + * +diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c +--- a/drivers/hwmon/nct6775-platform.c ++++ b/drivers/hwmon/nct6775-platform.c +@@ -34,7 +34,7 @@ static const char * const nct6775_sio_names[] __initconst = { + [nct6795] = "NCT6795D", + [nct6796] = "NCT6796D", + [nct6797] = "NCT6797D", +- [nct6798] = "NCT6798D", ++ [nct6798] = "NCT6798D/NCT5585D", + [nct6799] = "NCT6796D-S/NCT6799D-R", + }; + +diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c +--- a/drivers/hwmon/npcm750-pwm-fan.c ++++ b/drivers/hwmon/npcm750-pwm-fan.c +@@ -996,10 +996,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) + sprintf(name, "NPCM7XX-FAN-MD%d", i); + ret = devm_request_irq(dev, data->fan_irq[i], npcm7xx_fan_isr, + 0, name, (void *)data); +- if (ret) { +- dev_err(dev, "register IRQ fan%d failed\n", i); ++ if (ret) + return ret; +- } + } + + for_each_child_of_node_scoped(np, child) { +diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c +--- a/drivers/hwmon/nzxt-kraken3.c ++++ b/drivers/hwmon/nzxt-kraken3.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0+ + /* +- * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers. ++ * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73, 2023/2023 Elite, and ++ * 2024 Elite all in one coolers. + * X53 and Z53 in code refer to all models in their respective series (shortened for brevity). + * 2023 models use the Z53 code paths. + * +@@ -25,6 +26,7 @@ + #define USB_PRODUCT_ID_Z53 0x3008 + #define USB_PRODUCT_ID_KRAKEN2023 0x300E + #define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C ++#define USB_PRODUCT_ID_KRAKEN2024_ELITE 0x3012 + + enum kinds { X53, Z53, KRAKEN2023 } __packed; + enum pwm_enable { off, manual, curve } __packed; +@@ -927,6 +929,10 @@ static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id + priv->kind = KRAKEN2023; + device_name = "kraken2023elite"; + break; ++ case USB_PRODUCT_ID_KRAKEN2024_ELITE: ++ priv->kind = KRAKEN2023; ++ device_name = "kraken2024elite"; ++ break; + default: + ret = -ENODEV; + goto fail_and_close; +@@ -994,6 +1000,7 @@ static const struct hid_device_id kraken3_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2024_ELITE) }, + { } + }; + +diff --git a/drivers/hwmon/peci/common.h b/drivers/hwmon/peci/common.h +--- a/drivers/hwmon/peci/common.h ++++ b/drivers/hwmon/peci/common.h +@@ -31,7 +31,7 @@ struct peci_sensor_data { + + /** + * peci_sensor_need_update() - check whether sensor update is needed or not +- * @sensor: pointer to sensor data struct ++ * @state: pointer to sensor state struct + * + * Return: true if update is needed, false if not. + */ +@@ -44,7 +44,7 @@ static inline bool peci_sensor_need_update(struct peci_sensor_state *state) + + /** + * peci_sensor_mark_updated() - mark the sensor is updated +- * @sensor: pointer to sensor data struct ++ * @state: pointer to sensor state struct + */ + static inline void peci_sensor_mark_updated(struct peci_sensor_state *state) + { +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -52,8 +52,8 @@ config SENSORS_ADM1275 + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, +- ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and +- Digital Power Monitors. ++ ADM1293, ADM1294, ROHM BD12780, ROHM BD12790, and SQ24905C ++ Hot-Swap Controller and Digital Power Monitors. + + This driver can also be built as a module. If so, the module will + be called adm1275. +@@ -394,10 +394,10 @@ config SENSORS_MAX20751 + be called max20751. + + config SENSORS_MAX20830 +- tristate "Analog Devices MAX20830" ++ tristate "Analog Devices MAX20830 and compatibles" + help + If you say yes here you get hardware monitoring support for Analog +- Devices MAX20830. ++ Devices MAX20830, MAX20830C, and MAX20840C. + + This driver can also be built as a module. If so, the module will + be called max20830. +@@ -434,8 +434,9 @@ config SENSORS_MAX34440 + tristate "Maxim MAX34440 and compatibles" + help + If you say yes here you get hardware monitoring support for Maxim +- MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461. +- Other compatible are ADPM12160, and ADPM12200. ++ MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and ++ MAX34461. Other compatible devices are ADPM12160, ADPM12200, and ++ ADPM12250. + + This driver can also be built as a module. If so, the module will + be called max34440. +@@ -607,6 +608,15 @@ config SENSORS_MPQ7932 + This driver can also be built as a module. If so, the module will + be called mpq7932. + ++config SENSORS_MPQ82D00 ++ tristate "MPS MPQ82D00" ++ help ++ If you say yes here you get hardware monitoring functionality support ++ for power management IC MPS MPQ82D00. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mpq82d00. ++ + config SENSORS_MPQ8785 + tristate "MPS MPQ8785" + help +@@ -616,6 +626,37 @@ config SENSORS_MPQ8785 + This driver can also be built as a module. If so, the module will + be called mpq8785. + ++config SENSORS_MPQ8646 ++ tristate "MPS MPQ8646" ++ depends on REGULATOR || !REGULATOR ++ depends on NVMEM || !NVMEM ++ help ++ If you say yes here you get hardware monitoring support for the ++ Monolithic Power Systems MPQ8646. ++ ++ This driver can also be built as a module. If so, the module ++ will be called mpq8646. ++ ++config SENSORS_MPQ8646_DEBUG_UNSAFE ++ bool "MPQ8646 unsafe write/provisioning debugfs (DANGEROUS)" ++ depends on SENSORS_MPQ8646 && DEBUG_FS ++ default n ++ help ++ Expose additional WRITE-able debugfs files in the client's ++ pmbus debugfs directory, ++ /sys/kernel/debug/i2c/i2c-/-/ ++ ++ These are provisioning and bring-up aids. On many designs the ++ MPQ8646 powers the SoC core rail, so a wrong write can brown out ++ or permanently mis-provision the board. ++ ++ WARNING: Wrong register writes can and likely will physically ++ damage or destroy the chip and/or the board. ++ ++ It shall never be set in a production, shipping, or default configuration. ++ ++ If unsure, say N. ++ + config SENSORS_PIM4328 + tristate "Flex PIM4328 and compatibles" + help +@@ -677,6 +718,25 @@ config SENSORS_STEF48H28 + This driver can also be built as a module. If so, the module will + be called stef48h28. + ++config SENSORS_SQ24860 ++ tristate "Silergy SQ24860" ++ help ++ If you say yes here you get hardware monitoring support for Silergy ++ SQ24860 eFuse. ++ ++ This driver can also be built as a module. If so, the module will ++ be called sq24860. ++ ++config SENSORS_SQ24860_REGULATOR ++ bool "Regulator support for SQ24860" ++ depends on SENSORS_SQ24860 && REGULATOR ++ default SENSORS_SQ24860 ++ help ++ If you say yes here you get regulator support for Silergy SQ24860. ++ The regulator is registered through the PMBus regulator framework and ++ can be used to control the output exposed by the device. ++ This option is only useful if regulator framework support is needed. ++ + config SENSORS_STPDDC60 + tristate "ST STPDDC60" + help +@@ -767,6 +827,15 @@ config SENSORS_UCD9200 + This driver can also be built as a module. If so, the module will + be called ucd9200. + ++config SENSORS_VT7505 ++ tristate "Analog Devices MAX16545, MAX16550 and Volterra VT7505" ++ help ++ If you say yes here you get hardware monitoring support for Analog ++ Devices MAX16545, MAX16550 and Volterra VT7505 PMBus controllers. ++ ++ This driver can also be built as a module. If so, the module will ++ be called vt7505. ++ + config SENSORS_XDP710 + tristate "Infineon XDP710 family" + help +@@ -803,6 +872,16 @@ config SENSORS_XDPE1A2G7B + This driver can also be built as a module. If so, the module will + be called xdpe1a2g7b. + ++config SENSORS_XDPE1A2G7B_REGULATOR ++ bool "Regulator support for XDPE1A2G7B and compatibles" ++ depends on SENSORS_XDPE1A2G7B && REGULATOR ++ help ++ If you say yes here you get regulator support for Infineon ++ XDPE1A2G5B and XDPE1A2G7B multi-phase digital controllers. ++ ++ This enables the controllers to be used as regulator devices, ++ providing voltage control through the regulator framework. ++ + config SENSORS_XDPE122 + tristate "Infineon XDPE122 family" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -59,12 +59,15 @@ obj-$(CONFIG_SENSORS_MP5990) += mp5990.o + obj-$(CONFIG_SENSORS_MP9941) += mp9941.o + obj-$(CONFIG_SENSORS_MP9945) += mp9945.o + obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o ++obj-$(CONFIG_SENSORS_MPQ82D00) += mpq82d00.o + obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o ++obj-$(CONFIG_SENSORS_MPQ8646) += mpq8646.o + obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o + obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o + obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o + obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o + obj-$(CONFIG_SENSORS_STEF48H28) += stef48h28.o ++obj-$(CONFIG_SENSORS_SQ24860) += sq24860.o + obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o + obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o + obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o +@@ -73,6 +76,7 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o + obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o + obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o + obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o ++obj-$(CONFIG_SENSORS_VT7505) += vt7505.o + obj-$(CONFIG_SENSORS_XDP710) += xdp710.o + obj-$(CONFIG_SENSORS_XDP720) += xdp720.o + obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o +diff --git a/drivers/hwmon/pmbus/acbel-fsg032.c b/drivers/hwmon/pmbus/acbel-fsg032.c +--- a/drivers/hwmon/pmbus/acbel-fsg032.c ++++ b/drivers/hwmon/pmbus/acbel-fsg032.c +@@ -52,6 +52,7 @@ static const struct i2c_device_id acbel_fsg032_id[] = { + { .name = "acbel_fsg032" }, + { } + }; ++MODULE_DEVICE_TABLE(i2c, acbel_fsg032_id); + + static struct pmbus_driver_info acbel_fsg032_info = { + .pages = 1, +diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c +--- a/drivers/hwmon/pmbus/adm1275.c ++++ b/drivers/hwmon/pmbus/adm1275.c +@@ -19,7 +19,7 @@ + #include "pmbus.h" + + enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, +- adm1293, adm1294, sq24905c }; ++ adm1293, adm1294, bd12780, bd12790, sq24905c }; + + #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) + #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) +@@ -47,6 +47,8 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, + #define ADM1278_VOUT_EN BIT(1) + + #define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT) ++/* The BD127[89]0 data sheets mark TSFILT bit as reserved. */ ++#define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN) + + #define ADM1293_IRANGE_25 0 + #define ADM1293_IRANGE_50 BIT(6) +@@ -134,6 +136,30 @@ static const struct coefficients adm1272_coefficients[] = { + + }; + ++/* ++ * BD12790 coefficients derived from preliminary datasheet, Table 1 (p.18) ++ * and the PMBus direct-format relationship X = (Y * 10^(-R) - b) / m. ++ * ++ * Voltage: V[V] = 14.77e-3 * code (60V) / 24.62e-3 * code (100V) ++ * -> m = 6770, R=-2 / m = 4062, R=-2 ++ * Current: code = I[A] * RS * 132802.1 + 2048 (15mV) / * 66401.06 + 2048 (30mV) ++ * -> m = 1328, b = 2048 * 10^(-R) = 20480, R=-1 / m = 664, same b and R ++ * Power: code = k * RS * PIN, k = 35119.94 / 17559.97 / 21071.44 / 10535.72 ++ * -> m = round(k * 10^(-3-R)), R=-2 for 60V/15mV, R=-3 for the other three ++ * Temperature: code = 4.2 * T + 3188 -> m = 42, b = 3188 * 10 = 31880, R=-1 ++ */ ++static const struct coefficients bd12790_coefficients[] = { ++ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */ ++ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */ ++ [2] = { 1328, 20480, -1 }, /* current, vsense range 15mV */ ++ [3] = { 664, 20480, -1 }, /* current, vsense range 30mV */ ++ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */ ++ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */ ++ [6] = { 17560, 0, -3 }, /* power, vrange 60V, irange 30mV */ ++ [7] = { 10536, 0, -3 }, /* power, vrange 100V, irange 30mV */ ++ [8] = { 42, 31880, -1 }, /* temperature */ ++}; ++ + static const struct coefficients adm1275_coefficients[] = { + [0] = { 19199, 0, -2 }, /* voltage, vrange set */ + [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ +@@ -487,6 +513,8 @@ static const struct i2c_device_id adm1275_id[] = { + { .name = "adm1281", .driver_data = adm1281 }, + { .name = "adm1293", .driver_data = adm1293 }, + { .name = "adm1294", .driver_data = adm1294 }, ++ { .name = "bd12780", .driver_data = bd12780 }, ++ { .name = "bd12790", .driver_data = bd12790 }, + { .name = "mc09c", .driver_data = sq24905c }, + { } + }; +@@ -494,12 +522,13 @@ MODULE_DEVICE_TABLE(i2c, adm1275_id); + + /* Enable VOUT & TEMP1 if not enabled (disabled by default) */ + static int adm1275_enable_vout_temp(struct adm1275_data *data, +- struct i2c_client *client, int config) ++ struct i2c_client *client, int config, ++ u16 defconfig) + { + int ret; + +- if ((config & ADM1278_PMON_DEFCONFIG) != ADM1278_PMON_DEFCONFIG) { +- config |= ADM1278_PMON_DEFCONFIG; ++ if ((config & defconfig) != defconfig) { ++ config |= defconfig; + ret = adm1275_write_pmon_config(data, client, config); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable VOUT/TEMP1 monitoring\n"); +@@ -524,23 +553,19 @@ static int adm1275_probe(struct i2c_client *client) + u32 shunt; + u32 avg; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BYTE_DATA +- | I2C_FUNC_SMBUS_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) && +- (ret != 2 || strncmp(block_buffer, "SY", 2))) { ++ (ret != 2 || strncmp(block_buffer, "SY", 2)) && ++ (ret != 4 || strncmp(block_buffer, "ROHM", 4))) { + dev_err(&client->dev, "Unsupported Manufacturer ID\n"); + return -ENODEV; + } + +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return ret; +@@ -562,6 +587,7 @@ static int adm1275_probe(struct i2c_client *client) + if (mid->driver_data == adm1272 || mid->driver_data == adm1273 || + mid->driver_data == adm1278 || mid->driver_data == adm1281 || + mid->driver_data == adm1293 || mid->driver_data == adm1294 || ++ mid->driver_data == bd12780 || mid->driver_data == bd12790 || + mid->driver_data == sq24905c) + config_read_fn = i2c_smbus_read_word_data; + else +@@ -642,6 +668,7 @@ static int adm1275_probe(struct i2c_client *client) + data->have_power_sampling = true; + + coefficients = adm1272_coefficients; ++ + vindex = (config & ADM1275_VRANGE) ? 1 : 0; + cindex = (config & ADM1272_IRANGE) ? 3 : 2; + /* pindex depends on the combination of the above */ +@@ -666,7 +693,53 @@ static int adm1275_probe(struct i2c_client *client) + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +- ret = adm1275_enable_vout_temp(data, client, config); ++ ret = adm1275_enable_vout_temp(data, client, config, ++ ADM1278_PMON_DEFCONFIG); ++ if (ret) ++ return ret; ++ ++ if (config & ADM1278_VIN_EN) ++ info->func[0] |= PMBUS_HAVE_VIN; ++ break; ++ ++ /* ++ * The BD12790 is almost identical to the adm1272. Only the defconfig ++ * and coefficients have minor differences. ++ */ ++ case bd12790: ++ data->have_vout = true; ++ data->have_pin_max = true; ++ data->have_temp_max = true; ++ data->have_power_sampling = true; ++ ++ coefficients = bd12790_coefficients; ++ ++ vindex = (config & ADM1275_VRANGE) ? 1 : 0; ++ cindex = (config & ADM1272_IRANGE) ? 3 : 2; ++ /* pindex depends on the combination of the above */ ++ switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) { ++ case 0: ++ default: ++ pindex = 4; ++ break; ++ case ADM1275_VRANGE: ++ pindex = 5; ++ break; ++ case ADM1272_IRANGE: ++ pindex = 6; ++ break; ++ case ADM1275_VRANGE | ADM1272_IRANGE: ++ pindex = 7; ++ break; ++ } ++ tindex = 8; ++ ++ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; ++ ++ ret = adm1275_enable_vout_temp(data, client, config, ++ BD12780_PMON_DEFCONFIG); + if (ret) + return ret; + +@@ -728,13 +801,45 @@ static int adm1275_probe(struct i2c_client *client) + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +- ret = adm1275_enable_vout_temp(data, client, config); ++ ret = adm1275_enable_vout_temp(data, client, config, ++ ADM1278_PMON_DEFCONFIG); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; ++ ++ /* ++ * The BD12780 is almost functionally identical with the adm1278 above. ++ * Only differences visible to the driver are lack of TSFILT bits and ++ * different identification register contents. ++ */ ++ case bd12780: ++ data->have_vout = true; ++ data->have_pin_max = true; ++ data->have_temp_max = true; ++ data->have_power_sampling = true; ++ ++ coefficients = adm1278_coefficients; ++ vindex = 0; ++ cindex = 1; ++ pindex = 2; ++ tindex = 3; ++ ++ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; ++ ++ ret = adm1275_enable_vout_temp(data, client, config, ++ BD12780_PMON_DEFCONFIG); ++ if (ret) ++ return ret; ++ ++ if (config & ADM1278_VIN_EN) ++ info->func[0] |= PMBUS_HAVE_VIN; ++ ++ break; + case adm1293: + case adm1294: + data->have_iout_min = true; +@@ -870,9 +975,27 @@ static int adm1275_probe(struct i2c_client *client) + return pmbus_do_probe(client, info); + } + ++static const struct of_device_id adm1275_of_match[] = { ++ { .compatible = "adi,adm1075", }, ++ { .compatible = "adi,adm1272", }, ++ { .compatible = "adi,adm1273", }, ++ { .compatible = "adi,adm1275", }, ++ { .compatible = "adi,adm1276", }, ++ { .compatible = "adi,adm1278", }, ++ { .compatible = "adi,adm1281", }, ++ { .compatible = "adi,adm1293", }, ++ { .compatible = "adi,adm1294", }, ++ { .compatible = "rohm,bd12780", }, ++ { .compatible = "rohm,bd12790", }, ++ { .compatible = "silergy,mc09c", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, adm1275_of_match); ++ + static struct i2c_driver adm1275_driver = { + .driver = { + .name = "adm1275", ++ .of_match_table = adm1275_of_match, + }, + .probe = adm1275_probe, + .id_table = adm1275_id, +diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c +--- a/drivers/hwmon/pmbus/ir36021.c ++++ b/drivers/hwmon/pmbus/ir36021.c +@@ -31,21 +31,13 @@ static int ir36021_probe(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BYTE_DATA +- | I2C_FUNC_SMBUS_READ_WORD_DATA +- | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_MFR_MODEL, 2, buf); +- if (ret < 0) { +- dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); +- return ret; +- } +- if (ret != 2 || buf[0] != 0x01 || buf[1] != 0x2d) { +- dev_err(&client->dev, "MFR_MODEL unrecognised\n"); +- return -ENODEV; +- } ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to read PMBUS_MFR_MODEL\n"); ++ if (ret != 1 || buf[0] != 0x2d) ++ return dev_err_probe(&client->dev, -ENODEV, ++ "MFR_MODEL unrecognised\n"); + + return pmbus_do_probe(client, &ir36021_info); + } +diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c +--- a/drivers/hwmon/pmbus/lm25066.c ++++ b/drivers/hwmon/pmbus/lm25066.c +@@ -35,6 +35,7 @@ enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; + #define LM25066_READ_AVG_PIN 0xdf + + #define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */ ++#define LM25066_DEV_SETUP_CL_CFG BIT(2) /* Current limit configuration */ + + #define LM25066_SAMPLES_FOR_AVG_MAX 4096 + +@@ -485,6 +486,42 @@ static int lm25066_probe(struct i2c_client *client) + + data->id = (enum chips)(unsigned long)i2c_get_match_data(client); + ++ if (data->id != lm25056) { ++ int config_new = config; ++ const char *cl_setting; ++ int ret; ++ ++ if (!of_property_read_string(client->dev.of_node, ++ "ti,current-range", &cl_setting)) { ++ config_new |= LM25066_DEV_SETUP_CL_CFG; ++ if (strcmp(cl_setting, "high") == 0) { ++ if (data->id == lm25066) ++ config_new |= LM25066_DEV_SETUP_CL; ++ else ++ config_new &= ~LM25066_DEV_SETUP_CL; ++ } else if (strcmp(cl_setting, "low") == 0) { ++ if (data->id == lm25066) ++ config_new &= ~LM25066_DEV_SETUP_CL; ++ else ++ config_new |= LM25066_DEV_SETUP_CL; ++ } else { ++ dev_err(&client->dev, ++ "invalid current-range setting: %s\n", ++ cl_setting); ++ return -EINVAL; ++ } ++ } ++ ++ if (config_new != config) { ++ ret = i2c_smbus_write_byte_data(client, ++ LM25066_DEVICE_SETUP, ++ config_new); ++ if (ret < 0) ++ return ret; ++ config = config_new; ++ } ++ } ++ + info = &data->info; + + info->pages = 1; +diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c +--- a/drivers/hwmon/pmbus/ltc2978.c ++++ b/drivers/hwmon/pmbus/ltc2978.c +@@ -611,17 +611,13 @@ static int ltc2978_get_id(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) + return ret; + if (ret < 3 || (strncmp(buf, "LTC", 3) && strncmp(buf, "ADI", 3))) + return -ENODEV; + +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) + return ret; + for (id = <c2978_id[0]; strlen(id->name); id++) { +@@ -637,16 +633,17 @@ static int ltc2978_get_id(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, +- sizeof(buf), buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, ++ buf); + if (ret < 0) + return ret; + +- if (!strncmp(buf + 1, "LT7170", 6) || +- !strncmp(buf + 1, "LT7170-1", 8)) ++ if (ret < 6) ++ return -ENODEV; ++ ++ if (!strncmp(buf, "LT7170", 6)) + return lt7170; +- if (!strncmp(buf + 1, "LT7171", 6) || +- !strncmp(buf + 1, "LT7171-1", 8)) ++ if (!strncmp(buf, "LT7171", 6)) + return lt7171; + + return -ENODEV; +diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c +--- a/drivers/hwmon/pmbus/max20830.c ++++ b/drivers/hwmon/pmbus/max20830.c +@@ -7,12 +7,69 @@ + + #include + #include ++#include + #include + #include + #include "pmbus.h" + + #define MAX20830_IC_DEVICE_ID_LENGTH 9 + ++struct max20830_data { ++ struct pmbus_driver_info info; ++ u32 vout_rfb1; ++ u32 vout_rfb2; ++}; ++ ++static const char * const supported_chip_ids[] = { ++ "MAX20830", ++ "MAX20830C", ++ "MAX20840C", ++}; ++ ++/* ++ * MAX20830 only supports READ_VOUT for VOUT monitoring. ++ * ++ * Limit registers (VOUT_OV_WARN_LIMIT, VOUT_OV_FAULT_LIMIT, etc.) are not ++ * supported by this driver and return -ENODATA. This means sysfs attributes ++ * like in1_max, in1_crit, etc. will not be available. Only in1_input (the ++ * scaled output voltage) is supported. ++ * ++ * MAX20830 uses an external resistor divider for voltage sensing: ++ * - VOUT_COMMAND and VOUT_MAX set the reference voltage at the feedback pin ++ * - READ_VOUT reports the feedback voltage, which needs to be scaled for actual ++ * output voltage ++ * ++ * Scaling formula: vout_actual = vout_fb × (1 + RFB1 / RFB2) ++ * ++ * If regulator support is added in the future, some adjustments are needed to ++ * ensure correct feedback voltages are set. ++ */ ++static int max20830_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ const struct max20830_data *data = container_of(info, struct max20830_data, info); ++ int ret; ++ u64 temp; ++ ++ switch (reg) { ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ /* Apply voltage divider scaling if resistors are non-zero */ ++ if (data->vout_rfb1 && data->vout_rfb2) { ++ temp = (u64)data->vout_rfb1 + (u64)data->vout_rfb2; ++ temp = DIV_ROUND_CLOSEST_ULL((u64)ret * temp, data->vout_rfb2); ++ ret = clamp_val(temp, 0, 0xFFFF); ++ } ++ return ret; ++ default: ++ return -ENODATA; ++ } ++} ++ + static struct pmbus_driver_info max20830_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, +@@ -23,61 +80,53 @@ static struct pmbus_driver_info max20830_info = { + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, ++ .read_word_data = max20830_read_word_data, ++ .have_pmbus_revision = true, ++ .pmbus_revision = PMBUS_REV_13, + }; + + static int max20830_probe(struct i2c_client *client) + { + u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {}; +- int ret; ++ struct max20830_data *data; ++ int i, ret; + +- if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA) && +- !i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) +- return -ENODEV; ++ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; + +- /* +- * Use i2c_smbus_read_block_data() if supported, otherwise fall back +- * to i2c_smbus_read_i2c_block_data() to support I2C controllers +- * which do not support SMBus block reads. +- */ +- if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { +- /* Reads 9 Data bytes from MAX20830 */ +- ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); +- if (ret < 0) +- return dev_err_probe(&client->dev, ret, +- "Failed to read IC_DEVICE_ID\n"); +- } else { +- /* Reads 1 length byte + 9 Data bytes from MAX20830 */ +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, +- MAX20830_IC_DEVICE_ID_LENGTH + 1, +- buf); +- if (ret < 0) +- return dev_err_probe(&client->dev, ret, +- "Failed to read IC_DEVICE_ID\n"); +- /* +- * Moves data forward, removing the length byte, this is to +- * match the format of i2c_smbus_read_block_data(). +- * Also adjust return value to reflect length byte removal. +- */ +- memmove(buf, buf + 1, MAX20830_IC_DEVICE_ID_LENGTH); +- ret = ret - 1; +- } ++ data->info = max20830_info; + +- /* +- * MAX20830 IC_DEVICE_ID sends string data "MAX20830\0". +- * Return value should at least be 9 bytes of data. +- */ ++ /* Read optional voltage divider resistor values */ ++ device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1); ++ device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2); ++ ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, buf); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to read IC_DEVICE_ID\n"); ++ ++ /* Verify we read the expected number of bytes */ + if (ret < MAX20830_IC_DEVICE_ID_LENGTH) + return dev_err_probe(&client->dev, -ENODEV, +- "IC_DEVICE_ID too short: expected at least 9 bytes, got %d\n", +- ret); ++ "IC_DEVICE_ID too short: expected %d bytes, got %d\n", ++ MAX20830_IC_DEVICE_ID_LENGTH, ret); + +- /* 9 bytes of data, buf[0]-buf[7] = "MAX20830", buf[8] = '\0' */ +- buf[MAX20830_IC_DEVICE_ID_LENGTH - 1] = '\0'; +- if (strncmp(buf, "MAX20830", MAX20830_IC_DEVICE_ID_LENGTH - 1)) ++ /* Null-terminate the string */ ++ buf[ret] = '\0'; ++ ++ /* Verify the device ID matches what we expect */ ++ for (i = 0; i < ARRAY_SIZE(supported_chip_ids); i++) { ++ if (!strcmp(buf, supported_chip_ids[i])) ++ break; ++ } ++ ++ /* No match found - unsupported device */ ++ if (i == ARRAY_SIZE(supported_chip_ids)) + return dev_err_probe(&client->dev, -ENODEV, +- "Unsupported device: '%s'\n", buf); ++ "Unsupported device: '%*pE'\n", ret, buf); + +- return pmbus_do_probe(client, &max20830_info); ++ return pmbus_do_probe(client, &data->info); + } + + static const struct i2c_device_id max20830_id[] = { +diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c +--- a/drivers/hwmon/pmbus/max34440.c ++++ b/drivers/hwmon/pmbus/max34440.c +@@ -23,6 +23,7 @@ enum chips { + max34441, + max34446, + max34451, ++ max34452, + max34460, + max34461, + }; +@@ -59,6 +60,7 @@ enum chips { + #define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A + + #define MAX34451ETNA6_MFR_REV 0x0012 ++#define MAX34451ETNA8_MFR_REV 0x0014 + + #define MAX34451_MFR_CHANNEL_CONFIG 0xe4 + #define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f +@@ -105,16 +107,21 @@ static int max34440_read_word_data(struct i2c_client *client, int page, + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_MFR_MAX_TEMP_1: + /* +- * MAX34451/ADPM family do not support VIN/IIN limit registers, +- * manufacturer-specific min/max registers, or undercurrent/ +- * undertemperature fault limits. Accessing these triggers CML +- * error and asserts ALERT. ++ * MAX34451/MAX34452/ADPM family do not support VIN/IIN limit ++ * registers, manufacturer-specific min/max registers, or ++ * undercurrent/undertemperature fault limits. Accessing these ++ * triggers CML error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + ret = -ENODATA; + break; ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ if (data->id == max34452) ++ return -ENXIO; ++ return -ENODATA; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_MFR_VOUT_MIN); +@@ -125,8 +132,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, + break; + case PMBUS_VIRT_READ_IOUT_AVG: + if (data->id != max34446 && data->id != max34451 && +- data->id != adpm12160 && data->id != adpm12200 && +- data->id != adpm12250) ++ data->id != max34452 && data->id != adpm12160 && ++ data->id != adpm12200 && data->id != adpm12250) + return -ENXIO; + ret = pmbus_read_word_data(client, page, phase, + MAX34446_MFR_IOUT_AVG); +@@ -191,6 +198,10 @@ static int max34440_write_word_data(struct i2c_client *client, int page, + ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit, + word); + break; ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ if (data->id == max34452) ++ return -ENXIO; ++ return -ENODATA; + case PMBUS_VIRT_RESET_POUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_PEAK, 0); +@@ -211,8 +222,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page, + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_IOUT_PEAK, 0); + if (!ret && (data->id == max34446 || data->id == max34451 || +- data->id == adpm12160 || data->id == adpm12200 || +- data->id == adpm12250)) ++ data->id == max34452 || data->id == adpm12160 || ++ data->id == adpm12200 || data->id == adpm12250)) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_IOUT_AVG, 0); + +@@ -280,12 +291,13 @@ static int max34451_read_byte_data(struct i2c_client *client, int page, int reg) + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_OTHER: + /* +- * MAX34451/ADPM family do not support STATUS_BYTE or ++ * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or + * STATUS_OTHER registers. Accessing them triggers CML + * error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + return -ENODATA; + default: +@@ -303,12 +315,13 @@ static int max34451_write_byte_data(struct i2c_client *client, int page, + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_OTHER: + /* +- * MAX34451/ADPM family do not support STATUS_BYTE or ++ * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or + * STATUS_OTHER registers. Writing to them triggers CML + * error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + return -ENODATA; + default: +@@ -339,16 +352,28 @@ static int max34451_set_supported_funcs(struct i2c_client *client, + if (rv < 0) + return rv; + +- if (rv >= MAX34451ETNA6_MFR_REV) { ++ if (data->id == max34451 && rv >= MAX34451ETNA6_MFR_REV) { + max34451_na6 = true; + data->info.format[PSC_VOLTAGE_IN] = direct; + data->info.format[PSC_CURRENT_IN] = direct; +- data->info.m[PSC_VOLTAGE_IN] = 1; ++ + data->info.b[PSC_VOLTAGE_IN] = 0; +- data->info.R[PSC_VOLTAGE_IN] = 3; +- data->info.m[PSC_CURRENT_IN] = 1; + data->info.b[PSC_CURRENT_IN] = 0; +- data->info.R[PSC_CURRENT_IN] = 2; ++ if (rv >= MAX34451ETNA8_MFR_REV) { ++ data->info.m[PSC_VOLTAGE_IN] = 125; ++ data->info.R[PSC_VOLTAGE_IN] = 0; ++ data->info.m[PSC_VOLTAGE_OUT] = 125; ++ data->info.R[PSC_VOLTAGE_OUT] = 0; ++ data->info.m[PSC_CURRENT_IN] = 250; ++ data->info.R[PSC_CURRENT_IN] = -1; ++ data->info.m[PSC_CURRENT_OUT] = 250; ++ data->info.R[PSC_CURRENT_OUT] = -1; ++ } else { ++ data->info.m[PSC_VOLTAGE_IN] = 1; ++ data->info.R[PSC_VOLTAGE_IN] = 3; ++ data->info.m[PSC_CURRENT_IN] = 1; ++ data->info.R[PSC_CURRENT_IN] = 2; ++ } + data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; + } +@@ -665,6 +690,32 @@ static struct pmbus_driver_info max34440_info[] = { + .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, + }, ++ [max34452] = { ++ .pages = 21, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .m[PSC_CURRENT_OUT] = 1, ++ .b[PSC_CURRENT_OUT] = 0, ++ .R[PSC_CURRENT_OUT] = 2, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 2, ++ /* func 0-15 is set dynamically before probing */ ++ .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .read_byte_data = max34451_read_byte_data, ++ .read_word_data = max34440_read_word_data, ++ .write_byte_data = max34451_write_byte_data, ++ .write_word_data = max34440_write_word_data, ++ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, ++ }, + [max34460] = { + .pages = 18, + .format[PSC_VOLTAGE_OUT] = direct, +@@ -748,7 +799,7 @@ static int max34440_probe(struct i2c_client *client) + data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; + +- if (data->id == max34451) { ++ if (data->id == max34451 || data->id == max34452) { + rv = max34451_set_supported_funcs(client, data); + if (rv) + return rv; +@@ -769,6 +820,7 @@ static const struct i2c_device_id max34440_id[] = { + { .name = "max34441", .driver_data = max34441 }, + { .name = "max34446", .driver_data = max34446 }, + { .name = "max34451", .driver_data = max34451 }, ++ { .name = "max34452", .driver_data = max34452 }, + { .name = "max34460", .driver_data = max34460 }, + { .name = "max34461", .driver_data = max34461 }, + { } +diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c +--- a/drivers/hwmon/pmbus/mp2975.c ++++ b/drivers/hwmon/pmbus/mp2975.c +@@ -176,7 +176,6 @@ mp2975_vid2direct(int vrf, int val) + static u16 mp2975_data2reg_linear11(s64 val) + { + s16 exponent = 0, mantissa; +- bool negative = false; + + /* simple case */ + if (val == 0) +@@ -196,10 +195,6 @@ static u16 mp2975_data2reg_linear11(s64 val) + /* Convert mantissa from milli-units to units */ + mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff); + +- /* restore sign */ +- if (negative) +- mantissa = -mantissa; +- + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); + } +diff --git a/drivers/hwmon/pmbus/mpq82d00.c b/drivers/hwmon/pmbus/mpq82d00.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mpq82d00.c +@@ -0,0 +1,249 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Synchronous Step-Down Converter(MPQ82D00) ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++#define MPQ82D00_VOUT_DIV 64 ++ ++#define MPQ82D00_PAGE_NUM 1 ++ ++#define MPQ82D00_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \ ++ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \ ++ PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_STATUS_IOUT | \ ++ PMBUS_HAVE_STATUS_TEMP | \ ++ PMBUS_HAVE_STATUS_INPUT) ++ ++struct mpq82d00_data { ++ struct pmbus_driver_info info; ++ int vout_scale; ++}; ++ ++#define to_mpq82d00_data(x) container_of(x, struct mpq82d00_data, info) ++ ++static u16 mpq82d00_linear_exp_transfer(u16 word, u16 expect_exponent) ++{ ++ s16 exponent, mantissa, target_exponent; ++ ++ exponent = ((s16)word) >> 11; ++ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; ++ target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11; ++ ++ /* ++ * The MPQ82D00 does not support negtive limit value, if a negtive ++ * limit value is written, the limit value will become to 0. And ++ * the maximum positive limit value is limitted to 0x3FF. ++ */ ++ if (mantissa < 0) { ++ mantissa = 0; ++ } else { ++ if (exponent > target_exponent) { ++ mantissa = (1023 >> (exponent - target_exponent)) >= mantissa ? ++ mantissa << (exponent - target_exponent) : ++ 0x3FF; ++ } else { ++ mantissa = clamp_val(mantissa >> (target_exponent - exponent), ++ 0, 0x3FF); ++ } ++ } ++ ++ return mantissa | ((expect_exponent << 11) & 0xf800); ++} ++ ++static int mpq82d00_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ /* ++ * The MPQ82D00 does not follow standard PMBus protocol completely, ++ * and the calculation of vout in this driver is based on direct ++ * format. As a result, the format of vout is enforced to direct. ++ */ ++ ret = PB_VOUT_MODE_DIRECT; ++ break; ++ default: ++ /* ++ * These registers are not explicitly handled by the driver, ++ * as a result, return -ENODATA directly. ++ */ ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mpq82d00_data *data = to_mpq82d00_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_STATUS_WORD: ++ case PMBUS_READ_VIN: ++ case PMBUS_READ_IOUT: ++ case PMBUS_READ_POUT: ++ case PMBUS_READ_PIN: ++ case PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ /* ++ * These registers are not explicitly handled by the driver, ++ * as a result, return -ENODATA directly. ++ */ ++ ret = -ENODATA; ++ break; ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale, ++ MPQ82D00_VOUT_DIV); ++ break; ++ default: ++ /* ++ * The MPQ82D00 do not support other telemetry and limit ++ * value reading, so, return -EINVAL directly. ++ */ ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_write_word_data(struct i2c_client *client, int page, int reg, ++ u16 word) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_OT_FAULT_LIMIT: ++ /* ++ * The PMBUS_OT_FAULT_LIMIT of MPQ82D00 is linear11 format, ++ * and the exponent is a constant value(5'b00000), so the ++ * exponent of word parameter should be converted to 5'b00000. ++ */ ++ ret = pmbus_write_word_data(client, page, reg, ++ mpq82d00_linear_exp_transfer(word, 0x00)); ++ break; ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ /* ++ * The PMBUS_IOUT_OC_FAULT_LIMIT of MPQ82D00 is linear11 format, ++ * and the exponent is a constant value(5'b11100), so the ++ * exponent of word parameter should be converted to 5'b11100. ++ */ ++ ret = pmbus_write_word_data(client, page, reg, ++ mpq82d00_linear_exp_transfer(word, 0x1C)); ++ break; ++ default: ++ /* ++ * The MPQ82D00 do not support other limit value configuration, ++ * so, return -EINVAL directly. ++ */ ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_identify(struct i2c_client *client, struct pmbus_driver_info *info) ++{ ++ struct mpq82d00_data *data = to_mpq82d00_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * The MPQ82D00 supports three vout mode. If PMBUS_VOUT_MODE ++ * bit5 is 1, the vout scale is 5mv/LSB.If PMBUS_VOUT_MODE bit5 ++ * is 0, it is linear mode, the vout scale is 1.953125mv/LSB. ++ */ ++ if (FIELD_GET(GENMASK(5, 5), ret)) ++ data->vout_scale = 320; ++ else ++ data->vout_scale = 125; ++ ++ return 0; ++} ++ ++static struct pmbus_driver_info mpq82d00_info = { ++ .pages = MPQ82D00_PAGE_NUM, ++ .format[PSC_VOLTAGE_IN] = linear, ++ .format[PSC_CURRENT_OUT] = linear, ++ .format[PSC_TEMPERATURE] = linear, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_POWER] = linear, ++ ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ ++ .func[0] = MPQ82D00_RAIL1_FUNC, ++ .read_word_data = mpq82d00_read_word_data, ++ .read_byte_data = mpq82d00_read_byte_data, ++ .write_word_data = mpq82d00_write_word_data, ++ .identify = mpq82d00_identify, ++}; ++ ++static int mpq82d00_probe(struct i2c_client *client) ++{ ++ struct mpq82d00_data *data; ++ ++ data = devm_kzalloc(&client->dev, sizeof(struct mpq82d00_data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ memcpy(&data->info, &mpq82d00_info, sizeof(mpq82d00_info)); ++ ++ return pmbus_do_probe(client, &data->info); ++} ++ ++static const struct i2c_device_id mpq82d00_id[] = { ++ {.name = "mpq82d00"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mpq82d00_id); ++ ++static const struct of_device_id __maybe_unused mpq82d00_of_match[] = { ++ {.compatible = "mps,mpq82d00"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mpq82d00_of_match); ++ ++static struct i2c_driver mpq82d00_driver = { ++ .driver = { ++ .name = "mpq82d00", ++ .of_match_table = mpq82d00_of_match, ++ }, ++ .probe = mpq82d00_probe, ++ .id_table = mpq82d00_id, ++}; ++ ++module_i2c_driver(mpq82d00_driver); ++ ++MODULE_AUTHOR("Wensheng Wang ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++/* Default cadence for the in-driver alarm-poll fallback (ms) */ ++#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000 ++ ++/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */ ++#define MPS_CLEAR_LAST_FAULT 0x08 ++#define MPS_MFR_CFG_EXT 0xF5 ++#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6) ++#define MPS_PROTECTION_LAST 0xFB ++ ++/* ++ * PMBus 1.3 NVM commit / revert commands. MPS equivalent of ++ * STORE_ALL (15h) and RESTORE_ALL (16h). ++ */ ++#define PMBUS_STORE_USER_ALL 0x15 ++#define PMBUS_RESTORE_USER_ALL 0x16 ++ ++/* PMBus 1.3 timing / UVLO command codes */ ++#define PMBUS_VIN_ON 0x35 ++#define PMBUS_VIN_OFF 0x36 ++#define PMBUS_TON_DELAY 0x60 ++#define PMBUS_TON_RISE 0x61 ++#define PMBUS_TOFF_DELAY 0x64 ++#define PMBUS_TOFF_FALL 0x65 ++ ++/* MPS vendor-extended observability / identity registers */ ++#define MPS_MFR_CONFIG_ID 0xC0 ++#define MPS_MFR_CONFIG_CODE_REV 0xC1 ++#define MPS_MFR_PRODUCT_REV_USER 0xC2 ++#define MPS_MFR_SILICON_REV 0xC3 ++#define MPS_MFR_RETRY_TIMES 0xF4 ++#define MPS_MFR_VBOOT_CFG 0xFC ++ ++/* ++ * MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate ++ * subsequent PMBus writes ++ * bits[1:0] = 00 -- unlocked (POR default) ++ * 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21) ++ * so the operator can still DVFS the rail ++ * 11 -- lock all writes ++ * A negative-going PG edge resets these bits to 00, the lock ++ * is operationally reversible without a full chip POR. ++ */ ++#define MPS_MFR_PMBUS_LOCK 0xEE ++ ++/* ++ * Retry parameters for the MFR_CFG_EXT gate-close write after ++ * CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this ++ * silicon is about 1 ms; the datasheet does not have information. ++ */ ++#define MPQ8646_NVM_RETRY_MAX 5 ++#define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000 ++#define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000 ++ ++#define MPQ8646_DEBUG(client, fmt, ...) \ ++ dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__) ++ ++/* ++ * Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon ++ * The chip uses an 11-bit VOUT feedback-divider scale register. ++ */ ++#define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0) ++ ++/* Forward declaration */ ++struct mpq8646_dbg_reg_ctx; ++ ++/* Per-instance state */ ++struct mpq8646_priv { ++ struct pmbus_driver_info info; /* must be first, container_of target */ ++ struct i2c_client *client; ++ ++ /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */ ++ struct mutex mps_lock; ++ ++ /* Set alarm_poll_interval_ms = 0 to disable. */ ++ struct delayed_work alarm_poll_work; ++ u32 alarm_poll_interval_ms; ++ ++#ifdef CONFIG_DEBUG_FS ++ /* the only debugfs file that can re-arm the poll worker */ ++ struct dentry *dbg_poll; ++ struct mpq8646_dbg_reg_ctx *dbg_reg_ctx; ++#endif ++}; ++ ++static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ ++ return container_of(info, struct mpq8646_priv, info); ++} ++ ++/* ++ * VID-mode m/b/R coefficients, same values as the mpq8785 driver for ++ * the MPS VID encoding. Per the PMBus Direct formula used by ++ * pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields ++ * 1.5625 mV per LSB. ++ */ ++#define MPQ8646_VID_M 64 ++#define MPQ8646_VID_B 0 ++#define MPQ8646_VID_R 1 ++ ++static int mpq8646_identify(struct i2c_client *client, ++ struct pmbus_driver_info *info) ++{ ++ int vout_mode; ++ ++ vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); ++ if (vout_mode < 0) ++ return vout_mode; ++ ++ switch (vout_mode & PB_VOUT_MODE_MODE_MASK) { ++ case PB_VOUT_MODE_LINEAR: ++ info->format[PSC_VOLTAGE_OUT] = linear; ++ break; ++ case PB_VOUT_MODE_VID: ++ case PB_VOUT_MODE_DIRECT: ++ info->format[PSC_VOLTAGE_OUT] = direct; ++ info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M; ++ info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B; ++ info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R; ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ return 0; ++}; ++ ++static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg); ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ ret = pmbus_read_byte_data(client, page, reg); ++ MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret); ++ if (ret < 0) ++ return ret; ++ ++ if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID) ++ return PB_VOUT_MODE_DIRECT; ++ ++ return ret; ++ case PMBUS_STATUS_BYTE: ++ case PMBUS_STATUS_CML: ++ case PMBUS_STATUS_OTHER: ++ case PMBUS_STATUS_MFR_SPECIFIC: ++ case PMBUS_STATUS_FAN_12: ++ case PMBUS_STATUS_FAN_34: ++ return -ENXIO; ++ case PMBUS_MFR_LOCATION: ++ case PMBUS_MFR_DATE: ++ case PMBUS_MFR_SERIAL: ++ case PMBUS_IC_DEVICE_ID: ++ case PMBUS_IC_DEVICE_REV: ++ MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg); ++ return -ENXIO; ++ default: ++ return -ENODATA; ++ } ++} ++ ++/* ++ * Reference: ltc2978.c::ltc2978_write_word_data which uses the ++ * same virtual-register channel for its real chip-side peak reset. ++ */ ++static int mpq8646_write_word_data(struct i2c_client *client, int page, ++ int reg, u16 word) ++{ ++ struct mpq8646_priv *priv = mpq8646_priv_from_client(client); ++ int rc; ++ ++ switch (reg) { ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n", ++ reg); ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); ++ if (rc < 0) ++ MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc); ++ return rc < 0 ? rc : 0; ++ default: ++ return -ENODATA; /* let pmbus_core do the direct write */ ++ } ++} ++ ++static int mpq8646_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ int rc; ++ ++ MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n", ++ page, phase, reg); ++ ++ switch (reg) { ++ case PMBUS_READ_VIN: ++ case PMBUS_READ_VOUT: ++ case PMBUS_READ_IOUT: ++ case PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_STATUS_WORD: ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_MFR_VIN_MAX: ++ case PMBUS_MFR_VOUT_MAX: ++ case PMBUS_MFR_IOUT_MAX: ++ case PMBUS_MFR_MAX_TEMP_1: ++ break; ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ return 0; ++ default: ++ return -ENODATA; ++ } ++ ++ rc = i2c_smbus_read_word_data(client, reg); ++ ++ MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc); ++ return rc; ++} ++ ++static struct pmbus_driver_info mpq8646_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .m[PSC_VOLTAGE_IN] = 4, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 1, ++ .m[PSC_CURRENT_OUT] = 16, ++ .b[PSC_CURRENT_OUT] = 0, ++ .R[PSC_CURRENT_OUT] = 0, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 0, ++ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | ++ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, ++}; ++ ++#if IS_ENABLED(CONFIG_REGULATOR) ++static const struct regulator_desc mpq8646_reg_desc[] = { ++ PMBUS_REGULATOR("vout", 0), ++}; ++#endif /* CONFIG_REGULATOR */ ++ ++#if IS_ENABLED(CONFIG_NVMEM) ++/* ++ * Expose using ++ * /sys/bus/nvmem/devices//nvmem ++ * ++ * Layout (16 bytes): ++ * offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM ++ * offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM ++ * offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM ++ * offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM ++ * offset 8 MFR_SILICON_REV (0xC3) byte -- NVM ++ * offset 9..15 reserved (zero-fill, leaves room for additions) ++ */ ++#define MPQ8646_NVMEM_SIZE 16 ++ ++struct mpq8646_nvmem_entry { ++ unsigned int off; ++ u8 reg; ++ bool is_word; ++}; ++ ++static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = { ++ { 0, MPS_PROTECTION_LAST, true }, ++ { 2, MPS_MFR_RETRY_TIMES, true }, ++ { 4, MPS_MFR_CONFIG_ID, true }, ++ { 6, MPS_MFR_VBOOT_CFG, true }, ++ { 8, MPS_MFR_SILICON_REV, false }, ++}; ++ ++static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val, ++ size_t bytes) ++{ ++ struct mpq8646_priv *priv = data; ++ u8 *out = val; ++ size_t i; ++ ++ if (offset >= MPQ8646_NVMEM_SIZE) ++ return -EINVAL; ++ if (offset + bytes > MPQ8646_NVMEM_SIZE) ++ bytes = MPQ8646_NVMEM_SIZE - offset; ++ ++ memset(out, 0, bytes); ++ ++ guard(mutex)(&priv->mps_lock); ++ for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) { ++ const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i]; ++ unsigned int e_start = e->off; ++ unsigned int e_end = e_start + (e->is_word ? 2 : 1); ++ u8 raw[2]; ++ int rc; ++ unsigned int j; ++ ++ if (e_end <= offset || e_start >= offset + bytes) ++ continue; /* outside requested slice */ ++ ++ if (e->is_word) ++ rc = i2c_smbus_read_word_data(priv->client, e->reg); ++ else ++ rc = i2c_smbus_read_byte_data(priv->client, e->reg); ++ if (rc < 0) ++ continue; /* leave the zero-fill in place */ ++ ++ raw[0] = rc & 0xff; ++ raw[1] = (rc >> 8) & 0xff; ++ ++ for (j = 0; j < e_end - e_start; j++) { ++ unsigned int abs = e_start + j; ++ ++ if (abs >= offset && abs < offset + bytes) ++ out[abs - offset] = raw[j]; ++ } ++ } ++ return 0; ++} ++#endif /* CONFIG_NVMEM */ ++ ++static const struct i2c_device_id mpq8646_id[] = { ++ { .name = "mpq8646" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(i2c, mpq8646_id); ++ ++static const struct of_device_id __maybe_unused mpq8646_of_match[] = { ++ { .compatible = "mps,mpq8646" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mpq8646_of_match); ++ ++static struct pmbus_platform_data mpq8646_no_pec_pdata = { ++ .flags = PMBUS_NO_CAPABILITY, ++}; ++ ++#ifdef CONFIG_DEBUG_FS ++/* ++ * Read-only decode cases in the client's pmbus debugfs directory: ++ * the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the ++ * identity/timing registers. ++ */ ++ ++struct mpq_status_bit { ++ u16 mask; ++ const char *name; ++}; ++ ++static const struct mpq_status_bit mpq8646_status_word_bits[] = { ++ { PB_STATUS_VOUT, "VOUT" }, ++ { PB_STATUS_IOUT_POUT, "IOUT_POUT" }, ++ { PB_STATUS_INPUT, "INPUT" }, ++ { PB_STATUS_WORD_MFR, "NVM_SUMMARY" }, ++ { PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" }, ++ { PB_STATUS_FANS, "FANS" }, ++ { PB_STATUS_OTHER, "OTHER" }, ++ { PB_STATUS_UNKNOWN, "WATCH_DOG" }, ++ { PB_STATUS_BUSY, "BUSY" }, ++ { PB_STATUS_OFF, "OFF" }, ++ { PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" }, ++ { PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" }, ++ { PB_STATUS_VIN_UV, "VIN_UV_FAULT" }, ++ { PB_STATUS_TEMPERATURE, "TEMP" }, ++ { PB_STATUS_CML, "CML" }, ++ { PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" }, ++ { /* sentinel */ } ++}; ++ ++/* PROTECTION_LAST (0xFB) bit names, it survives chip POR */ ++static const struct mpq_status_bit mpq8646_protection_last_bits[] = { ++ { BIT(15), "INIT_FAULT" }, ++ { BIT(14), "NVM_CRC_ERROR" }, ++ { BIT(13), "NVM_FAULT" }, ++ { BIT(12), "OC_PHASE_FAULT" }, ++ { BIT(11), "OTP_SELF_FAULT" }, ++ { BIT(9), "SWITCH_PRD_FAULT" }, ++ { BIT(8), "VIN_OV_FAULT" }, ++ { BIT(7), "VOUT_OV_FAULT" }, ++ { BIT(6), "VOUT_UV_FAULT" }, ++ { BIT(5), "OC_TOT_FAULT" }, ++ { BIT(4), "VIN_UVLO_FAULT" }, ++ { BIT(3), "DRMOS_OTP" }, ++ { /* sentinel */ } ++}; ++ ++static void mpq8646_print_bits(struct seq_file *s, u16 v, ++ const struct mpq_status_bit *tab) ++{ ++ const struct mpq_status_bit *t; ++ bool first = true; ++ ++ seq_printf(s, "0x%04x", v); ++ if (!v) { ++ seq_puts(s, " [clean]\n"); ++ return; ++ } ++ seq_puts(s, " ["); ++ for (t = tab; t->mask; t++) { ++ if (v & t->mask) { ++ if (!first) ++ seq_putc(s, ' '); ++ seq_puts(s, t->name); ++ first = false; ++ } ++ } ++ seq_puts(s, "]\n"); ++} ++ ++static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_priv *priv = s->private; ++ int rc; ++ ++ scoped_guard(pmbus_lock, priv->client) ++ rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD); ++ if (rc < 0) { ++ seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc); ++ return 0; ++ } ++ seq_puts(s, "STATUS_WORD: "); ++ mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits); ++ seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded); ++ ++static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_priv *priv = s->private; ++ int rc; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST); ++ ++ if (rc < 0) { ++ seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc); ++ return 0; ++ } ++ seq_puts(s, "PROTECTION_LAST: "); ++ mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits); ++ seq_puts(s, "(NVM-backed, survives chip POR)\n"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last); ++ ++struct mpq8646_dbg_reg { ++ u8 reg; ++ bool is_word; ++ const char *name; ++}; ++ ++static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = { ++ /* MPS vendor extensions (read-only identity / observability) */ ++ { MPS_MFR_CONFIG_ID, true, "mfr_config_id" }, ++ { MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" }, ++ { MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" }, ++ { MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" }, ++ { MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" }, ++ /* PMBus 1.3 standard timing / UVLO (read-only introspection) */ ++ { PMBUS_VIN_ON, true, "vin_on" }, ++ { PMBUS_VIN_OFF, true, "vin_off" }, ++ { PMBUS_TON_DELAY, true, "ton_delay" }, ++ { PMBUS_TON_RISE, true, "ton_rise" }, ++ { PMBUS_TOFF_DELAY, true, "toff_delay" }, ++ { PMBUS_TOFF_FALL, true, "toff_fall" }, ++}; ++ ++struct mpq8646_dbg_reg_ctx { ++ struct mpq8646_priv *priv; ++ const struct mpq8646_dbg_reg *desc; ++}; ++ ++static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = s->private; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_read_word_data(ctx->priv->client, ++ ctx->desc->reg); ++ else ++ rc = i2c_smbus_read_byte_data(ctx->priv->client, ++ ctx->desc->reg); ++ ++ if (rc < 0) { ++ seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n", ++ ctx->desc->reg, ctx->desc->name, rc); ++ return 0; ++ } ++ seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg); ++ ++#ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE ++/* ++ * Write/provisioning data, disabled by default: NVM commit and ++ * revert, the CLEAR_LAST_FAULT sequences and a small set of named ++ * writable registers. ++ */ ++ ++static void mpq8646_unsafe_banner(struct mpq8646_priv *priv) ++{ ++ dev_warn(&priv->client->dev, ++ "**********************************************************\n" ++ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" ++ "** **\n" ++ "** The MPQ8646 provisioning debugfs writes are enabled. **\n" ++ "** Wrong register writes can and likely will physically **\n" ++ "** damage or destroy the chip and/or the board. **\n" ++ "** **\n" ++ "** If you see this message and you are not debugging **\n" ++ "** the kernel, report this immediately to your system **\n" ++ "** administrator! **\n" ++ "** **\n" ++ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" ++ "**********************************************************\n"); ++} ++ ++static int mpq8646_dbg_clear_protection_last(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops, ++ NULL, mpq8646_dbg_clear_protection_last, "%llu\n"); ++ ++static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc, ret; ++ int wp_orig, cfg_orig; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ guard(mutex)(&priv->mps_lock); ++ ++ wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT); ++ if (wp_orig < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n", ++ wp_orig); ++ return wp_orig; ++ } ++ cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT); ++ if (cfg_orig < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n", ++ cfg_orig); ++ return cfg_orig; ++ } ++ ++ if (wp_orig != 0) { ++ rc = i2c_smbus_write_byte_data(priv->client, ++ PMBUS_WRITE_PROTECT, 0); ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WP clear failed (%d), aborting\n", ++ rc); ++ return rc; ++ } ++ } ++ ++ ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, ++ (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN); ++ if (ret < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: gate open failed (%d)\n", ++ ret); ++ } else { ++ ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); ++ if (ret < 0) ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n", ++ ret); ++ ++ for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) { ++ rc = i2c_smbus_write_word_data(priv->client, ++ MPS_MFR_CFG_EXT, ++ (u16)cfg_orig); ++ if (rc >= 0) ++ break; ++ usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN, ++ MPQ8646_NVM_RETRY_DELAY_US_MAX); ++ } ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n", ++ rc); ++ if (ret == 0) ++ ret = rc; ++ } ++ } ++ ++ if (wp_orig != 0) { ++ rc = i2c_smbus_write_byte_data(priv->client, ++ PMBUS_WRITE_PROTECT, ++ (u8)wp_orig); ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WP restore failed (%d)\n", ++ rc); ++ if (ret == 0) ++ ret = rc; ++ } ++ } ++ return ret; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops, ++ NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n"); ++ ++static int mpq8646_dbg_store_all(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "store_all: STORE_USER_ALL (0x15) write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops, ++ NULL, mpq8646_dbg_store_all, "%llu\n"); ++ ++static int mpq8646_dbg_restore_all(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops, ++ NULL, mpq8646_dbg_restore_all, "%llu\n"); ++ ++static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = { ++ /* PMBus 1.3 control / margin */ ++ { PMBUS_ON_OFF_CONFIG, false, "on_off_config" }, ++ { PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" }, ++ { PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" }, ++ /* MPS PMBus-level write-protect */ ++ { MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" }, ++ /* MPS user-writable product revision */ ++ { MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" }, ++}; ++ ++static int mpq8646_dbg_reg_get(void *data, u64 *val) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = data; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_read_word_data(ctx->priv->client, ++ ctx->desc->reg); ++ else ++ rc = i2c_smbus_read_byte_data(ctx->priv->client, ++ ctx->desc->reg); ++ if (rc < 0) ++ return rc; ++ *val = rc; ++ return 0; ++} ++ ++static int mpq8646_dbg_reg_set(void *data, u64 val) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = data; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_write_word_data(ctx->priv->client, ++ ctx->desc->reg, (u16)val); ++ else ++ rc = i2c_smbus_write_byte_data(ctx->priv->client, ++ ctx->desc->reg, (u8)val); ++ return rc < 0 ? rc : 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops, ++ mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n"); ++ ++static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, ++ struct dentry *root) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx; ++ size_t i; ++ ++ mpq8646_unsafe_banner(priv); ++ ++ debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv, ++ &mpq8646_dbg_clear_protection_last_fops); ++ debugfs_create_file_unsafe("clear_protection_last_force", 0200, root, ++ priv, ++ &mpq8646_dbg_clear_protection_last_force_fops); ++ debugfs_create_file_unsafe("store_all", 0200, root, priv, ++ &mpq8646_dbg_store_all_fops); ++ debugfs_create_file_unsafe("restore_all", 0200, root, priv, ++ &mpq8646_dbg_restore_all_fops); ++ ++ ctx = devm_kcalloc(&priv->client->dev, ++ ARRAY_SIZE(mpq8646_dbg_regs_unsafe), ++ sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return; ++ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) { ++ ctx[i].priv = priv; ++ ctx[i].desc = &mpq8646_dbg_regs_unsafe[i]; ++ debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name, ++ 0600, root, &ctx[i], ++ &mpq8646_dbg_reg_rw_fops); ++ } ++} ++#else ++static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, ++ struct dentry *root) {} ++#endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */ ++ ++static int mpq8646_dbg_poll_interval_get(void *data, u64 *val) ++{ ++ struct mpq8646_priv *priv = data; ++ ++ *val = priv->alarm_poll_interval_ms; ++ return 0; ++} ++ ++static int mpq8646_dbg_poll_interval_set(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ bool was_off = !priv->alarm_poll_interval_ms; ++ ++ priv->alarm_poll_interval_ms = (u32)val; ++ ++ if (val && was_off && !priv->client->irq) ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies((u32)val)); ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops, ++ mpq8646_dbg_poll_interval_get, ++ mpq8646_dbg_poll_interval_set, "%llu\n"); ++ ++static void mpq8646_debugfs_register(struct mpq8646_priv *priv) ++{ ++ struct dentry *root; ++ size_t i; ++ ++ root = pmbus_get_debugfs_dir(priv->client); ++ if (!root) ++ return; ++ ++ /* MPS extensions: status decode + NVM-backed PROTECTION_LAST */ ++ debugfs_create_file("status_decoded", 0400, root, priv, ++ &mpq8646_dbg_status_decoded_fops); ++ debugfs_create_file("protection_last", 0400, root, priv, ++ &mpq8646_dbg_protection_last_fops); ++ priv->dbg_poll = ++ debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600, ++ root, priv, ++ &mpq8646_dbg_poll_interval_fops); ++ ++ priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev, ++ ARRAY_SIZE(mpq8646_dbg_regs), ++ sizeof(*priv->dbg_reg_ctx), ++ GFP_KERNEL); ++ if (!priv->dbg_reg_ctx) ++ return; ++ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) { ++ priv->dbg_reg_ctx[i].priv = priv; ++ priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i]; ++ debugfs_create_file(mpq8646_dbg_regs[i].name, 0400, ++ root, &priv->dbg_reg_ctx[i], ++ &mpq8646_dbg_reg_fops); ++ } ++ ++ mpq8646_debugfs_register_unsafe(priv, root); ++} ++ ++static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) ++{ ++ debugfs_remove(priv->dbg_poll); ++} ++#else ++static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {} ++static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {} ++#endif /* CONFIG_DEBUG_FS */ ++ ++static void mpq8646_alarm_poll_work(struct work_struct *work) ++{ ++ struct mpq8646_priv *priv = container_of(to_delayed_work(work), ++ struct mpq8646_priv, ++ alarm_poll_work); ++ ++ if (priv->client->irq) ++ return; /* SMBALERT# wired; polling not needed */ ++ ++ if (!priv->alarm_poll_interval_ms) ++ return; /* polling disabled; don't re-arm */ ++ ++ pmbus_check_and_notify_faults(priv->client); ++ ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies(priv->alarm_poll_interval_ms)); ++} ++ ++static int mpq8646_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct pmbus_driver_info *info; ++ struct mpq8646_priv *priv; ++ u32 voltage_scale; ++ int ret; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ priv->client = client; ++ mutex_init(&priv->mps_lock); ++ memcpy(&priv->info, &mpq8646_info, sizeof(priv->info)); ++ info = &priv->info; ++ ++ info->identify = mpq8646_identify; ++ info->read_byte_data = mpq8646_read_byte_data; ++ info->read_word_data = mpq8646_read_word_data; ++ info->write_word_data = mpq8646_write_word_data; ++ dev->platform_data = &mpq8646_no_pec_pdata; ++ ++#if IS_ENABLED(CONFIG_REGULATOR) ++ info->reg_desc = mpq8646_reg_desc; ++ info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc); ++#endif ++ ++ INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work); ++ priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT; ++ ++ if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille", ++ &voltage_scale)) { ++ if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX) ++ return -EINVAL; ++ ++ ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, ++ voltage_scale); ++ if (ret) ++ return ret; ++ } ++ ++ ret = pmbus_do_probe(client, info); ++ if (ret) ++ return ret; ++ ++ mpq8646_debugfs_register(priv); ++ ++ if (!client->irq) ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies(priv->alarm_poll_interval_ms)); ++ ++#if IS_ENABLED(CONFIG_NVMEM) ++ { ++ struct nvmem_config cfg = { ++ .dev = &client->dev, ++ .name = dev_name(&client->dev), ++ .owner = THIS_MODULE, ++ .read_only = true, ++ .root_only = true, ++ .word_size = 1, ++ .stride = 1, ++ .size = MPQ8646_NVMEM_SIZE, ++ .reg_read = mpq8646_nvmem_read, ++ .priv = priv, ++ }; ++ struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg); ++ ++ if (IS_ERR(nv)) ++ dev_warn(&client->dev, ++ "nvmem snapshot register failed (%pe)\n", ++ nv); ++ } ++#endif ++ return 0; ++}; ++ ++static void mpq8646_remove(struct i2c_client *client) ++{ ++ struct mpq8646_priv *priv = mpq8646_priv_from_client(client); ++ ++ mpq8646_debugfs_unregister(priv); ++ cancel_delayed_work_sync(&priv->alarm_poll_work); ++} ++ ++static struct i2c_driver mpq8646_driver = { ++ .driver = { ++ .name = "mpq8646", ++ .of_match_table = of_match_ptr(mpq8646_of_match), ++ }, ++ .probe = mpq8646_probe, ++ .remove = mpq8646_remove, ++ .id_table = mpq8646_id, ++}; ++ ++module_i2c_driver(mpq8646_driver); ++ ++MODULE_AUTHOR("Vincent Jardin "); ++MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h +--- a/drivers/hwmon/pmbus/pmbus.h ++++ b/drivers/hwmon/pmbus/pmbus.h +@@ -421,10 +421,12 @@ enum pmbus_data_format { linear = 0, ieee754, direct, vid }; + enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; + + /* PMBus revision identifiers */ +-#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ +-#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ +-#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ +-#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ ++#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ ++#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ ++#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ ++#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ ++#define PMBUS_REV_131 0x44 /* PMBus revision 1.3.1 */ ++#define PMBUS_REV_14 0x55 /* PMBus revision 1.4 */ + + /* Operation type flags for pmbus_update_ts */ + #define PMBUS_OP_WRITE BIT(0) +@@ -489,6 +491,16 @@ struct pmbus_driver_info { + int access_delay; /* in microseconds */ + int write_delay; /* in microseconds */ + int page_change_delay; /* in microseconds */ ++ ++ /* ++ * Some chips do not support the PMBUS_REVISION command. ++ * Drivers for such chips can report the supported PMBus revision here. ++ * ++ * Drivers must set have_pmbus_revision to true and provide the ++ * supported PMBus version in pmbus_revision. ++ */ ++ bool have_pmbus_revision; /* true if pmbus_revision is valid */ ++ u8 pmbus_revision; /* PMBus revision */ + }; + + /* Regulator ops */ +@@ -550,6 +562,7 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update); + void pmbus_wait(struct i2c_client *client); + void pmbus_update_ts(struct i2c_client *client, int op); + int pmbus_set_page(struct i2c_client *client, int page, int phase); ++int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf); + int pmbus_read_word_data(struct i2c_client *client, int page, int phase, + u8 reg); + int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, +@@ -561,6 +574,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, + int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + u8 mask, u8 value); + void pmbus_clear_faults(struct i2c_client *client); ++void pmbus_check_and_notify_faults(struct i2c_client *client); + bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); + bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); + int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info); +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -96,7 +96,8 @@ struct pmbus_data { + + u32 flags; /* from platform data */ + +- u8 revision; /* The PMBus revision the device is compliant with */ ++ bool have_pmbus_revision; ++ u8 revision; /* The PMBus revision the device is compliant with */ + + int exponent[PMBUS_PAGES]; + /* linear mode: exponent for output voltages */ +@@ -181,7 +182,12 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); + void pmbus_wait(struct i2c_client *client) + { + struct pmbus_data *data = i2c_get_clientdata(client); +- s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get()); ++ s64 delay; ++ ++ if (!data) ++ return; ++ ++ delay = ktime_us_delta(data->next_access_backoff, ktime_get()); + + if (delay > 0) + fsleep(delay); +@@ -192,8 +198,14 @@ EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS"); + void pmbus_update_ts(struct i2c_client *client, int op) + { + struct pmbus_data *data = i2c_get_clientdata(client); +- const struct pmbus_driver_info *info = data->info; +- int delay = info->access_delay; ++ const struct pmbus_driver_info *info; ++ int delay; ++ ++ if (!data) ++ return; ++ ++ info = data->info; ++ delay = info->access_delay; + + if (op & PMBUS_OP_WRITE) + delay = max(delay, info->write_delay); +@@ -517,6 +529,81 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + } + EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS"); + ++/** ++ * pmbus_read_smbus_i2c_block_data() - Read SMBus/I2C block data ++ * @client: Handle to slave device ++ * @reg: Byte interpreted by slave ++ * @data_buf: Byte array into which data will be read ++ * Return: Negative errno or number of bytes read ++ * ++ * PMBus internal function to read a SMBus block from a PMBus chip. ++ * ++ * PMBus chips report various properties using SMBus block read operations. ++ * However, not all I2C controllers support this operation. ++ * ++ * Execute SMBus block read if supported. If not supported, but SMBus I2C block ++ * read is supported, use it instead. Note that at most 31 data bytes can be ++ * read from the device if i2c_smbus_read_i2c_block_data() is used to read the ++ * data. This is a SMBUs protocol limit which can not be avoided. ++ * ++ * Return -EOPNOTSUPP if neither I2C_FUNC_SMBUS_READ_BLOCK_DATA nor ++ * I2C_FUNC_SMBUS_READ_I2C_BLOCK is supported. ++ * ++ * Callers must hold pmbus_lock or execute calls from the probe function. ++ */ ++int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf) ++{ ++ u8 buf[I2C_SMBUS_BLOCK_MAX]; ++ int blen, len, ret; ++ ++ if (i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { ++ pmbus_wait(client); ++ ret = i2c_smbus_read_block_data(client, reg, data_buf); ++ pmbus_update_ts(client, 0); ++ return ret; ++ } ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { ++ dev_err_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_I2C_BLOCK\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ /* ++ * If the returned data is valid SMBus block data, the first byte ++ * must be the data length. ++ * ++ * i2c_smbus_read_i2c_block_data() may return an error if the chip ++ * sends NACK before the number of requested bytes is received. ++ * Handle this by reading the data length first, then reading the ++ * entire message up to I2C_SMBUS_BLOCK_MAX bytes. This ensures ++ * that requested number of bytes never exceeds the number of ++ * bytes sent by the chip. ++ */ ++ pmbus_wait(client); ++ ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); ++ pmbus_update_ts(client, 0); ++ if (ret < 0) ++ return ret; ++ ++ len = buf[0]; ++ if (len == 0) ++ return 0; ++ blen = len; ++ if (len >= I2C_SMBUS_BLOCK_MAX) ++ len = I2C_SMBUS_BLOCK_MAX - 1; ++ pmbus_wait(client); ++ ret = i2c_smbus_read_i2c_block_data(client, reg, len + 1, buf); ++ pmbus_update_ts(client, 0); ++ if (ret < 0) ++ return ret; ++ if (buf[0] != blen) ++ return -EIO; ++ memcpy(data_buf, buf + 1, len); ++ return len; ++} ++EXPORT_SYMBOL_NS_GPL(pmbus_read_smbus_i2c_block_data, "PMBUS"); ++ + static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + char *data_buf) + { +@@ -526,11 +613,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + if (rv < 0) + return rv; + +- pmbus_wait(client); +- rv = i2c_smbus_read_block_data(client, reg, data_buf); +- pmbus_update_ts(client, 0); +- +- return rv; ++ return pmbus_read_smbus_i2c_block_data(client, reg, data_buf); + } + + static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, +@@ -2849,9 +2932,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, + if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) + pmbus_init_wp(client, data); + +- ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); +- if (ret >= 0) +- data->revision = ret; ++ if (info->have_pmbus_revision) { ++ data->have_pmbus_revision = true; ++ data->revision = info->pmbus_revision; ++ } else { ++ ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); ++ if (ret >= 0) { ++ data->have_pmbus_revision = true; ++ data->revision = ret; ++ } ++ } + + if (data->info->pages) + pmbus_clear_faults(client); +@@ -3455,10 +3545,9 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, + return ret; + } + +-static irqreturn_t pmbus_fault_handler(int irq, void *pdata) ++void pmbus_check_and_notify_faults(struct i2c_client *client) + { +- struct pmbus_data *data = pdata; +- struct i2c_client *client = to_i2c_client(data->dev); ++ struct pmbus_data *data = i2c_get_clientdata(client); + int i, status, event; + + guard(pmbus_lock)(client); +@@ -3471,6 +3560,15 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata) + } + + pmbus_clear_faults(client); ++} ++EXPORT_SYMBOL_NS_GPL(pmbus_check_and_notify_faults, "PMBUS"); ++ ++static irqreturn_t pmbus_fault_handler(int irq, void *pdata) ++{ ++ struct pmbus_data *data = pdata; ++ struct i2c_client *client = to_i2c_client(data->dev); ++ ++ pmbus_check_and_notify_faults(client); + + return IRQ_HANDLED; + } +@@ -3514,10 +3612,8 @@ static int pmbus_irq_setup(struct i2c_client *client, struct pmbus_data *data) + /* Register notifiers */ + err = devm_request_threaded_irq(dev, client->irq, NULL, pmbus_fault_handler, + IRQF_ONESHOT, "pmbus-irq", data); +- if (err) { +- dev_err(dev, "failed to request an irq %d\n", err); ++ if (err) + return err; +- } + + return 0; + } +@@ -3543,6 +3639,17 @@ static int pmbus_debugfs_get(void *data, u64 *val) + DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, + "0x%02llx\n"); + ++static int pmbus_debugfs_get_revision(void *data, u64 *val) ++{ ++ struct pmbus_data *pdata = data; ++ ++ *val = pdata->revision; ++ ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_revision_ops, pmbus_debugfs_get_revision, NULL, ++ "0x%02llx\n"); ++ + static int pmbus_debugfs_get_status(void *data, u64 *val) + { + struct pmbus_debugfs_entry *entry = data; +@@ -3698,14 +3805,9 @@ static void pmbus_init_debugfs(struct i2c_client *client, + &entries[idx++], + &pmbus_debugfs_ops); + } +- if (pmbus_check_byte_register(client, 0, PMBUS_REVISION)) { +- entries[idx].client = client; +- entries[idx].page = 0; +- entries[idx].reg = PMBUS_REVISION; +- debugfs_create_file("pmbus_revision", 0444, debugfs, +- &entries[idx++], +- &pmbus_debugfs_ops); +- } ++ if (data->have_pmbus_revision) ++ debugfs_create_file("pmbus_revision", 0444, debugfs, data, ++ &pmbus_debugfs_revision_ops); + + for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_block_data); i++) { + const struct pmbus_debugfs_data *d = &pmbus_debugfs_block_data[i]; +diff --git a/drivers/hwmon/pmbus/sq24860.c b/drivers/hwmon/pmbus/sq24860.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/sq24860.c +@@ -0,0 +1,430 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Author: Ziming Zhu ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmbus.h" ++ ++#define SQ24860_IIN_CAL_GAIN 0x38 ++#define SQ24860_READ_VAUX 0xd0 ++#define SQ24860_READ_VIN_MIN 0xd1 ++#define SQ24860_READ_VIN_PEAK 0xd2 ++#define SQ24860_READ_IIN_PEAK 0xd4 ++#define SQ24860_READ_PIN_PEAK 0xd5 ++#define SQ24860_READ_TEMP_AVG 0xd6 ++#define SQ24860_READ_TEMP_PEAK 0xd7 ++#define SQ24860_READ_VOUT_MIN 0xda ++#define SQ24860_READ_VIN_AVG 0xdc ++#define SQ24860_READ_VOUT_AVG 0xdd ++#define SQ24860_READ_IIN_AVG 0xde ++#define SQ24860_READ_PIN_AVG 0xdf ++#define SQ24860_VIREF 0xe0 ++#define SQ24860_PK_MIN_AVG 0xea ++#define PK_MIN_AVG_RST_PEAK BIT(7) ++#define PK_MIN_AVG_RST_AVG BIT(6) ++#define PK_MIN_AVG_RST_MIN BIT(5) ++#define PK_MIN_AVG_AVG_CNT GENMASK(2, 0) ++#define SQ24860_MFR_WRITE_PROTECT 0xf8 ++#define SQ24860_UNLOCKED BIT(7) ++ ++#define SQ24860_8B_SHIFT 2 ++#define SQ24860_IIN_OCF_NUM 1000000 ++#define SQ24860_IIN_OCF_DIV 129278 ++#define SQ24860_IIN_OCF_OFF 165 ++ ++#define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ ++ PK_MIN_AVG_RST_AVG | \ ++ PK_MIN_AVG_RST_MIN) ++#define SQ24860_MAX_SAMPLES BIT(FIELD_MAX(PK_MIN_AVG_AVG_CNT)) ++/* ++ * Arbitrary default Rimon value: 1.6kOhm ++ */ ++#define SQ24860_DEFAULT_RIMON 1600000000 ++#define SQ24860_GIMON 18180 ++ ++#define SQ24860_VAUX_DIV 20 ++ ++static int sq24860_write_iin_cal_gain(struct i2c_client *client, u32 rimon) ++{ ++ u64 temp = 6400ULL * 1000000000ULL * 1000ULL; ++ u64 denom; ++ u64 word; ++ ++ if (!rimon) ++ return -EINVAL; ++ ++ denom = (u64)rimon * SQ24860_GIMON; ++ word = div64_u64(temp, denom); ++ if (!word || word > U16_MAX) ++ return -EINVAL; ++ ++ return i2c_smbus_write_word_data(client, SQ24860_IIN_CAL_GAIN, ++ (u16)word); ++} ++ ++static int sq24860_mfr_write_protect_set(struct i2c_client *client, ++ u8 protect) ++{ ++ u8 val; ++ ++ switch (protect) { ++ case 0: ++ val = 0xa2; ++ break; ++ case PB_WP_ALL: ++ val = 0x0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return pmbus_write_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT, ++ val); ++} ++ ++static int sq24860_mfr_write_protect_get(struct i2c_client *client) ++{ ++ int ret = pmbus_read_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT); ++ ++ if (ret < 0) ++ return ret; ++ ++ return (ret & SQ24860_UNLOCKED) ? 0 : PB_WP_ALL; ++} ++ ++static int sq24860_read_word_data(struct i2c_client *client, ++ int page, int phase, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIRT_READ_VIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_VIN_MIN: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_MIN); ++ break; ++ ++ case PMBUS_VIRT_READ_VIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_VOUT_MIN: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VOUT_MIN); ++ break; ++ ++ case PMBUS_VIRT_READ_VOUT_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VOUT_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_IIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_IIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_IIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_IIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_TEMP_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_TEMP_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_TEMP_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_TEMP_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_PIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_PIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_PIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_PIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_VMON: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VAUX); ++ if (ret < 0) ++ break; ++ ret = DIV_ROUND_CLOSEST(ret, SQ24860_VAUX_DIV); ++ break; ++ ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ /* ++ * These registers provide an 8 bits value instead of a ++ * 10bits one. Just shifting twice the register value is ++ * enough to make the sensor type conversion work, even ++ * if the datasheet provides different m, b and R for ++ * those. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ break; ++ ret <<= SQ24860_8B_SHIFT; ++ break; ++ ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ /* ++ * VIREF directly sets the over-current limit at which the eFuse ++ * will turn the FET off and trigger a fault. Expose it through ++ * this generic property instead of a manufacturer specific one. ++ */ ++ ret = pmbus_read_byte_data(client, page, SQ24860_VIREF); ++ if (ret < 0) ++ break; ++ ret = DIV_ROUND_CLOSEST(ret * SQ24860_IIN_OCF_NUM, ++ SQ24860_IIN_OCF_DIV); ++ ret += SQ24860_IIN_OCF_OFF; ++ break; ++ ++ case PMBUS_VIRT_SAMPLES: ++ ret = pmbus_read_byte_data(client, page, SQ24860_PK_MIN_AVG); ++ if (ret < 0) ++ break; ++ ret = BIT(FIELD_GET(PK_MIN_AVG_AVG_CNT, ret)); ++ break; ++ ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IIN_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ ret = 0; ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_write_word_data(struct i2c_client *client, ++ int page, int reg, u16 value) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ value = max_t(s16, (s16)value, 0); ++ value >>= SQ24860_8B_SHIFT; ++ value = clamp_val(value, 0, 0xff); ++ ret = pmbus_write_word_data(client, page, reg, value); ++ break; ++ ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ value = max_t(s16, (s16)value, SQ24860_IIN_OCF_OFF); ++ value -= SQ24860_IIN_OCF_OFF; ++ value = DIV_ROUND_CLOSEST(((unsigned int)value) * SQ24860_IIN_OCF_DIV, ++ SQ24860_IIN_OCF_NUM); ++ value = clamp_val(value, 0, 0x3f); ++ ret = pmbus_write_byte_data(client, page, SQ24860_VIREF, value); ++ break; ++ ++ case PMBUS_VIRT_SAMPLES: ++ value = clamp_val(value, 1, SQ24860_MAX_SAMPLES); ++ value = ilog2(value); ++ ret = pmbus_update_byte_data(client, page, SQ24860_PK_MIN_AVG, ++ PK_MIN_AVG_AVG_CNT, ++ FIELD_PREP(PK_MIN_AVG_AVG_CNT, value)); ++ break; ++ ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IIN_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ /* ++ * SQ24860 has history resets based on MIN/AVG/PEAK instead of per ++ * sensor type. Exposing this quirk in hwmon is not desirable so ++ * reset MIN, AVG and PEAK together. Even is there effectively only ++ * one reset, which resets everything, expose the 5 entries so ++ * userspace is not required map a sensor type to another to trigger ++ * a reset ++ */ ++ ret = pmbus_update_byte_data(client, 0, SQ24860_PK_MIN_AVG, ++ PK_MIN_AVG_RST_MASK, ++ PK_MIN_AVG_RST_MASK); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_read_byte_data(struct i2c_client *client, ++ int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_WRITE_PROTECT: ++ ret = sq24860_mfr_write_protect_get(client); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_write_byte_data(struct i2c_client *client, ++ int page, int reg, u8 byte) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_WRITE_PROTECT: ++ ret = sq24860_mfr_write_protect_set(client, byte); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) ++static const struct regulator_desc sq24860_reg_desc[] = { ++ PMBUS_REGULATOR_ONE_NODE("vout"), ++}; ++#endif ++ ++static const struct pmbus_driver_info sq24860_base_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .m[PSC_VOLTAGE_IN] = 64, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 0, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .m[PSC_VOLTAGE_OUT] = 64, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 0, ++ .format[PSC_TEMPERATURE] = direct, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 0, ++ /* ++ * Current and power measurements depend on the calibration gain ++ * programmed from the board-specific IMON resistor value. ++ */ ++ .format[PSC_CURRENT_IN] = direct, ++ .m[PSC_CURRENT_IN] = 16, ++ .b[PSC_CURRENT_IN] = 0, ++ .R[PSC_CURRENT_IN] = 0, ++ .format[PSC_POWER] = direct, ++ .m[PSC_POWER] = 2, ++ .b[PSC_POWER] = 0, ++ .R[PSC_POWER] = 0, ++ .func[0] = PMBUS_HAVE_VIN | ++ PMBUS_HAVE_VOUT | ++ PMBUS_HAVE_VMON | ++ PMBUS_HAVE_IIN | ++ PMBUS_HAVE_PIN | ++ PMBUS_HAVE_TEMP | ++ PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_SAMPLES, ++ .read_word_data = sq24860_read_word_data, ++ .write_word_data = sq24860_write_word_data, ++ .read_byte_data = sq24860_read_byte_data, ++ .write_byte_data = sq24860_write_byte_data, ++ ++#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) ++ .reg_desc = sq24860_reg_desc, ++ .num_regulators = ARRAY_SIZE(sq24860_reg_desc), ++#endif ++}; ++ ++static const struct i2c_device_id sq24860_i2c_id[] = { ++ { "sq24860" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, sq24860_i2c_id); ++ ++static const struct of_device_id sq24860_of_match[] = { ++ { .compatible = "silergy,sq24860" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, sq24860_of_match); ++ ++static int sq24860_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct pmbus_driver_info *info; ++ u32 rimon; ++ int ret; ++ ++ if (device_property_read_u32(dev, "silergy,rimon-micro-ohms", &rimon)) ++ rimon = SQ24860_DEFAULT_RIMON; ++ ret = sq24860_write_iin_cal_gain(client, rimon); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to set gain\n"); ++ info = devm_kmemdup(dev, &sq24860_base_info, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static struct i2c_driver sq24860_driver = { ++ .driver = { ++ .name = "sq24860", ++ .of_match_table = sq24860_of_match, ++ }, ++ .probe = sq24860_probe, ++ .id_table = sq24860_i2c_id, ++}; ++module_i2c_driver(sq24860_driver); ++ ++MODULE_AUTHOR("Ziming Zhu "); ++MODULE_DESCRIPTION("PMBUS driver for SQ24860 eFuse"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/vt7505.c b/drivers/hwmon/pmbus/vt7505.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/vt7505.c +@@ -0,0 +1,287 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for Analog Devices MAX16545/MAX16550 and ++ * Volterra VT7505 PMBus controllers. ++ * ++ * Copyright 2026 Hewlett Packard Enterprise Development LP ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmbus.h" ++ ++#define VT7505_MFR_CONFIG 0xd0 ++#define VT7505_CFG_OCP_S_FILT_MASK GENMASK(15, 14) ++ ++#define VT7505_MFR_PEAK_VIN 0xd1 ++#define VT7505_MFR_PEAK_IOUT 0xd2 ++#define VT7505_MFR_PEAK_PIN 0xd3 ++#define VT7505_MFR_PEAK_TEMP 0xd4 ++#define VT7505_MFR_CLEAR_PEAKS 0xd5 ++#define VT7505_MFR_PEAK_VOUT 0xfd ++ ++#define VT7505_RLOAD_DEFAULT 4750 ++/* Largest RLOAD for which coeff * rload / 1000 still fits in s32. */ ++#define VT7505_RLOAD_MAX 283383959U ++ ++struct vt7505_chip_data { ++ int temp_m; ++ int temp_b; ++ bool has_ocp_filter; ++}; ++ ++static const struct vt7505_chip_data max16545_data = { ++ .temp_m = 205, ++ .temp_b = 6545, ++}; ++ ++static const struct vt7505_chip_data max16550_data = { ++ .temp_m = 199, ++ .temp_b = 7046, ++ .has_ocp_filter = true, ++}; ++ ++static const struct vt7505_chip_data vt7505_data = { ++ .temp_m = 205, ++ .temp_b = 6545, ++ .has_ocp_filter = true, ++}; ++ ++static int vt7505_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ switch (reg) { ++ case PMBUS_VIRT_READ_VIN_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_VIN); ++ case PMBUS_VIRT_READ_IOUT_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_IOUT); ++ case PMBUS_VIRT_READ_PIN_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_PIN); ++ case PMBUS_VIRT_READ_TEMP_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_TEMP); ++ case PMBUS_VIRT_READ_VOUT_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_VOUT); ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ return 0; ++ default: ++ return -ENODATA; ++ } ++} ++ ++static int vt7505_write_word_data(struct i2c_client *client, int page, ++ int reg, u16 word) ++{ ++ switch (reg) { ++ /* ++ * A single reset command clears all peak values. CLEAR_PEAKS is a ++ * send-byte command; the device NAKs a word or byte-data write to it. ++ */ ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ return pmbus_write_byte(client, page, ++ VT7505_MFR_CLEAR_PEAKS); ++ default: ++ return -ENODATA; ++ } ++} ++ ++/* ++ * None of these controllers implement the standard PMBus WRITE_PROTECT ++ * (0x10) register, so tell the core not to access it. ++ */ ++static struct pmbus_platform_data vt7505_pdata = { ++ .flags = PMBUS_NO_WRITE_PROTECT, ++}; ++ ++static int vt7505_set_ocp_filter(struct i2c_client *client) ++{ ++ u32 ocp_us; ++ u8 field; ++ int ret; ++ u16 word; ++ ++ if (of_property_read_u32(client->dev.of_node, "adi,ocp-severe-filter-us", ++ &ocp_us)) ++ return 0; ++ ++ switch (ocp_us) { ++ case 0: ++ field = 0; ++ break; ++ case 1: ++ field = 1; ++ break; ++ case 2: ++ field = 2; ++ break; ++ case 10: ++ field = 3; ++ break; ++ default: ++ return dev_err_probe(&client->dev, -EINVAL, ++ "invalid adi,ocp-severe-filter-us value %u\n", ++ ocp_us); ++ } ++ ++ ret = i2c_smbus_read_word_data(client, VT7505_MFR_CONFIG); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "failed to read MFR_CONFIG\n"); ++ ++ word = ret & ~VT7505_CFG_OCP_S_FILT_MASK; ++ word |= FIELD_PREP(VT7505_CFG_OCP_S_FILT_MASK, field); ++ ++ ret = i2c_smbus_write_word_data(client, VT7505_MFR_CONFIG, word); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "failed to write MFR_CONFIG\n"); ++ ++ return 0; ++} ++ ++static void vt7505_set_m(int *m, u32 rload) ++{ ++ u64 val = (u64)*m * rload; ++ ++ /* rload is range-checked in probe, so the result fits in int. */ ++ *m = DIV_ROUND_CLOSEST_ULL(val, 1000); ++} ++ ++static int vt7505_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ const struct vt7505_chip_data *chip; ++ struct pmbus_driver_info *info; ++ u32 rload; ++ int ret; ++ ++ chip = i2c_get_match_data(client); ++ if (!chip) ++ return -ENODEV; ++ ++ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ dev->platform_data = &vt7505_pdata; ++ ++ /* ++ * The m coefficient used in the direct-format current and power ++ * calculations depends on RLOAD, the external current-report resistor ++ * connected between the ILOAD pin and ground. Use the default value if ++ * none is specified. ++ */ ++ if (of_property_read_u32(dev->of_node, "adi,rload-ohms", &rload)) ++ rload = VT7505_RLOAD_DEFAULT; ++ ++ if (!rload || rload > VT7505_RLOAD_MAX) ++ return dev_err_probe(dev, -EINVAL, ++ "adi,rload-ohms must be 1-%u\n", ++ VT7505_RLOAD_MAX); ++ ++ info->pages = 1; ++ info->read_word_data = vt7505_read_word_data; ++ info->write_word_data = vt7505_write_word_data; ++ ++ info->format[PSC_VOLTAGE_IN] = direct; ++ info->format[PSC_VOLTAGE_OUT] = direct; ++ info->format[PSC_CURRENT_IN] = direct; ++ info->format[PSC_CURRENT_OUT] = direct; ++ info->format[PSC_POWER] = direct; ++ info->format[PSC_TEMPERATURE] = direct; ++ ++ /* ++ * Direct data format coefficients from the device datasheet ("PMBus ++ * Equation Parameters"). The current and power m coefficients scale ++ * with RLOAD; vt7505_set_m() applies the 1/1000 factor below, giving ++ * m = 3.824 * RLOAD for current and 0.895 * RLOAD for power. The ++ * temperature coefficients are chip specific (see the chip data). ++ */ ++ info->m[PSC_VOLTAGE_IN] = 7578; ++ info->R[PSC_VOLTAGE_IN] = -2; ++ info->m[PSC_VOLTAGE_OUT] = 7578; ++ info->R[PSC_VOLTAGE_OUT] = -2; ++ info->m[PSC_CURRENT_IN] = 3824; ++ info->b[PSC_CURRENT_IN] = -4300; ++ info->R[PSC_CURRENT_IN] = -3; ++ info->m[PSC_CURRENT_OUT] = 3824; ++ info->b[PSC_CURRENT_OUT] = -4300; ++ info->R[PSC_CURRENT_OUT] = -3; ++ info->m[PSC_POWER] = 895; ++ info->b[PSC_POWER] = -9100; ++ info->R[PSC_POWER] = -2; ++ ++ vt7505_set_m(&info->m[PSC_CURRENT_IN], rload); ++ vt7505_set_m(&info->m[PSC_CURRENT_OUT], rload); ++ vt7505_set_m(&info->m[PSC_POWER], rload); ++ ++ info->m[PSC_TEMPERATURE] = chip->temp_m; ++ info->b[PSC_TEMPERATURE] = chip->temp_b; ++ info->R[PSC_TEMPERATURE] = -2; ++ ++ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_IIN | PMBUS_HAVE_PIN; ++ ++ /* ++ * The severe OCP deglitch filter is programmable on the MAX16550 and ++ * the VT7505, but fixed on the MAX16545. ++ */ ++ if (chip->has_ocp_filter) { ++ ret = vt7505_set_ocp_filter(client); ++ if (ret) ++ return ret; ++ } ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static const struct i2c_device_id vt7505_id[] = { ++ { .name = "max16545", .driver_data = (kernel_ulong_t)&max16545_data }, ++ { .name = "max16550", .driver_data = (kernel_ulong_t)&max16550_data }, ++ { .name = "vt7505", .driver_data = (kernel_ulong_t)&vt7505_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, vt7505_id); ++ ++static const struct of_device_id vt7505_of_match[] = { ++ { .compatible = "adi,max16545", .data = &max16545_data }, ++ { .compatible = "adi,max16550", .data = &max16550_data }, ++ { .compatible = "adi,vt7505", .data = &vt7505_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, vt7505_of_match); ++ ++static struct i2c_driver vt7505_driver = { ++ .driver = { ++ .name = "vt7505", ++ .of_match_table = vt7505_of_match, ++ }, ++ .probe = vt7505_probe, ++ .id_table = vt7505_id, ++}; ++module_i2c_driver(vt7505_driver); ++ ++MODULE_AUTHOR("Georgi Vlaev "); ++MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c +--- a/drivers/hwmon/pmbus/xdpe1a2g7b.c ++++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c +@@ -57,6 +57,13 @@ static int xdpe1a2g7b_identify(struct i2c_client *client, + return 0; + } + ++#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) ++static const struct regulator_desc xdpe1a2g7b_reg_desc[] = { ++ PMBUS_REGULATOR("vout", 0), ++ PMBUS_REGULATOR("vout", 1), ++}; ++#endif ++ + static struct pmbus_driver_info xdpe1a2g7b_info = { + .pages = XDPE1A2G7B_PAGE_NUM, + .identify = xdpe1a2g7b_identify, +@@ -72,6 +79,10 @@ static struct pmbus_driver_info xdpe1a2g7b_info = { + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT, ++#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) ++ .num_regulators = XDPE1A2G7B_PAGE_NUM, ++ .reg_desc = xdpe1a2g7b_reg_desc, ++#endif + }; + + static int xdpe1a2g7b_probe(struct i2c_client *client) +diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c +--- a/drivers/hwmon/pwm-fan.c ++++ b/drivers/hwmon/pwm-fan.c +@@ -634,12 +634,8 @@ static int pwm_fan_probe(struct platform_device *pdev) + if (tach->irq > 0) { + ret = devm_request_irq(dev, tach->irq, pulse_handler, + IRQF_NO_THREAD, pdev->name, tach); +- if (ret) { +- dev_err(dev, +- "Failed to request interrupt: %d\n", +- ret); ++ if (ret) + return ret; +- } + } + + if (!ctx->pulses_per_revolution[i]) { +diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c +--- a/drivers/hwmon/sht15.c ++++ b/drivers/hwmon/sht15.c +@@ -981,10 +981,8 @@ static int sht15_probe(struct platform_device *pdev) + IRQF_TRIGGER_FALLING, + "sht15 data", + data); +- if (ret) { +- dev_err(&pdev->dev, "failed to get irq for data line\n"); ++ if (ret) + goto err_release_reg; +- } + disable_irq_nosync(gpiod_to_irq(data->data)); + ret = sht15_connection_reset(data); + if (ret) +diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c +--- a/drivers/hwmon/sht3x.c ++++ b/drivers/hwmon/sht3x.c +@@ -62,7 +62,7 @@ static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 }; + #define SHT3X_MAX_HUMIDITY 100000 + + enum sht3x_chips { +- sht3x, ++ sht3x = 1, + sts3x, + }; + +@@ -940,8 +940,19 @@ static const struct i2c_device_id sht3x_ids[] = { + + MODULE_DEVICE_TABLE(i2c, sht3x_ids); + ++static const struct of_device_id sht3x_of_match[] = { ++ { .compatible = "sensirion,sht30", .data = (void *)(uintptr_t)sht3x }, ++ { .compatible = "sensirion,sts30", .data = (void *)(uintptr_t)sts3x }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(of, sht3x_of_match); ++ + static struct i2c_driver sht3x_i2c_driver = { +- .driver.name = "sht3x", ++ .driver = { ++ .name = "sht3x", ++ .of_match_table = sht3x_of_match, ++ }, + .probe = sht3x_probe, + .id_table = sht3x_ids, + }; +diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c +--- a/drivers/hwmon/tmp102.c ++++ b/drivers/hwmon/tmp102.c +@@ -396,6 +396,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume + + static const struct i2c_device_id tmp102_id[] = { + { .name = "tmp102" }, ++ { .name = "tmp110" }, ++ { .name = "tmp113" }, + { } + }; + MODULE_DEVICE_TABLE(i2c, tmp102_id); +diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c +--- a/drivers/hwmon/tmp401.c ++++ b/drivers/hwmon/tmp401.c +@@ -695,7 +695,7 @@ static int tmp401_probe(struct i2c_client *client) + info->type = hwmon_chip; + info->config = data->chip_channel_config; + +- data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; ++ data->chip_channel_config[0] = HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL; + + info = &data->temp_info; + info->type = hwmon_temp; +diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/tvs-mpfs.c +@@ -0,0 +1,390 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Author: Lars Randers ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MPFS_TVS_CTRL 0x08 ++#define MPFS_TVS_OUTPUT0 0x24 ++#define MPFS_TVS_OUTPUT1 0x28 ++ ++#define MPFS_TVS_CTRL_TEMP_VALID BIT(19) ++#define MPFS_TVS_CTRL_V2P5_VALID BIT(18) ++#define MPFS_TVS_CTRL_V1P8_VALID BIT(17) ++#define MPFS_TVS_CTRL_V1P05_VALID BIT(16) ++ ++#define MPFS_TVS_CTRL_TEMP_ENABLE BIT(3) ++#define MPFS_TVS_CTRL_V2P5_ENABLE BIT(2) ++#define MPFS_TVS_CTRL_V1P8_ENABLE BIT(1) ++#define MPFS_TVS_CTRL_V1P05_ENABLE BIT(0) ++#define MPFS_TVS_CTRL_ENABLE_ALL GENMASK(3, 0) ++ ++/* ++ * For all of these the value in millivolts is stored in 16 bits, with an upper ++ * sign bit and a lower 3 bits of decimal. These masks discard the sign bit and ++ * decimal places, because if Linux is running these voltages cannot be negative ++ * and so avoid having to convert to two's complement. ++ */ ++#define MPFS_OUTPUT0_V1P8_MASK GENMASK(30, 19) ++#define MPFS_OUTPUT0_V1P05_MASK GENMASK(14, 3) ++#define MPFS_OUTPUT1_V2P5_MASK GENMASK(14, 3) ++ ++/* ++ * The register map claims that the temperature is stored in bits 31:16, but ++ * application note "AN4682: PolarFire FPGA Temperature and Voltage Sensor" ++ * says that 31 is reserved. Temperature is in kelvin, so what's probably a ++ * sign bit has no value anyway. ++ */ ++#define MPFS_OUTPUT1_TEMP_MASK GENMASK(30, 16) ++ ++#define MPFS_TVS_INTERVAL_MASK GENMASK(15, 8) ++#define MPFS_TVS_INTERVAL_OFFSET 8 ++/* The interval register is in increments of 32 us */ ++#define MPFS_TVS_INTERVAL_SCALE 32 ++/* with 254 usable increments of 32 us available, 8 ms is the integer limit */ ++#define MPFS_TVS_INTERVAL_MAX_MS 8 ++ ++/* 273.1875 in 11.4 fixed-point notation */ ++#define MPFS_TVS_K_TO_C 0x1113 ++ ++enum mpfs_tvs_sensors { ++ SENSOR_V1P05 = 0, ++ SENSOR_V1P8, ++ SENSOR_V2P5, ++}; ++ ++static const char * const mpfs_tvs_voltage_labels[] = { "1P05", "1P8", "2P5" }; ++ ++struct mpfs_tvs { ++ struct regmap *regmap; ++}; ++ ++static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr, ++ int channel, long *val) ++{ ++ u32 tmp, control; ++ ++ if (attr != hwmon_in_input && attr != hwmon_in_enable) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &control); ++ ++ switch (channel) { ++ case SENSOR_V2P5: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V2P5_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V2P5_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT1_V2P5_MASK, tmp); ++ break; ++ case SENSOR_V1P8: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V1P8_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V1P8_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT0_V1P8_MASK, tmp); ++ break; ++ case SENSOR_V1P05: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V1P05_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V1P05_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT0_V1P05_MASK, tmp); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_voltage_write(struct mpfs_tvs *data, u32 attr, ++ int channel, long val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_in_enable) ++ return -EOPNOTSUPP; ++ ++ if (val > 1 || val < 0) ++ return -EINVAL; ++ ++ switch (channel) { ++ case SENSOR_V2P5: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V2P5_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V2P5_ENABLE, tmp); ++ break; ++ case SENSOR_V1P8: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P8_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V1P8_ENABLE, tmp); ++ break; ++ case SENSOR_V1P05: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P05_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V1P05_ENABLE, tmp); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr, long *val) ++{ ++ u32 tmp, control; ++ ++ if (attr != hwmon_temp_input && attr != hwmon_temp_enable) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &control); ++ ++ if (attr == hwmon_temp_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_TEMP_ENABLE, control); ++ return 0; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_TEMP_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT1_TEMP_MASK, tmp); ++ *val -= MPFS_TVS_K_TO_C; ++ *val = (1000 * *val) >> 4; /* fixed point (11.4) to millidegrees */ ++ ++ return 0; ++} ++ ++static int mpfs_tvs_temp_write(struct mpfs_tvs *data, u32 attr, long val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_temp_enable) ++ return -EOPNOTSUPP; ++ ++ if (val > 1 || val < 0) ++ return -EINVAL; ++ ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_TEMP_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_TEMP_ENABLE, tmp); ++ ++ return 0; ++} ++ ++static int mpfs_tvs_interval_read(struct mpfs_tvs *data, u32 attr, long *val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_chip_update_interval) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &tmp); ++ *val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp); ++ *val *= MPFS_TVS_INTERVAL_SCALE; ++ *val = roundup(*val, 1000); ++ *val /= 1000; ++ ++ return 0; ++} ++ ++static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val) ++{ ++ long temp = val; ++ ++ if (attr != hwmon_chip_update_interval) ++ return -EOPNOTSUPP; ++ ++ temp = clamp(temp, 0, MPFS_TVS_INTERVAL_MAX_MS); ++ ++ temp *= 1000; ++ temp /= MPFS_TVS_INTERVAL_SCALE; ++ ++ temp <<= MPFS_TVS_INTERVAL_OFFSET; ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_INTERVAL_MASK, temp); ++ ++ return 0; ++} ++ ++static umode_t mpfs_tvs_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ if (type == hwmon_chip && attr == hwmon_chip_update_interval) ++ return 0644; ++ ++ if (type == hwmon_temp) { ++ switch (attr) { ++ case hwmon_temp_enable: ++ return 0644; ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ return 0444; ++ default: ++ return 0; ++ } ++ } ++ ++ if (type == hwmon_in) { ++ switch (attr) { ++ case hwmon_in_enable: ++ return 0644; ++ case hwmon_in_input: ++ case hwmon_in_label: ++ return 0444; ++ default: ++ return 0; ++ } ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct mpfs_tvs *data = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_temp: ++ return mpfs_tvs_temp_read(data, attr, val); ++ case hwmon_in: ++ return mpfs_tvs_voltage_read(data, attr, channel, val); ++ case hwmon_chip: ++ return mpfs_tvs_interval_read(data, attr, val); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mpfs_tvs_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ struct mpfs_tvs *data = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_temp: ++ return mpfs_tvs_temp_write(data, attr, val); ++ case hwmon_in: ++ return mpfs_tvs_voltage_write(data, attr, channel, val); ++ case hwmon_chip: ++ return mpfs_tvs_interval_write(data, attr, val); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mpfs_tvs_read_labels(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, ++ const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ *str = "Die Temp"; ++ return 0; ++ case hwmon_in: ++ *str = mpfs_tvs_voltage_labels[channel]; ++ return 0; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static const struct hwmon_ops mpfs_tvs_ops = { ++ .is_visible = mpfs_tvs_is_visible, ++ .read_string = mpfs_tvs_read_labels, ++ .read = mpfs_tvs_read, ++ .write = mpfs_tvs_write, ++}; ++ ++static const struct hwmon_channel_info *mpfs_tvs_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_ENABLE), ++ HWMON_CHANNEL_INFO(in, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE), ++ NULL ++}; ++ ++static const struct hwmon_chip_info mpfs_tvs_chip_info = { ++ .ops = &mpfs_tvs_ops, ++ .info = mpfs_tvs_info, ++}; ++ ++static int mpfs_tvs_probe(struct platform_device *pdev) ++{ ++ struct device *hwmon_dev; ++ struct mpfs_tvs *data; ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->regmap = device_node_to_regmap(pdev->dev.parent->of_node); ++ if (IS_ERR(data->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(data->regmap), ++ "Failed to find syscon regmap\n"); ++ ++ /* ++ * It's an MMIO regmap with no resources, there's nothing that can fail ++ * and return an error ++ */ ++ regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL); ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "mpfs_tvs", ++ data, ++ &mpfs_tvs_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev), ++ "hwmon device registration failed.\n"); ++ ++ return 0; ++} ++ ++static struct platform_driver mpfs_tvs_driver = { ++ .probe = mpfs_tvs_probe, ++ .driver = { ++ .name = "mpfs-tvs", ++ }, ++}; ++module_platform_driver(mpfs_tvs_driver); ++ ++MODULE_AUTHOR("Lars Randers "); ++MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c +--- a/drivers/hwmon/xgene-hwmon.c ++++ b/drivers/hwmon/xgene-hwmon.c +@@ -133,10 +133,6 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) + init_completion(&ctx->rd_complete); + ctx->resp_pending = true; + +- /* Write signature for subspace */ +- WRITE_ONCE(generic_comm_base->signature, +- cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx)); +- + /* Write to the shared command region */ + WRITE_ONCE(generic_comm_base->command, + cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR)); +diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c +--- a/drivers/hwmon/yogafan.c ++++ b/drivers/hwmon/yogafan.c +@@ -53,6 +53,7 @@ struct yoga_fan_data { + }; + + /* Specific configurations mapped via DMI */ ++ + static const struct yogafan_config yoga_8bit_fans_cfg = { + .multiplier = 100, + .fan_count = 1, +@@ -71,6 +72,24 @@ static const struct yogafan_config legion_16bit_dual_cfg = { + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } + }; + ++static const struct yogafan_config loq_15iax9_8bit_dual_cfg = { ++ .multiplier = 100, ++ .fan_count = 2, ++ .paths = { "\\_SB.PC00.LPCB.EC0.FA1S", "\\_SB.PC00.LPCB.EC0.FA2S" } ++}; ++ ++static const struct yogafan_config xiaoxin_8bit_dual_cfg = { ++ .multiplier = 100, ++ .fan_count = 2, ++ .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } ++}; ++ ++static const struct yogafan_config yoga_pro_7_14iah10_cfg = { ++ .multiplier = 100, ++ .fan_count = 1, ++ .paths = { "\\_SB.PC00.LPCB.EC0.FANS", NULL } ++}; ++ + static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) + { + ktime_t now = ktime_get_boottime(); +@@ -170,6 +189,54 @@ static const struct hwmon_chip_info yoga_fan_chip_info = { + }; + + static const struct dmi_system_id yogafan_quirks[] = { ++ { ++ .ident = "Lenovo LOQ 15IAX9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"), ++ }, ++ .driver_data = (void *)&loq_15iax9_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo XiaoXin Pro 13ARE 2020", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "XiaoXinPro-13ARE 2020"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo IdeaPad 3 15ALC6", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad 3 15ALC6"), ++ }, ++ .driver_data = (void *)&ideapad_8bit_fan0_cfg, ++ }, ++ { ++ .ident = "Lenovo Legion Pro 7 16AFR10H", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion Pro 7 16AFR10H"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo Yoga Pro 7 14IAH10", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga Pro 7 14IAH10"), ++ }, ++ .driver_data = (void *)&yoga_pro_7_14iah10_cfg, ++ }, ++ { ++ .ident = "Lenovo Yoga 7 16ARP8", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 7 16ARP8"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, + { + .ident = "Lenovo Yoga", + .matches = { +diff --git a/include/linux/hwmon-sysfs.h b/include/linux/hwmon-sysfs.h +--- a/include/linux/hwmon-sysfs.h ++++ b/include/linux/hwmon-sysfs.h +@@ -15,10 +15,10 @@ struct sensor_device_attribute{ + int index; + }; + #define to_sensor_dev_attr(_dev_attr) \ +- container_of(_dev_attr, struct sensor_device_attribute, dev_attr) ++ container_of_const(_dev_attr, struct sensor_device_attribute, dev_attr) + +-#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ +- { .dev_attr = __ATTR(_name, _mode, _show, _store), \ ++#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ ++ { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ + .index = _index } + + #define SENSOR_ATTR_RO(_name, _func, _index) \ +@@ -49,11 +49,11 @@ struct sensor_device_attribute_2 { + u8 nr; + }; + #define to_sensor_dev_attr_2(_dev_attr) \ +- container_of(_dev_attr, struct sensor_device_attribute_2, dev_attr) ++ container_of_const(_dev_attr, struct sensor_device_attribute_2, dev_attr) + +-#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ +- { .dev_attr = __ATTR(_name, _mode, _show, _store), \ +- .index = _index, \ ++#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ ++ { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ ++ .index = _index, \ + .nr = _nr } + + #define SENSOR_ATTR_2_RO(_name, _func, _nr, _index) \ diff --git a/pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..9a153ff5fc0b27e4ae793f85e74a076864fef14f GIT binary patch literal 594 zcmV-Y0IjgUq);-I3FT2+qa67?o-_Jmf&~Jg^A*gW^ZdaBsa*Qq2;Wql>bF z<53V5Wem;Nw&kL^Rdit0jE1P?B(JbT+*cX;1piz9mi(6awfIz2UwWh_i{PJ<(xzX3 z!wHfV_h5EWNT)dSBCZ&9+@LmQW|_1J$>q85j9(n?0Cy$8)CsXI|4k#w_Pn+RNb8N5 zNca<2TO9OBO(jWVuVWLVQ4(mr#!pJaZ`$pagUQLV0Hi~1mKC&16jIiycB*lDk;%w0 z$kZ`{oe9?lQs|P8|6hR5A7Hx@dQfnhIN47 zJ3bmnBjv29Dpw%vVv0P?#W`va$weiX)X$?;m!_5LFgyU(I-kG-@$d%-^9m}M!Nztype = hwmon_pwm; + applesmc_info_pwm->config = applesmc_pwm_config; + +- applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); ++ applesmc_info_arr = kzalloc_objs(*applesmc_info_arr, 4); + if (!applesmc_info_arr) { + ret = -ENOMEM; + goto out_info; +@@ -1509,7 +1509,7 @@ static int __init applesmc_init(void) + applesmc_info_arr[2] = applesmc_info_pwm; + applesmc_info_arr[3] = NULL; + +- applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); ++ applesmc_chip = kzalloc_obj(*applesmc_chip); + if (!applesmc_chip) { + ret = -ENOMEM; + goto out_info; +@@ -1519,10 +1519,9 @@ static int __init applesmc_init(void) + applesmc_chip->info = applesmc_info_arr; + + /* Create non-standard fanX_safe attributes group */ +- fan_safe_attrs = kcalloc(smcreg.fan_count, +- sizeof(*fan_safe_attrs), GFP_KERNEL); +- fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, +- sizeof(*fan_safe_attr_list), GFP_KERNEL); ++ fan_safe_attrs = kzalloc_objs(*fan_safe_attrs, smcreg.fan_count); ++ fan_safe_attr_list = kzalloc_objs(*fan_safe_attr_list, ++ smcreg.fan_count + 1); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; +diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c +--- a/drivers/hwmon/asus_rog_ryujin.c ++++ b/drivers/hwmon/asus_rog_ryujin.c +@@ -184,6 +184,7 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd + static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) + { ++ unsigned long flags; + int ret; + + /* +@@ -191,9 +192,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ +- spin_lock_bh(&priv->status_report_request_lock); ++ spin_lock_irqsave(&priv->status_report_request_lock, flags); + reinit_completion(status_completion); +- spin_unlock_bh(&priv->status_report_request_lock); ++ spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); +@@ -421,15 +422,18 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + int size) + { + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); ++ unsigned long flags; + + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) + return 0; + ++ spin_lock_irqsave(&priv->status_report_request_lock, flags); ++ + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) +- return 0; ++ goto unlock; + + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + +@@ -443,7 +447,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) +- return 0; ++ goto unlock; + + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); +@@ -455,7 +459,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) +- return 0; ++ goto unlock; + + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { +@@ -474,7 +478,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; +- return 0; ++ goto unlock; + } + read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); +@@ -484,7 +488,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) +- return 0; ++ goto unlock; + + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { +@@ -503,7 +507,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_controller_duty; +- return 0; ++ goto unlock; + } + read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; +@@ -512,6 +516,8 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_duty_received); + } + ++unlock: ++ spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return 0; + } + +diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c +--- a/drivers/hwmon/hwmon.c ++++ b/drivers/hwmon/hwmon.c +@@ -318,6 +318,11 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) + return 1; + } + ++static bool is_hwmon_device(struct device *dev) ++{ ++ return dev->class == &hwmon_class; ++} ++ + #if IS_REACHABLE(CONFIG_I2C) + + /* +@@ -338,7 +343,7 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) + + static int hwmon_match_device(struct device *dev, const void *data) + { +- return dev->class == &hwmon_class; ++ return is_hwmon_device(dev); + } + + static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, +@@ -781,6 +786,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, + const char *template; + int base; + ++ if (WARN(!is_hwmon_device(dev), "%s is not a hardware monitoring device\n", ++ dev_name(dev))) ++ return -EINVAL; + if (type >= ARRAY_SIZE(__templates)) + return -EINVAL; + if (attr >= __templates_size[type]) diff --git a/pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..11778c986971c9ffe668c5b6d80e222aa22f1ce9 GIT binary patch literal 594 zcmV-Y0a-&}q`RV04wD;4iKTcKn+Xzzk2G4$}sM6!9oN(_`>@2WrT|pQC!7 zbr+f9KFLz^OIFJ8a`26KhAtCaN5gXtO?g#D+1FfIm1H7NJenf6*!pe1H(=E@{vY%Z zVTY~`YPh3@G2YJ|evQAVEe?nMFODpyMiziGsH%Ejk`OjRhxD$sw(ZobBRWMrd=T#I zJO(IBO4~5DZ6%Y{v6D7KW6nRf*$y$hGX!jSccd_offset = 0x1F0; + k10temp_get_ccd_support(data, 16); + break; diff --git a/pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..db34c4b49aa6bef62e80372a899c81c4e1bc3488 GIT binary patch literal 594 zcmV-Y0PZp}4zufCEmmlNYO(@R(`9n!|uDC@TCnxDk4!NvW{dp96?k488AQJK&y(u0e9{9I*1`*uWy$15{v9HLJi2}2;M!iD# zb$zWQQtoGU?4m;*YNwKah;G(SA8uwH*q^0_enQKTJSFLt^}cvIEUKKZ0uYZ(O*c*O zwq9zw_w2W>fzizy9T*GhjZ0 z>~d<#$u~T=2QE6u-j}UUwaJw{+uNNUHo)$q_BeejTN)8K0x7zzA>67IQP}Ff$pAxj zb_(Cz;xx6>QoUX~zJo8x>%$Y}-gV@Qh;*8pO?#I$;T>O4p2E4U^vv%|sQU-bTD6V310lG$~di1GSDseOr}w82lV38kUM#b4etZkvMvrH@vBxBY;=% gWVMZ#ez&(ogyq3%w3x`!KEyS8wnddG*Qlf`$%y$OvH$=8 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch b/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch new file mode 100644 index 0000000..da85b37 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch @@ -0,0 +1,23 @@ +diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c +--- a/drivers/acpi/power.c ++++ b/drivers/acpi/power.c +@@ -1121,6 +1121,19 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "ZERO"), + } + ++ }, ++ { ++ /* ++ * Acer Swift 3 SF314-56G with NVIDIA MX250 suffers from the ++ * same ACPI power resource regression as the Thunderobot ZERO. ++ * The power resource controlling the dGPU is incorrectly turned off ++ * during initialization, causing the GPU to fall off the bus (D3cold). ++ */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Acer"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-56G"), ++ } ++ + }, + {} + }; diff --git a/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig b/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..8744164ac3a4d90610b19b46f85c3a5bc00cda88 GIT binary patch literal 594 zcmV-Y0ox9x6Eg?(6uhdi<11!X(M^}!(cvhIb=wosq3<7tWj_u2v;0oG0 zI)bvGnH=Hb8m;Rm>{cB6y(g>ECW4vtwy;VoXXtrjFhXa_ujr-J{)#p}DM2E2_1X`b zKj$N&_&!M`5207L(ORLp2%I$F0a~cDX2KohLC!fasE9fmx(Bm8hTVV3W1Pg;ktfw>K?=hcP?|mWE7R}J%>GEQRF}^mp-~rEK4)M0fH+est>N4wDRV6IdBio%8 z?Dz{q$g zcSJvg$(aJU0%0v$$kB}-G&z72v8Tc19%Fq2X-K@bjILVR)-TxBUe(e^1bwmfK(`$u z;*<*#42^D=T)A{Direpfg-Bv%Q+J2xWa=aNR1R~*FWFG)Ua2cMasv9*2RdyF;l_-{ z3oRWxRe$s&iJvwQu3=M7B{noJ(uZn{P6Ag?Sn!L<%IH{Ido*lA2*W6B`-Ib|&UOzu gxY%}?=-wJ~lQ=E(cZ99`LfgSE;~A8JbhJ$K>MBMTu>b%7 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch b/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch new file mode 100644 index 0000000..c6018e3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch @@ -0,0 +1,8290 @@ +diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h +--- a/arch/x86/include/uapi/asm/amd_hsmp.h ++++ b/arch/x86/include/uapi/asm/amd_hsmp.h +@@ -53,9 +53,21 @@ enum hsmp_message_ids { + HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */ + HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */ + HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */ ++ HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */ ++ HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */ + HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */ + HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */ + HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */ ++ HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */ ++ HSMP_READ_CCD_POWER, /* 34h Get average CCD power */ ++ HSMP_READ_TDELTA, /* 35h Get thermal behaviour */ ++ HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */ ++ HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */ ++ HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */ ++ HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */ ++ HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */ ++ HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */ ++ HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */ + HSMP_MSG_ID_MAX, + }; + +@@ -170,16 +182,27 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {0, 1, HSMP_GET}, + + /* +- * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0 +- * input: args[0] = min link width[15:8] + max link width[7:0] ++ * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) + ++ * min link width[15:8] + max link width[7:0] ++ * Link width encoding: 0 = x4, 1 = x8, 2 = x16. ++ * On SET, max must be >= min. On GET, [15:0] are reserved. ++ * output: args[0] = reserved[31:16] + min link width[15:8] + ++ * max link width[7:0] + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* +- * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0 +- * input: args[0] = df pstate[7:0] ++ * HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set APB_DISABLE / get APB state[31] ++ * (0 = set & lock DF P-state, 1 = get) + ++ * reserved[30:8] + ++ * DF P-state[7:0] (0..2; reserved on GET) ++ * output: args[0] = reserved[31:9] + ++ * APB state[8] (1 = disabled, 0 = enabled) + ++ * locked DF P-state[7:0] if [8] = 1, else reserved + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */ + {0, 0, HSMP_SET}, +@@ -305,16 +328,32 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {1, 1, HSMP_SET}, + + /* +- * HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0 +- * input: args[0] = power efficiency mode[2:0] ++ * HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection), ++ * num_args = 1, response_sz = 1 ++ * input: args[0] = set/get policy[31] (0 = set, 1 = get) + ++ * high util point[30:24] + ++ * low util point[23:17] + ++ * PPT limit[16:5] + ++ * reserved[4:3] + mode selection[2:0] ++ * [30:5] are valid only when [2:0] is a balanced core mode ++ * (4 or 5). [2:0] is reserved when getting (bit[31] = 1). ++ * output: args[0] same layout, [31] reserved, [2:0] = arbitrated ++ * current efficiency mode. + */ + {1, 1, HSMP_SET_GET}, + + /* +- * HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0 +- * input: args[0] = min df pstate[15:8] + max df pstate[7:0] ++ * HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) + ++ * reserved[30:16] + ++ * min DF P-state[15:8] + max DF P-state[7:0] ++ * DF P-state encoding: 0 = DFP0 (high performance), ++ * 1 = DFP1, 2 = DFP2 (low performance). ++ * [15:0] are reserved when getting (args[0] bit[31] = 1). ++ * output: args[0] = reserved[31:16] + min DF P-state[15:8] + ++ * max DF P-state[7:0] + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* + * HSMP_GET_METRIC_TABLE_VER, num_args = 0, response_sz = 1 +@@ -328,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {0, 0, HSMP_GET}, + + /* +- * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2 ++ * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3 + * output: args[0] = lower 32 bits of the address + * output: args[1] = upper 32 bits of the address ++ * output: args[2] = DRAM region size in bytes + */ +- {0, 2, HSMP_GET}, ++ {0, 3, HSMP_GET}, + + /* + * HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0 +@@ -355,9 +395,31 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + */ + {1, 1, HSMP_SET_GET}, + +- /* RESERVED(0x29-0x2f) */ +- {0, 0, HSMP_RSVD}, +- {0, 0, HSMP_RSVD}, ++ /* ++ * HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) + ++ * reserved[30:1] + ++ * enable PC6[0] (0 = disable, 1 = enable; ++ * reserved on GET) ++ * output: args[0] = reserved[31:1] + current PC6 control[0] ++ * (last value configured via HSMP or APML) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1 ++ * Configures CC6 enable for all cores; changing the setting does ++ * not by itself transition cores in or out of CC6. ++ * input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) + ++ * reserved[30:1] + ++ * enable CC6[0] (0 = disable, 1 = enable; ++ * reserved on GET) ++ * output: args[0] = reserved[31:1] + current CC6 control[0] ++ * (last value configured via HSMP or APML) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* RESERVED(0x2B-0x2F) */ + {0, 0, HSMP_RSVD}, + {0, 0, HSMP_RSVD}, + {0, 0, HSMP_RSVD}, +@@ -385,6 +447,89 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + */ + {0, 2, HSMP_GET}, + ++ /* ++ * HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1 ++ * input: args[0] = reg space[23] + reg offset[22:12] + ++ * device LID[11:8] + DIMM address[7:0] ++ * output: args[0] = read data byte[3:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1 ++ * input: args[0] = apic id of core[15:0] ++ * output: args[0] = CCD power(mWatts)[31:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_READ_TDELTA, num_args = 0, response_sz = 1 ++ * output: args[0] = thermal behaviour[31:0] ++ */ ++ {0, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1 ++ * input: args[0] = SVI3 rail index[3:1] + read temperature[0] ++ * output: args[0] = SVI3 rail index[30:28] + ++ * rail temperature in degree C[27:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3 ++ * input: args[0] = HSMP command mask[0] ++ * output: status of HSMP command = args[0], args[1], args[2] ++ */ ++ {1, 3, HSMP_GET}, ++ ++ /* ++ * HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1 ++ * input: args[0] = op[31:30] + reserved[29:28] + ++ * apic id[27:16] + floor frequency MHz[15:0] ++ * op encoding: 00 = set per-core floor, ++ * 01 = set all-cores floor (apic id reserved), ++ * 10 = get per-core floor, ++ * 11 = get per-core effective floor. ++ * Floor frequency field is reserved on GET (bit[31] = 1). ++ * output: args[0] = floor frequency MHz[15:0] ++ * (effective for op 11, configured for op 10; ++ * reserved on SET) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0 ++ * input: args[0] = write data[31:24] + reg space[23] + ++ * reg offset[22:12] + device LID[11:8] + ++ * DIMM address[7:0] ++ */ ++ {1, 0, HSMP_SET}, ++ ++ /* ++ * HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1 ++ * input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) + ++ * SDPS limit[30:0] ++ * output: args[0] = SDPS limit[30:0] ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1 ++ * input: args[0] = op[31:30] + priority sel[27:26] + ++ * priority val[21:20] + input[19:0] ++ * output: args[0] = supported priorities or priority val[1:0] ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2 ++ * input: args[0] = op[31] + sub-op[30:29] + params[28:0] ++ * output: args[0] = discovery bits or floating/global memory BW (Gbps) ++ * output: args[1] = reserved or config (drop adj, sampling delay, ++ * hysteresis) ++ */ ++ {1, 2, HSMP_SET_GET}, + }; + + /* Metrics table (supported only with proto version 6) */ +@@ -467,6 +612,39 @@ struct hsmp_metric_table { + __u32 gfxclk_frequency[8]; + }; + ++/** ++ * struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL ++ * @buf: Input. Userspace pointer (encoded as __u64 to keep the layout ++ * stable between 32-bit and 64-bit callers) to the destination ++ * buffer that receives the metric table. ++ * @size: Input. Size in bytes of the buffer pointed to by @buf, and the ++ * number of bytes copied out on success. Must be non-zero and no ++ * larger than the metric table size firmware reports for this ++ * socket; a larger value is rejected with -EINVAL rather than ++ * short-written. A smaller value returns the leading @size bytes ++ * of the snapshot. The kernel does not write this field back. ++ * @sock_ind: Input. Socket index from which the metric table is read. ++ * @reserved: Reserved for future use. Callers should set this to zero; ++ * future kernels may begin interpreting the field, so passing ++ * a non-zero value today is not forwards compatible. ++ * ++ * Placing @buf first lets all fields fall on their natural alignment under ++ * the surrounding #pragma pack(4), so the struct is a tight 16 bytes with ++ * the same wire layout on 32-bit and 64-bit userspace. ++ * ++ * The metric table layout depends on the HSMP protocol version reported by ++ * firmware, which userspace can read from the protocol_version sysfs ++ * attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on ++ * that version pass sizeof(struct hsmp_metric_table). Later version metrics ++ * table layout is documented in the Public PPR. ++ */ ++struct hsmp_telemetry_data { ++ __u64 buf; ++ __u32 size; ++ __u16 sock_ind; ++ __u16 reserved; ++}; ++ + /* Reset to default packing */ + #pragma pack() + +@@ -474,4 +652,16 @@ struct hsmp_metric_table { + #define HSMP_BASE_IOCTL_NR 0xF8 + #define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message) + ++/* ++ * Fetch the firmware metric (telemetry) table for a given socket via the ++ * HSMP character device. This avoids the PAGE_SIZE limitation of the ++ * sysfs binary attribute path for tables larger than one page (such as the ++ * ~13 KB table used by HSMP protocol version 7). ++ * ++ * The direction is _IOW because the kernel only reads the request struct; ++ * the table itself is written to the buffer that @buf points at. ++ */ ++#define HSMP_IOCTL_GET_TELEMETRY_DATA \ ++ _IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data) ++ + #endif /*_ASM_X86_AMD_HSMP_H_*/ +diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c +--- a/arch/x86/platform/olpc/olpc-xo15-sci.c ++++ b/arch/x86/platform/olpc/olpc-xo15-sci.c +@@ -18,8 +18,6 @@ + + #define DRV_NAME "olpc-xo15-sci" + #define PFX DRV_NAME ": " +-#define XO15_SCI_CLASS DRV_NAME +-#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI" + + static unsigned long xo15_sci_gpe; + static bool lid_wake_on_close; +@@ -148,9 +146,6 @@ static int xo15_sci_probe(struct platform_device *pdev) + if (!device) + return -ENODEV; + +- strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME); +- strscpy(acpi_device_class(device), XO15_SCI_CLASS); +- + /* Get GPE bit assignment (EC events). */ + status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp); + if (ACPI_FAILURE(status)) +diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c +--- a/drivers/char/sonypi.c ++++ b/drivers/char/sonypi.c +@@ -1125,8 +1125,6 @@ static int sonypi_acpi_probe(struct platform_device *pdev) + return -ENODEV; + + sonypi_acpi_device = device; +- strscpy(acpi_device_name(device), "Sony laptop hotkeys"); +- strscpy(acpi_device_class(device), "sony/hotkey"); + return 0; + } + +diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c ++++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +@@ -7,6 +7,7 @@ + * + * Author: Basavaraj Natikar + */ ++#include + #include + #include + #include +@@ -102,20 +103,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) + *platform_type = mode.op_mode.devicemode; + + if (mode.op_mode.ontablestate == 1) { +- *laptop_placement = ON_TABLE; ++ *laptop_placement = AMD_PMF_ON_TABLE; + } else if (mode.op_mode.ontablestate == 2) { +- *laptop_placement = ON_LAP_MOTION; ++ *laptop_placement = AMD_PMF_ON_LAP_MOTION; + } else if (mode.op_mode.inbagstate == 1) { +- *laptop_placement = IN_BAG; ++ *laptop_placement = AMD_PMF_IN_BAG; + } else if (mode.op_mode.outbagstate == 1) { +- *laptop_placement = OUT_OF_BAG; ++ *laptop_placement = AMD_PMF_OUT_OF_BAG; + } else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 || + mode.op_mode.outbagstate == 0) { +- *laptop_placement = LP_UNKNOWN; ++ *laptop_placement = AMD_PMF_LP_UNKNOWN; + pr_warn_once("Unknown laptop placement\n"); + } else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 || + mode.op_mode.outbagstate == 3) { +- *laptop_placement = LP_UNDEFINED; ++ *laptop_placement = AMD_PMF_LP_UNDEFINED; + pr_warn_once("Undefined laptop placement\n"); + } + +diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c +--- a/drivers/platform/surface/surface_aggregator_hub.c ++++ b/drivers/platform/surface/surface_aggregator_hub.c +@@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = { + /* -- Driver registration. -------------------------------------------------- */ + + static const struct ssam_device_id ssam_hub_match[] = { +- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, +- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, ++ { ++ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), ++ .driver_data = (unsigned long)&kip_hub, ++ }, { ++ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), ++ .driver_data = (unsigned long)&base_hub, ++ }, + { } + }; + MODULE_DEVICE_TABLE(ssam, ssam_hub_match); +diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c +--- a/drivers/platform/surface/surface_aggregator_tabletsw.c ++++ b/drivers/platform/surface/surface_aggregator_tabletsw.c +@@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { + /* -- Driver registration. -------------------------------------------------- */ + + static const struct ssam_device_id ssam_tablet_sw_match[] = { +- { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, +- { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, ++ { ++ SSAM_SDEV(KIP, SAM, 0x00, 0x01), ++ .driver_data = (unsigned long)&ssam_kip_sw_desc, ++ }, { ++ SSAM_SDEV(POS, SAM, 0x00, 0x01), ++ .driver_data = (unsigned long)&ssam_pos_sw_desc, ++ }, + { }, + }; + MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -20,7 +20,6 @@ + #define SURFACE_PRO3_BUTTON_HID "MSHW0028" + #define SURFACE_PRO4_BUTTON_HID "MSHW0040" + #define SURFACE_BUTTON_OBJ_NAME "VGBI" +-#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" + + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -212,11 +211,10 @@ static int surface_button_probe(struct platform_device *pdev) + goto err_free_button; + } + +- strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); + snprintf(button->phys, sizeof(button->phys), "%s/buttons", + acpi_device_hid(device)); + +- input->name = acpi_device_name(device); ++ input->name = "Surface Pro 3/4 Buttons"; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; +@@ -239,8 +237,8 @@ static int surface_button_probe(struct platform_device *pdev) + goto err_free_button; + } + +- dev_info(&pdev->dev, "%s [%s]\n", acpi_device_name(device), +- acpi_device_bid(device)); ++ dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device)); ++ + return 0; + + err_free_input: +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -802,7 +802,6 @@ config LG_LAPTOP + tristate "LG Laptop Extras" + depends on ACPI + depends on ACPI_BATTERY +- depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + select NEW_LEDS +diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig +--- a/drivers/platform/x86/amd/Kconfig ++++ b/drivers/platform/x86/amd/Kconfig +@@ -34,6 +34,17 @@ config AMD_WBRF + This mechanism will only be activated on platforms that advertise a + need for it. + ++config AMD_HALO_LED ++ tristate "AMD Halo Box RGB LED Driver" ++ depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR ++ help ++ This driver provides RGB LED control for AMD Halo Box devices ++ through the LED multicolor subsystem. The Halo Box light bar can ++ be controlled via sysfs to display any RGB color combination. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called amd_halo_led. ++ + config AMD_ISP_PLATFORM + tristate "AMD ISP4 platform driver" + depends on I2C && X86_64 && ACPI +diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile +--- a/drivers/platform/x86/amd/Makefile ++++ b/drivers/platform/x86/amd/Makefile +@@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/ + obj-$(CONFIG_AMD_HSMP) += hsmp/ + obj-$(CONFIG_AMD_PMF) += pmf/ + obj-$(CONFIG_AMD_WBRF) += wbrf.o ++obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o + obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o + obj-$(CONFIG_AMD_HFI) += hfi/ +diff --git a/drivers/platform/x86/amd/amd_halo_led.c b/drivers/platform/x86/amd/amd_halo_led.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/amd_halo_led.c +@@ -0,0 +1,315 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * AMD Halo Box RGB LED Driver ++ * ++ * Copyright (C) 2026 Advanced Micro Devices, Inc. ++ * ++ * This driver provides RGB LED control for AMD Halo Box devices through ++ * the LED multicolor subsystem. The Halo Box light bar can be controlled ++ * via sysfs to display any RGB color combination. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86" ++ ++/* WMI method IDs from MOF */ ++enum { ++ AMD_HALO_WMI_TURN_OFF = 0x04, ++ AMD_HALO_WMI_RGB = 0x07, ++}; ++ ++/* Arg0 of the AMD_HALO_WMI_RGB Method */ ++enum { ++ AMD_HALO_RGB_CMD_GET = 0x0, ++ AMD_HALO_RGB_CMD_SET = 0x1, ++}; ++ ++/* Status codes from spec */ ++#define AMD_HALO_STATUS_SUCCESS 0x0000 ++#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD ++ ++/* Brightness uses 0-100 range */ ++#define AMD_HALO_MAX_HW_BRIGHTNESS 100 ++ ++/** ++ * struct amd_halo_led_data - Driver private data ++ * @wdev: WMI device pointer ++ * @led_mc: LED multicolor class device ++ * @subled_info: RGB channel information ++ * @lock: Mutex to protect WMI calls ++ */ ++struct amd_halo_led_data { ++ struct wmi_device *wdev; ++ struct led_classdev_mc led_mc; ++ struct mc_subled subled_info[3]; ++ struct mutex lock; ++}; ++ ++struct amd_halo_wmi_args { ++ __le32 arg0; ++ __le32 arg1; ++}; ++ ++struct amd_halo_wmi_args_rgb { ++ __le32 cmd; ++ __le32 red; ++ __le32 green; ++ __le32 blue; ++}; ++ ++struct amd_halo_wmi_output_rgb { ++ __le16 status; ++ u8 red; ++ u8 green; ++ u8 blue; ++} __packed; ++ ++static inline int wmi_status_to_err(u16 status) ++{ ++ switch (status) { ++ case AMD_HALO_STATUS_SUCCESS: ++ return 0; ++ case AMD_HALO_STATUS_INVALID_PARAM: ++ return -EINVAL; ++ default: ++ return -EIO; ++ } ++} ++ ++static int __amd_halo_wmi_call(struct wmi_device *wdev, ++ u32 method_id, void *data, size_t length) ++{ ++ struct wmi_buffer input = { ++ .length = length, ++ .data = data, ++ }; ++ struct wmi_buffer output = { }; ++ int ret; ++ ++ /* Return buffer per spec: Bytes[0:1] = Status (little-endian) */ ++ ret = wmidev_invoke_method(wdev, 0, method_id, ++ &input, &output, sizeof(__le16)); ++ if (ret) ++ return ret; ++ ++ __le16 *result_status __free(kfree) = output.data; ++ ++ return wmi_status_to_err(le16_to_cpu(*result_status)); ++} ++ ++/** ++ * amd_halo_wmi_turn_off - Turn off all LED channels ++ * @wdev: WMI device pointer ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_turn_off(struct wmi_device *wdev) ++{ ++ struct amd_halo_wmi_args args = { }; ++ ++ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args)); ++} ++ ++/** ++ * amd_halo_wmi_set_rgb - Set all RGB channels atomically ++ * @wdev: WMI device pointer ++ * @r: brightness for red channel (0 - 100) ++ * @g: brightness for green channel (0 - 100) ++ * @b: brightness for blue channel (0 - 100) ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b) ++{ ++ struct amd_halo_wmi_args_rgb args = { ++ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET), ++ .red = cpu_to_le32(r), ++ .green = cpu_to_le32(g), ++ .blue = cpu_to_le32(b), ++ }; ++ ++ if (r > AMD_HALO_MAX_HW_BRIGHTNESS || ++ g > AMD_HALO_MAX_HW_BRIGHTNESS || ++ b > AMD_HALO_MAX_HW_BRIGHTNESS) { ++ return -EINVAL; ++ } ++ ++ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args)); ++} ++ ++/** ++ * amd_halo_wmi_get_rgb - Get RGB values ++ * @wdev: WMI device pointer ++ * @r: output buffer for red value ++ * @g: output buffer for green value ++ * @b: output buffer for blue value ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b) ++{ ++ struct amd_halo_wmi_args_rgb args = { ++ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET), ++ }; ++ struct wmi_buffer input = { ++ .length = sizeof(args), ++ .data = &args, ++ }; ++ struct wmi_buffer output = { }; ++ int ret; ++ ++ ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB, ++ &input, &output, sizeof(struct amd_halo_wmi_output_rgb)); ++ if (ret) ++ return ret; ++ ++ struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data; ++ ++ ret = wmi_status_to_err(le16_to_cpu(data->status)); ++ if (ret) ++ return ret; ++ ++ if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS || ++ data->green > AMD_HALO_MAX_HW_BRIGHTNESS || ++ data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) { ++ return -EPROTO; ++ } ++ ++ *r = data->red; ++ *g = data->green; ++ *b = data->blue; ++ ++ return 0; ++} ++ ++/** ++ * amd_halo_brightness_set - Set LED brightness ++ * @cdev: LED class device ++ * @brightness: Brightness value ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_brightness_set(struct led_classdev *cdev, ++ enum led_brightness brightness) ++{ ++ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); ++ struct amd_halo_led_data *data = container_of(mc_cdev, ++ struct amd_halo_led_data, ++ led_mc); ++ u32 red_hw, green_hw, blue_hw; ++ int ret; ++ ++ guard(mutex)(&data->lock); ++ ++ led_mc_calc_color_components(mc_cdev, brightness); ++ ++ if (brightness == 0) ++ return amd_halo_wmi_turn_off(data->wdev); ++ ++ red_hw = mc_cdev->subled_info[0].brightness; ++ green_hw = mc_cdev->subled_info[1].brightness; ++ blue_hw = mc_cdev->subled_info[2].brightness; ++ ++ ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw); ++ if (ret) ++ goto out; ++ ++ return 0; ++ ++out: ++ /* ++ * Consider the light bar non-functional if AMD_HALO_WMI_RGB failed. ++ * Attempt to turn the LED off completely as clean-up. ++ */ ++ if (amd_halo_wmi_turn_off(data->wdev)) ++ dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n"); ++ ++ return ret; ++} ++ ++static int amd_halo_probe(struct wmi_device *wdev, const void *context) ++{ ++ struct led_init_data led_init_data = { ++ .devicename = "amd_halo", ++ .default_label = "multicolor:" LED_FUNCTION_STATUS, ++ .devname_mandatory = true, ++ }; ++ struct amd_halo_led_data *data; ++ u8 r, g, b; ++ int ret; ++ ++ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ ret = devm_mutex_init(&wdev->dev, &data->lock); ++ if (ret) ++ return ret; ++ ++ data->wdev = wdev; ++ dev_set_drvdata(&wdev->dev, data); ++ ++ data->subled_info[0].color_index = LED_COLOR_ID_RED; ++ data->subled_info[1].color_index = LED_COLOR_ID_GREEN; ++ data->subled_info[2].color_index = LED_COLOR_ID_BLUE; ++ ++ data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS; ++ data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS; ++ data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set; ++ data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; ++ data->led_mc.num_colors = ARRAY_SIZE(data->subled_info); ++ data->led_mc.subled_info = data->subled_info; ++ ++ ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b); ++ if (ret) ++ return ret; ++ ++ data->subled_info[0].intensity = r; ++ data->subled_info[1].intensity = g; ++ data->subled_info[2].intensity = b; ++ ++ ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc, ++ &led_init_data); ++ if (ret) ++ return dev_err_probe(&wdev->dev, ret, ++ "Failed to register multicolor LED\n"); ++ return 0; ++} ++ ++static const struct wmi_device_id amd_halo_id_table[] = { ++ { .guid_string = AMD_HALO_GUID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(wmi, amd_halo_id_table); ++ ++static struct wmi_driver amd_halo_driver = { ++ .driver = { ++ .name = "amd_halo_led", ++ }, ++ .id_table = amd_halo_id_table, ++ .probe = amd_halo_probe, ++ .no_singleton = true, ++}; ++ ++module_wmi_driver(amd_halo_driver); ++ ++MODULE_AUTHOR("Mario Limonciello (AMD) "); ++MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) "); ++MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c +--- a/drivers/platform/x86/amd/hsmp/acpi.c ++++ b/drivers/platform/x86/amd/hsmp/acpi.c +@@ -15,12 +15,18 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -38,6 +44,17 @@ + + static struct hsmp_plat_device *hsmp_pdev; + ++/* ++ * Tracks the ACPI socket platform devices that share the socket array and the ++ * /dev/hsmp misc device. The first probe initializes it, each further probe ++ * takes a reference and every remove (or probe failure) drops one; the last ++ * put frees the shared state via hsmp_acpi_sock_release(). All get/put run ++ * under hsmp_sock_rwsem held for write, so the counting is already serialized ++ * and the atomic in kref is not strictly needed; kref is used for the clearer ++ * get/put interface and its release callback. ++ */ ++static struct kref hsmp_acpi_sock_kref; ++ + struct hsmp_sys_attr { + struct device_attribute dattr; + u32 msg_id; +@@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) + * bytes to integer. + */ + uid = acpi_device_uid(ACPI_COMPANION(dev)); ++ if (!uid || strlen(uid) < 3) ++ return -EINVAL; + + return kstrtou16(uid + 2, 10, sock_ind); + } +@@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) + return AE_OK; + } + +-static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) ++static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) + { + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *guid, *mailbox_package; +@@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + int ret = 0; + int j; + +- status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, ++ status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, + &buf, ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) { +- dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", ++ dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } +@@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + guid = &dsd->package.elements[0]; + mailbox_package = &dsd->package.elements[1]; + if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { +- dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); ++ dev_err(dev, "Invalid hsmp _DSD table data\n"); + ret = -EINVAL; + goto free_buf; + } +@@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + union acpi_object *msgobj, *msgstr, *msgint; + + msgobj = &mailbox_package->package.elements[j]; +- msgstr = &msgobj->package.elements[0]; +- msgint = &msgobj->package.elements[1]; + + /* package should have 1 string and 1 integer object */ + if (msgobj->type != ACPI_TYPE_PACKAGE || +- msgstr->type != ACPI_TYPE_STRING || ++ msgobj->package.count < 2) { ++ ret = -EINVAL; ++ goto free_buf; ++ } ++ ++ msgstr = &msgobj->package.elements[0]; ++ msgint = &msgobj->package.elements[1]; ++ ++ if (msgstr->type != ACPI_TYPE_STRING || + msgint->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto free_buf; +@@ -183,14 +208,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + return ret; + } + +-static int hsmp_read_acpi_crs(struct hsmp_socket *sock) ++static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) + { + acpi_status status; + +- status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, ++ status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, + hsmp_resource, sock); + if (ACPI_FAILURE(status)) { +- dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", ++ dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + acpi_format_exception(status)); + return -EINVAL; + } +@@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) + return -EINVAL; + + /* The mapped region should be un-cached */ +- sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, ++ sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, + sock->mbinfo.size); + if (!sock->virt_base_addr) { +- dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); ++ dev_err(dev, "Failed to ioremap MP1 base address\n"); + return -ENOMEM; + } + +@@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) + int ret; + + sock->sock_ind = sock_ind; +- sock->dev = dev; + sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; + + sema_init(&sock->hsmp_sem, 1); +@@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) + dev_set_drvdata(dev, sock); + + /* Read MP1 base address from CRS method */ +- ret = hsmp_read_acpi_crs(sock); ++ ret = hsmp_read_acpi_crs(dev, sock); + if (ret) + return ret; + + /* Read mailbox offsets from DSD table */ +- return hsmp_read_acpi_dsd(sock); ++ ret = hsmp_read_acpi_dsd(dev, sock); ++ if (ret) ++ return ret; ++ ++ /* ++ * Publish sock->dev last. hsmp_send_message() uses it (via ++ * smp_load_acquire()) as the readiness gate for the lock-free data ++ * plane, so it must become visible only after virt_base_addr, the ++ * mailbox offsets and the semaphore are fully initialized. On a ++ * multi-socket system socket 0 exposes /dev/hsmp before later sockets ++ * finish probing, so without this an ioctl aimed at a socket still in ++ * bring-up could pass the gate and dereference a NULL virt_base_addr. ++ */ ++ smp_store_release(&sock->dev, dev); ++ ++ return 0; + } + + static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, +@@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj + struct device *dev = container_of(kobj, struct device, kobj); + struct hsmp_socket *sock = dev_get_drvdata(dev); + ++ /* ++ * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE. ++ * It can therefore only carry the protocol version 6 metric table ++ * (struct hsmp_metric_table). The larger tables defined from protocol ++ * version 7 onwards do not fit; userspace on those systems must read ++ * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp. ++ * Surface the unsupported case here as -EOPNOTSUPP rather than ++ * silently truncating the snapshot. ++ */ ++ if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6) ++ return -EOPNOTSUPP; ++ + return hsmp_metric_tbl_read(sock, buf, count); + } + + static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, + const struct bin_attribute *battr, int id) + { +- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) ++ /* ++ * Keep metrics_bin visible on protocol version 7 and later as well, ++ * so that userspace which expects the file to exist gets a clear ++ * -EOPNOTSUPP from the read handler instead of -ENOENT, and is ++ * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path. ++ */ ++ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) + return battr->attr.mode; + + return 0; +@@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att + return len; + } + ++/* ++ * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox ++ * handshake and register its sysfs/hwmon interfaces. ++ * ++ * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the ++ * per-socket bring-up cannot race a concurrent probe or remove. ++ */ + static int init_acpi(struct device *dev) + { + u16 sock_ind; + int ret; + ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ + ret = hsmp_get_uid(dev, &sock_ind); + if (ret) + return ret; +@@ -491,7 +557,7 @@ static int init_acpi(struct device *dev) + return ret; + } + +- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { ++ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { + ret = hsmp_get_tbl_dram_base(sock_ind); + if (ret) + dev_info(dev, "Failed to init metric table\n"); +@@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = { + }; + MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids); + ++/* ++ * kref release: tear down the shared ACPI socket state once the last socket ++ * drops its reference. Deregister /dev/hsmp if it was registered, unmap any ++ * metric-table DRAM, destroy the per-socket mutexes and free the socket array. ++ * ++ * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove ++ * and probe-failure paths both drop their reference under that lock. The write ++ * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox ++ * and freeing the array cannot race the data plane. ++ */ ++static void hsmp_acpi_sock_release(struct kref *kref) ++{ ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ ++ if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) ++ hsmp_misc_deregister(); ++ hsmp_unmap_metric_tbls(hsmp_pdev); ++ hsmp_destroy_metric_read_locks(hsmp_pdev); ++ kfree(hsmp_pdev->sock); ++ hsmp_pdev->sock = NULL; ++ hsmp_pdev->num_sockets = 0; ++ hsmp_pdev->proto_ver = 0; ++} ++ ++/** ++ * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe. ++ * @dev: ACPI companion device whose probe failed. ++ * ++ * This device already took a reference on entry to hsmp_acpi_probe(), so clear ++ * its sock->dev and drop that reference; the shared state is released if it was ++ * the last one. ++ * ++ * Clearing sock->dev matters on multi-socket systems: when a non-first socket ++ * fails, the array stays alive (owned by an already-probed socket) and ++ * remove() is never called for this device, yet devres unmaps its mailbox once ++ * probe() returns. Without clearing dev, a later message to this index would ++ * pass every gate in hsmp_send_message() and reach the unmapped mailbox. ++ * ++ * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata. ++ * ++ * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write. ++ */ ++static void hsmp_acpi_probe_failure_cleanup(struct device *dev) ++{ ++ struct hsmp_socket *sock = dev_get_drvdata(dev); ++ ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ ++ if (sock) ++ sock->dev = NULL; ++ ++ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); ++} ++ + static int hsmp_acpi_probe(struct platform_device *pdev) + { + int ret; +@@ -584,34 +704,61 @@ static int hsmp_acpi_probe(struct platform_device *pdev) + if (!hsmp_pdev) + return -ENOMEM; + +- if (!hsmp_pdev->is_probed) { ++ /* ++ * Multiple ACPI socket devices probe in parallel, but the one-time ++ * socket-array allocation and /dev/hsmp registration below must run ++ * exactly once. Hold the socket rwsem for write across the whole ++ * bring-up so it cannot race a concurrent probe or remove, and so the ++ * probe-failure teardown drains the data plane. ++ */ ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ ++ if (!hsmp_pdev->sock) { + hsmp_pdev->num_sockets = topology_max_packages(); + if (!hsmp_pdev->num_sockets) { + dev_err(&pdev->dev, "No CPU sockets detected\n"); + return -ENODEV; + } + +- hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, +- sizeof(*hsmp_pdev->sock), +- GFP_KERNEL); ++ hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets, ++ sizeof(*hsmp_pdev->sock), ++ GFP_KERNEL); + if (!hsmp_pdev->sock) + return -ENOMEM; ++ ++ hsmp_init_metric_read_locks(hsmp_pdev); ++ kref_init(&hsmp_acpi_sock_kref); ++ } else { ++ kref_get(&hsmp_acpi_sock_kref); + } + ++ /* ++ * This socket now holds a reference (kref_init on the first socket, ++ * kref_get afterwards). Every failure path below drops it via ++ * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to ++ * hsmp_acpi_remove(). ++ */ + ret = init_acpi(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); ++ hsmp_acpi_probe_failure_cleanup(&pdev->dev); + return ret; + } + +- if (!hsmp_pdev->is_probed) { +- ret = hsmp_misc_register(&pdev->dev); ++ if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) { ++ /* ++ * Register /dev/hsmp unparented. It is a singleton shared by all ++ * ACPI sockets and outlives all but the last of them, so ++ * parenting it to this socket's device would leave a dangling ++ * parent once that socket is unbound. ++ */ ++ ret = hsmp_misc_register(NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to register misc device\n"); ++ hsmp_acpi_probe_failure_cleanup(&pdev->dev); + return ret; + } +- hsmp_pdev->is_probed = true; +- dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); ++ dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n"); + } + + return 0; +@@ -619,14 +766,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev) + + static void hsmp_acpi_remove(struct platform_device *pdev) + { ++ struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev); ++ + /* +- * We register only one misc_device even on multi-socket system. +- * So, deregister should happen only once. ++ * Serialize the kref_put() and any release it triggers against a ++ * concurrent probe, and drain the data plane for the whole ++ * teardown: this covers the per-socket unbind, whose mailbox devres ++ * unmaps once we return, and the last unbind that frees the socket ++ * array in hsmp_acpi_sock_release(). + */ +- if (hsmp_pdev->is_probed) { +- hsmp_misc_deregister(); +- hsmp_pdev->is_probed = false; +- } ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ ++ /* ++ * Clear this socket's dev so hsmp_send_message() rejects it before ++ * devres unmaps the mailbox. On a non-final unbind the socket array ++ * stays alive, so without this a later message to this index would ++ * reach an unmapped iomem region. ++ */ ++ sock->dev = NULL; ++ ++ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); + } + + static struct platform_driver amd_hsmp_driver = { +diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c +--- a/drivers/platform/x86/amd/hsmp/hsmp.c ++++ b/drivers/platform/x86/amd/hsmp/hsmp.c +@@ -10,10 +10,17 @@ + #include + + #include ++#include + #include + #include ++#include ++#include ++#include ++#include + #include ++#include + #include ++#include + + #include "hsmp.h" + +@@ -40,6 +47,17 @@ + + static struct hsmp_plat_device hsmp_pdev; + ++/* ++ * Gates the AMD HSMP data plane against socket bring-up and teardown. ++ * ++ * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads ++ * run concurrently. Probe and remove take it for write: probe brings sockets ++ * up (running the mailbox handshake via hsmp_send_message_locked()) and remove ++ * tears them down, both excluding and draining the data plane. ++ */ ++DECLARE_RWSEM(hsmp_sock_rwsem); ++EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); ++ + /* + * Send a message to the HSMP port via PCI-e config space registers + * or by writing to MMIO space. +@@ -182,29 +200,33 @@ static int validate_message(struct hsmp_message *msg) + return -EINVAL; + + /* +- * Some older HSMP SET messages are updated to add GET in the same message. +- * In these messages, GET returns the current value and SET also returns +- * the successfully set value. To support this GET and SET in same message +- * while maintaining backward compatibility for the HSMP users, +- * hsmp_msg_desc_table[] indicates only maximum allowed response_sz. ++ * As the HSMP protocol evolves, newer platforms may define more ++ * response arguments for existing messages. Use an upper-bound ++ * check so that older userspace callers requesting fewer response ++ * words than what the current hsmp_msg_desc_table[] defines are ++ * still accepted, while rejecting requests that exceed the ++ * hardware capability. + */ +- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) { +- if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) +- return -EINVAL; +- } else { +- /* only HSMP_SET or HSMP_GET messages go through this strict check */ +- if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) +- return -EINVAL; +- } ++ if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) ++ return -EINVAL; ++ + return 0; + } + +-int hsmp_send_message(struct hsmp_message *msg) ++/* ++ * Core message send. The caller must hold hsmp_sock_rwsem: the data plane ++ * takes it for read so many messages run concurrently, while the probe-time ++ * senders run under the write lock taken by probe. Holding it here serializes ++ * every message against socket teardown, which also holds it for write. ++ */ ++static int hsmp_send_message_locked(struct hsmp_message *msg) + { + struct hsmp_socket *sock; + unsigned int sock_ind; + int ret; + ++ lockdep_assert_held(&hsmp_sock_rwsem); ++ + if (!msg) + return -EINVAL; + ret = validate_message(msg); +@@ -223,6 +245,20 @@ int hsmp_send_message(struct hsmp_message *msg) + sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + ++ /* ++ * A slot exists for every possible socket, but it is only usable once ++ * that socket has actually been probed. Reject messages aimed at a ++ * socket that was never brought up or is still in bring-up, so we never ++ * operate on a zero-initialized semaphore or an unmapped mailbox. A ++ * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and ++ * the semaphore are visible. ++ * ++ * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev) ++ * in hsmp_parse_acpi_table(). ++ */ ++ if (!smp_load_acquire(&sock->dev)) ++ return -ENODEV; ++ + ret = down_interruptible(&sock->hsmp_sem); + if (ret < 0) + return ret; +@@ -233,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg) + + return ret; + } ++ ++int hsmp_send_message(struct hsmp_message *msg) ++{ ++ /* ++ * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue ++ * messages from here. Take hsmp_sock_rwsem for read so messages run ++ * concurrently with each other but are drained and kept out while ++ * probe/remove hold it for write to tear a socket down. ++ */ ++ guard(rwsem_read)(&hsmp_sock_rwsem); ++ ++ return hsmp_send_message_locked(msg); ++} + EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); + + int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args) +@@ -273,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value) + msg.args[0] = value; + msg.sock_ind = sock_ind; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + +@@ -301,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg) + return false; + } + +-long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) + { + int __user *arguser = (int __user *)arg; + struct hsmp_message msg = { 0 }; +@@ -370,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) + return 0; + } + +-ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) ++static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, ++ size_t size); ++ ++/* ++ * Fetch the firmware metric (telemetry) table for the requested socket and ++ * copy it to the userspace buffer described by the request. ++ * ++ * The metric table size is variable across HSMP protocol versions and on ++ * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer ++ * size, which may be anything up to the size firmware reported for this ++ * socket's table. ++ */ ++static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg) ++{ ++ void *kbuf __free(kvfree) = NULL; ++ void __user *arguser = (void __user *)arg; ++ struct hsmp_telemetry_data req; ++ struct hsmp_socket *sock; ++ void __user *user_buf; ++ size_t tbl_size; ++ unsigned int sock_ind; ++ int ret; ++ ++ /* Telemetry data is read-only; require read access on the fd. */ ++ if (!(fp->f_mode & FMODE_READ)) ++ return -EPERM; ++ ++ if (copy_from_user(&req, arguser, sizeof(req))) ++ return -EFAULT; ++ ++ /* ++ * Reserved fields must be zero so future kernels can safely ++ * repurpose them without breaking already-deployed userspace. ++ */ ++ if (req.reserved) ++ return -EINVAL; ++ ++ user_buf = u64_to_user_ptr(req.buf); ++ ++ /* ++ * /dev/hsmp is a singleton character device that outlives an individual ++ * socket unbind, so an ioctl on an already-open fd can run concurrently ++ * with socket teardown. Hold hsmp_sock_rwsem for read across the socket ++ * lookup, the checks on its metric-table state and the read itself: ++ * probe and remove take the same lock for write, so they cannot free the ++ * socket array, unmap the table or destroy the per-socket mutex while ++ * this runs. ++ * ++ * The lock is dropped before the copy_to_user() below. Faulting in the ++ * destination can block indefinitely on a userfaultfd-backed buffer, ++ * which would leave a socket unbind waiting for the write lock. ++ */ ++ scoped_guard(rwsem_read, &hsmp_sock_rwsem) { ++ if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets) ++ return -ENODEV; ++ ++ /* ++ * Sanitize the user-controlled socket index against speculative ++ * execution. The bounds check above retires the out-of-range ++ * case with -ENODEV, but a mispredicted branch can still let the ++ * CPU speculatively use sock_ind as an index into ++ * hsmp_pdev.sock[] and pull arbitrary kernel memory into the ++ * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns ++ * the bounds check into a data-flow clamp so the speculative ++ * load is in-range too. ++ */ ++ sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets); ++ sock = &hsmp_pdev.sock[sock_ind]; ++ if (!sock->metric_tbl_addr) ++ return -ENODEV; ++ ++ tbl_size = sock->metric_tbl_size; ++ if (!tbl_size) ++ return -ENODEV; ++ ++ /* ++ * A request shorter than the firmware table is served with the ++ * leading @size bytes of the snapshot, so userspace built ++ * against an older table layout keeps working on firmware that ++ * grew the table. Asking for more than firmware provides is ++ * rejected rather than short-written, so a caller can never ++ * mistake a partial copy for a full one. ++ */ ++ if (!req.size || req.size > tbl_size) ++ return -EINVAL; ++ ++ /* ++ * The bounce buffer is overwritten in full by memcpy_fromio() ++ * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid ++ * the zeroing cost of kvzalloc() on the ~13 KB allocation done ++ * on every ioctl call. ++ */ ++ kbuf = kvmalloc(tbl_size, GFP_KERNEL); ++ if (!kbuf) ++ return -ENOMEM; ++ ++ ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size); ++ } ++ ++ if (ret < 0) ++ return ret; ++ ++ if (copy_to_user(user_buf, kbuf, req.size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ switch (cmd) { ++ case HSMP_IOCTL_CMD: ++ return hsmp_ioctl_msg(fp, arg); ++ case HSMP_IOCTL_GET_TELEMETRY_DATA: ++ return hsmp_ioctl_get_telemetry(fp, arg); ++ default: ++ return -ENOTTY; ++ } ++} ++ ++/* ++ * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping ++ * and its metric_read_lock alive: probe and remove take the same lock for ++ * write while they bring sockets up and tear them down. ++ */ ++static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, ++ size_t size) + { + struct hsmp_message msg = { 0 }; + int ret; + ++ lockdep_assert_held(&hsmp_sock_rwsem); ++ + if (!sock || !buf) + return -EINVAL; + +@@ -383,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) + return -ENOMEM; + } + +- /* Do not support lseek(), also don't allow more than the size of metric table */ +- if (size != sizeof(struct hsmp_metric_table)) { ++ if (size != sock->metric_tbl_size) { + dev_err(sock->dev, "Wrong buffer size\n"); + return -EINVAL; + } +@@ -392,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) + msg.msg_id = HSMP_GET_METRIC_TABLE; + msg.sock_ind = sock->sock_ind; + +- ret = hsmp_send_message(&msg); ++ /* ++ * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared ++ * metric DRAM region, which is then copied out below. Hold the ++ * per-socket lock across the fill-and-copy so concurrent readers of the ++ * same socket cannot return a torn snapshot. ++ */ ++ guard(mutex)(&sock->metric_read_lock); ++ ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + memcpy_fromio(buf, sock->metric_tbl_addr, size); + + return size; + } ++ ++ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) ++{ ++ guard(rwsem_read)(&hsmp_sock_rwsem); ++ ++ return hsmp_metric_tbl_read_locked(sock, buf, size); ++} + EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); + ++void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) ++{ ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) ++ mutex_init(&pdev->sock[i].metric_read_lock); ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP"); ++ ++void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev) ++{ ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) ++ mutex_destroy(&pdev->sock[i].metric_read_lock); ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP"); ++ ++void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) ++{ ++ struct hsmp_socket *sock; ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) { ++ sock = &pdev->sock[i]; ++ if (sock->metric_tbl_addr) { ++ iounmap(sock->metric_tbl_addr); ++ sock->metric_tbl_addr = NULL; ++ } ++ sock->metric_tbl_size = 0; ++ } ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); ++ + int hsmp_get_tbl_dram_base(u16 sock_ind) + { + struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; + struct hsmp_message msg = { 0 }; + phys_addr_t dram_addr; ++ size_t tbl_size; + int ret; + + msg.sock_ind = sock_ind; + msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; + msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + +@@ -425,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) + dev_err(sock->dev, "Invalid DRAM address for metric table\n"); + return -ENOMEM; + } +- sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, +- sizeof(struct hsmp_metric_table)); ++ /* ++ * The ACPI socket array is shared across sockets and outlives a ++ * per-socket unbind, so metric_tbl_addr may hold a mapping from an ++ * earlier bind of this socket. Unmap it before remapping so an ++ * unbind/rebind cycle does not leak a metric-table mapping. This runs ++ * during probe before the metric sysfs attribute is exposed, so no ++ * reader can be using it. ++ */ ++ if (sock->metric_tbl_addr) { ++ iounmap(sock->metric_tbl_addr); ++ sock->metric_tbl_addr = NULL; ++ } ++ sock->metric_tbl_size = 0; ++ ++ /* SMU returns table size from Family 1Ah Model 50h and forward */ ++ if (msg.args[2]) ++ tbl_size = msg.args[2]; ++ else ++ tbl_size = sizeof(struct hsmp_metric_table); ++ ++ sock->metric_tbl_addr = ioremap(dram_addr, tbl_size); + if (!sock->metric_tbl_addr) { + dev_err(sock->dev, "Failed to ioremap metric table addr\n"); + return -ENOMEM; + } ++ sock->metric_tbl_size = tbl_size; ++ + return 0; + } + EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP"); +@@ -444,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind) + msg.sock_ind = sock_ind; + msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (!ret) + hsmp_pdev.proto_ver = msg.args[0]; + +@@ -463,6 +710,14 @@ int hsmp_misc_register(struct device *dev) + hsmp_pdev.mdev.name = HSMP_CDEV_NAME; + hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR; + hsmp_pdev.mdev.fops = &hsmp_fops; ++ /* ++ * The caller chooses the parent. The platform driver has a single ++ * device whose lifetime matches /dev/hsmp and parents it there. The ++ * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by ++ * per-socket devices that can be unbound individually and out of order, ++ * so parenting it to one would leave it attached to an already-removed ++ * device. ++ */ + hsmp_pdev.mdev.parent = dev; + hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; + hsmp_pdev.mdev.mode = 0644; +@@ -474,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP"); + void hsmp_misc_deregister(void) + { + misc_deregister(&hsmp_pdev.mdev); ++ hsmp_pdev.mdev.this_device = NULL; + } + EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP"); + +diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h +--- a/drivers/platform/x86/amd/hsmp/hsmp.h ++++ b/drivers/platform/x86/amd/hsmp/hsmp.h +@@ -15,9 +15,12 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include ++#include + + #define HSMP_METRICS_TABLE_NAME "metrics_bin" + +@@ -27,7 +30,7 @@ + #define HSMP_DEVNODE_NAME "hsmp" + #define ACPI_HSMP_DEVICE_HID "AMDI0097" + +-#define DRIVER_VERSION "2.5" ++#define DRIVER_VERSION "2.6" + + struct hsmp_mbaddr_info { + u32 base_addr; +@@ -41,8 +44,12 @@ struct hsmp_socket { + struct bin_attribute hsmp_attr; + struct hsmp_mbaddr_info mbinfo; + void __iomem *metric_tbl_addr; ++ /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */ ++ size_t metric_tbl_size; + void __iomem *virt_base_addr; + struct semaphore hsmp_sem; ++ /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ ++ struct mutex metric_read_lock; + char name[HSMP_ATTR_GRP_NAME_SIZE]; + struct device *dev; + u16 sock_ind; +@@ -54,7 +61,6 @@ struct hsmp_plat_device { + struct hsmp_socket *sock; + u32 proto_ver; + u16 num_sockets; +- bool is_probed; + }; + + int hsmp_cache_proto_ver(u16 sock_ind); +@@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); + void hsmp_misc_deregister(void); + int hsmp_misc_register(struct device *dev); + int hsmp_get_tbl_dram_base(u16 sock_ind); ++void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); ++void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev); ++void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev); + ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); + struct hsmp_plat_device *get_hsmp_pdev(void); + #if IS_ENABLED(CONFIG_HWMON) +@@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); + static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } + #endif + int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); ++ ++/* ++ * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and ++ * remove take it for write to bring sockets up and tear them down. ++ */ ++extern struct rw_semaphore hsmp_sock_rwsem; + #endif /* HSMP_H */ +diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c +--- a/drivers/platform/x86/amd/hsmp/plat.c ++++ b/drivers/platform/x86/amd/hsmp/plat.c +@@ -13,12 +13,14 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include + #include ++#include + #include + + #include +@@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev) + return 0; + } + ++/* ++ * The socket array is devm-managed and freed by the driver core, but the ++ * metric-table DRAM regions are mapped with plain ioremap() during probe and ++ * the per-socket mutexes need an explicit mutex_destroy(), neither of which ++ * devres covers. ++ * ++ * Take the data-plane rwsem for write to drain any in-flight ++ * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop ++ * the global socket pointer, all before devres frees the array. Registered as ++ * a devres action so it runs on both remove and probe failure. ++ */ ++static void hsmp_pltdrv_release(void *data) ++{ ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ hsmp_unmap_metric_tbls(hsmp_pdev); ++ hsmp_destroy_metric_read_locks(hsmp_pdev); ++ hsmp_pdev->sock = NULL; ++} ++ + static int hsmp_pltdrv_probe(struct platform_device *pdev) + { + int ret; +@@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) + if (!hsmp_pdev->sock) + return -ENOMEM; + +- ret = init_platform_device(&pdev->dev); ++ hsmp_init_metric_read_locks(hsmp_pdev); ++ ++ ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); ++ if (ret) ++ return ret; ++ ++ /* ++ * init_platform_device() runs the mailbox handshake via the probe-only ++ * senders, which issue messages through hsmp_send_message_locked() and ++ * so require hsmp_sock_rwsem held. Hold it for write, matching probe's ++ * role as a socket bring-up path. The lock is not held across ++ * devm_add_action_or_reset() above so the release action, which also ++ * takes it for write, does not deadlock if that registration fails. ++ */ ++ scoped_guard(rwsem_write, &hsmp_sock_rwsem) ++ ret = init_platform_device(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); + return ret; +diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c +--- a/drivers/platform/x86/amd/pmc/mp1_stb.c ++++ b/drivers/platform/x86/amd/pmc/mp1_stb.c +@@ -299,10 +299,7 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) + if (!enable_stb) + return 0; + +- if (amd_is_stb_supported(dev)) { +- debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, +- &amd_stb_debugfs_fops_v2); +- } else { ++ if (!amd_is_stb_supported(dev)) { + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_stb_debugfs_fops); + return 0; +@@ -342,8 +339,18 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) + } + + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); +- if (!dev->stb_virt_addr) ++ if (!dev->stb_virt_addr) { + ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* ++ * Only expose stb_read once the buffer is mapped; otherwise a read ++ * faults on a NULL dev->stb_virt_addr, now that a failed STB init no ++ * longer aborts probe. ++ */ ++ debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, ++ &amd_stb_debugfs_fops_v2); + + out: + /* Restore the default message port for subsequent SMU operations */ +diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c +--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c ++++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c +@@ -71,6 +71,14 @@ static const struct dmi_system_id fwbug_list[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "20XK"), + } + }, ++ { ++ .ident = "T14 Gen2 AMD", ++ .driver_data = &quirk_s2idle_spurious_8042, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "20XL"), ++ } ++ }, + { + .ident = "T14 Gen1 AMD", + .driver_data = &quirk_s2idle_spurious_8042, +diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c +--- a/drivers/platform/x86/amd/pmc/pmc.c ++++ b/drivers/platform/x86/amd/pmc/pmc.c +@@ -939,19 +939,22 @@ static int amd_pmc_probe(struct platform_device *pdev) + } + + amd_pmc_dbgfs_register(dev); ++ ++ /* ++ * STB is an optional debug facility (enable_stb); a failure to set it ++ * up must not stop the rest of the driver - most importantly the s0i3 ++ * LPS0 handler - from working, so treat it as non-fatal. ++ */ + err = amd_stb_s2d_init(dev); + if (err) +- goto err_dbgfs_unregister; ++ dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n", ++ err); + + if (IS_ENABLED(CONFIG_AMD_MP2_STB)) + amd_mp2_stb_init(dev); + pm_report_max_hw_sleep(U64_MAX); + return 0; + +-err_dbgfs_unregister: +- amd_pmc_dbgfs_unregister(dev); +- if (IS_ENABLED(CONFIG_SUSPEND)) +- acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); + err_pci_dev_put: + pci_dev_put(rdev); + return err; +diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig +--- a/drivers/platform/x86/amd/pmf/Kconfig ++++ b/drivers/platform/x86/amd/pmf/Kconfig +@@ -30,3 +30,13 @@ config AMD_PMF_DEBUG + in the PMF config store. + + Say Y here to enable more debug logs and Say N here if you are not sure. ++ ++config AMD_PMF_UTIL_SUPPORT ++ bool "AMD PMF Util layer support" ++ depends on AMD_PMF ++ help ++ Enabling this option provides a character device for userspace to capture ++ PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic ++ Power Slider AC/DC) along with PMF metrics from the AMD PMF driver. ++ ++ Say Y here to enable it and Say N here if you are not sure. +diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile +--- a/drivers/platform/x86/amd/pmf/Makefile ++++ b/drivers/platform/x86/amd/pmf/Makefile +@@ -7,4 +7,6 @@ + obj-$(CONFIG_AMD_PMF) += amd-pmf.o + amd-pmf-y := core.o acpi.o sps.o \ + auto-mode.o cnqf.o \ +- tee-if.o spc.o ++ tee-if.o spc.o metrics.o ++# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled ++amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o +diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c +--- a/drivers/platform/x86/amd/pmf/core.c ++++ b/drivers/platform/x86/amd/pmf/core.c +@@ -11,13 +11,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include "pmf.h" + +@@ -26,6 +26,13 @@ + #define AMD_PMF_REGISTER_RESPONSE 0xA78 + #define AMD_PMF_REGISTER_ARGUMENT 0xA58 + ++/* PMF-SMU communication registers for 1AH_M80H */ ++#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04 ++#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08 ++#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C ++#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC ++#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0 ++ + /* Base address of SMU for mapping physical address to virtual address */ + #define AMD_PMF_MAPPING_SIZE 0x01000 + #define AMD_PMF_BASE_ADDR_OFFSET 0x10000 +@@ -48,7 +55,7 @@ + #define DELAY_MAX_US 3000 + + /* override Metrics Table sample size time (in ms) */ +-static int metrics_table_loop_ms = 1000; ++int metrics_table_loop_ms = 1000; + module_param(metrics_table_loop_ms, int, 0644); + MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)"); + +@@ -61,7 +68,15 @@ static bool smart_pc_support = true; + module_param(smart_pc_support, bool, 0444); + MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)"); + +-static struct device *pmf_device; ++static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev) ++{ ++ switch (pdev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ return true; ++ default: ++ return false; ++ } ++} + + static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) + { +@@ -131,37 +146,6 @@ int amd_pmf_get_power_source(void) + return POWER_SOURCE_DC; + } + +-static void amd_pmf_get_metrics(struct work_struct *work) +-{ +- struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); +- ktime_t time_elapsed_ms; +- int socket_power; +- +- guard(mutex)(&dev->update_mutex); +- +- /* Transfer table contents */ +- memset(dev->buf, 0, sizeof(dev->m_table)); +- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); +- memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); +- +- time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; +- /* Calculate the avg SoC power consumption */ +- socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; +- +- if (dev->amt_enabled) { +- /* Apply the Auto Mode transition */ +- amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); +- } +- +- if (dev->cnqf_enabled) { +- /* Apply the CnQF transition */ +- amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); +- } +- +- dev->start_time = ktime_to_ms(ktime_get()); +- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); +-} +- + static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset) + { + return ioread32(dev->regbase + reg_offset); +@@ -176,13 +160,19 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) + { + u32 value; + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value); + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value); ++ if (amd_pmf_supports_accumulator_metrics(dev)) { ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]); ++ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]); ++ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value); ++ } + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); + } + +@@ -208,7 +198,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + guard(mutex)(&dev->lock); + + /* Wait until we get a valid response */ +- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, ++ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, + val, val != 0, PMF_MSG_DELAY_MIN_US, + PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { +@@ -217,16 +207,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + } + + /* Write zero to response register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0); ++ amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0); + + /* Write argument into argument register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg); ++ amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg); + + /* Write message ID to message ID register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message); ++ amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message); + + /* Wait until we get a valid response */ +- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, ++ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, + val, val != 0, PMF_MSG_DELAY_MIN_US, + PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { +@@ -239,7 +229,14 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + if (get) { + /* PMFW may take longer time to return back the data */ + usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); +- *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); ++ *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); ++ if (amd_pmf_supports_accumulator_metrics(dev) && ++ message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) { ++ dev->dram_addr.hi = amd_pmf_reg_read(dev, ++ dev->smu_regs->arg_reg[1]); ++ dev->dram_addr.size = amd_pmf_reg_read(dev, ++ dev->smu_regs->arg_reg[2]); ++ } + } + break; + case AMD_PMF_RESULT_CMD_REJECT_BUSY: +@@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + return rc; + } + +-static const struct pci_device_id pmf_pci_ids[] = { +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) }, +- { } ++/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */ ++static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = { ++ .msg_reg = AMD_PMF_REGISTER_MESSAGE, ++ .resp_reg = AMD_PMF_REGISTER_RESPONSE, ++ .arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 }, + }; + +-int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) +-{ +- u64 phys_addr; +- u32 hi, low; ++/* 1AH_M80H uses an extended mailbox with three argument registers */ ++static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = { ++ .msg_reg = AMD_PMF_REGISTER_MESSAGE_V2, ++ .resp_reg = AMD_PMF_REGISTER_RESPONSE_V2, ++ .arg_reg = { ++ AMD_PMF_REGISTER_ARGUMENT0_V2, ++ AMD_PMF_REGISTER_ARGUMENT1_V2, ++ AMD_PMF_REGISTER_ARGUMENT2_V2, ++ }, ++}; + +- /* Get Metrics Table Address */ +- if (alloc_buffer) { +- switch (dev->cpu_id) { +- case AMD_CPU_ID_PS: +- case AMD_CPU_ID_RMB: +- dev->mtable_size = sizeof(dev->m_table); +- break; +- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: +- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: +- dev->mtable_size = sizeof(dev->m_table_v2); +- break; +- default: +- dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id); +- } +- +- dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); +- if (!dev->buf) +- return -ENOMEM; +- } +- +- phys_addr = virt_to_phys(dev->buf); +- hi = phys_addr >> 32; +- low = phys_addr & GENMASK(31, 0); +- +- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); +- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); +- +- return 0; +-} +- +-int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) +-{ +- int ret; +- +- INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); +- +- ret = amd_pmf_set_dram_addr(dev, true); +- if (ret) +- return ret; +- +- /* +- * Start collecting the metrics data after a small delay +- * or else, we might end up getting stale values from PMFW. +- */ +- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); +- +- return 0; +-} +- +-static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) +-{ +- switch (pdev->cpu_id) { +- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: +- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: +- return 0; +- default: +- return -EOPNOTSUPP; +- } +-} +- +-static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) +-{ +- int ret, i; +- +- guard(mutex)(&dev->metrics_mutex); +- +- ret = is_npu_metrics_supported(dev); +- if (ret) +- return ret; +- +- ret = amd_pmf_set_dram_addr(dev, true); +- if (ret) +- return ret; +- +- memset(dev->buf, 0, dev->mtable_size); +- +- /* Send SMU command to get NPU metrics */ +- ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); +- if (ret) { +- dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); +- return ret; +- } +- +- memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); +- +- data->npuclk_freq = dev->m_table_v2.npuclk_freq; +- for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) +- data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; +- data->npu_power = dev->m_table_v2.npu_power; +- data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; +- data->npu_reads = dev->m_table_v2.npu_reads; +- data->npu_writes = dev->m_table_v2.npu_writes; +- +- return 0; +-} +- +-int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) +-{ +- struct amd_pmf_dev *pdev; +- +- if (!info) +- return -EINVAL; +- +- if (!pmf_device) +- return -ENODEV; +- +- pdev = dev_get_drvdata(pmf_device); +- if (!pdev) +- return -ENODEV; +- +- return amd_pmf_get_smu_metrics(pdev, info); +-} +-EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); ++static const struct pci_device_id pmf_pci_ids[] = { ++ { PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) }, ++ { } ++}; + + static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev) + { +@@ -536,6 +433,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev) + } + } + ++static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev) ++{ ++ const struct pci_device_id *id; ++ ++ id = pci_match_id(pmf_pci_ids, rdev); ++ if (!id) ++ return -ENODEV; ++ ++ pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data; ++ ++ return 0; ++} ++ + static const struct acpi_device_id amd_pmf_acpi_ids[] = { + {"AMDI0100", 0x100}, + {"AMDI0102", 0}, +@@ -543,6 +453,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { + {"AMDI0105", 0}, + {"AMDI0107", 0}, + {"AMDI0108", 0}, ++ {"AMDI0109", 0}, + { } + }; + MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); +@@ -624,6 +535,17 @@ static int amd_pmf_probe(struct platform_device *pdev) + if (err) + return err; + ++ /* Populate smu_regs with SoC-specific SMU mailbox register offsets */ ++ err = amd_pmf_get_smu_mb_offset(dev, rdev); ++ if (err) ++ return err; ++ ++ if (amd_pmf_supports_accumulator_metrics(dev)) { ++ err = amd_pmf_get_tbl_dram_addr(dev); ++ if (err) ++ return err; ++ } ++ + apmf_acpi_init(dev); + platform_set_drvdata(pdev, dev); + amd_pmf_dbgfs_register(dev); +@@ -632,7 +554,11 @@ static int amd_pmf_probe(struct platform_device *pdev) + if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) + amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); + +- pmf_device = dev->dev; ++ amd_pmf_set_device(dev->dev); ++ ++ err = amd_pmf_cdev_register(dev); ++ if (err) ++ dev_warn(dev->dev, "failed to register util interface: %d\n", err); + + dev_info(dev->dev, "registered PMF device successfully\n"); + +@@ -643,6 +569,7 @@ static void amd_pmf_remove(struct platform_device *pdev) + { + struct amd_pmf_dev *dev = platform_get_drvdata(pdev); + ++ amd_pmf_cdev_unregister(); + amd_pmf_deinit_features(dev); + if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) + amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD); +diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/pmf/metrics.c +@@ -0,0 +1,330 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * AMD Platform Management Framework Driver - Metrics Support ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * Authors: Shyam Sundar S K ++ * Patil Rajesh Reddy ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmf.h" ++ ++static struct device *pmf_device; ++ ++static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc) ++{ ++ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT; ++ ++ return min_t(u16, val, U16_MAX); ++} ++ ++static u16 amd_pmf_q10_acc_to_int(u64 avg_acc) ++{ ++ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024); ++ ++ return min_t(u16, val, U16_MAX); ++} ++ ++static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter, ++ u64 curr_value, u64 prev_value) ++{ ++ u32 counter_diff; ++ u64 val_diff; ++ ++ /* Check for counter reset */ ++ if (curr_counter <= prev_counter) ++ return 0; ++ ++ counter_diff = curr_counter - prev_counter; ++ ++ if (curr_value < prev_value) ++ return 0; ++ ++ val_diff = curr_value - prev_value; ++ ++ return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff); ++} ++ ++int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD, ++ ARG_NONE, &dev->dram_addr.lo); ++ if (ret) { ++ dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret); ++ return ret; ++ } ++ ++ dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo; ++ dev->mtable_size = dev->dram_addr.size; ++ ++ if (dev->mtable_size != sizeof(dev->mtable_v3)) { ++ dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n", ++ dev->dram_addr.size, sizeof(dev->mtable_v3)); ++ return -EINVAL; ++ } ++ ++ dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size); ++ if (!dev->metrics_table_virt) { ++ dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n"); ++ dev->metrics_table_phys = 0; ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ if (!dev->metrics_table_virt) { ++ dev_err(dev->dev, "Metrics DRAM not mapped\n"); ++ return -EINVAL; ++ } ++ ++ /* Send command to PMFW to update metrics table log sample */ ++ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL); ++ if (ret) { ++ dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void amd_pmf_get_metrics(struct work_struct *work) ++{ ++ struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); ++ ktime_t time_elapsed_ms; ++ int socket_power; ++ ++ guard(mutex)(&dev->update_mutex); ++ ++ /* Transfer table contents */ ++ memset(dev->buf, 0, sizeof(dev->m_table)); ++ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); ++ memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); ++ ++ time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; ++ /* Calculate the avg SoC power consumption */ ++ socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; ++ ++ if (dev->amt_enabled) { ++ /* Apply the Auto Mode transition */ ++ amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); ++ } ++ ++ if (dev->cnqf_enabled) { ++ /* Apply the CnQF transition */ ++ amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); ++ } ++ ++ dev->start_time = ktime_to_ms(ktime_get()); ++ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); ++} ++ ++int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) ++{ ++ u64 phys_addr; ++ u32 hi, low; ++ ++ /* Get Metrics Table Address */ ++ if (alloc_buffer) { ++ switch (dev->cpu_id) { ++ case AMD_CPU_ID_PS: ++ case AMD_CPU_ID_RMB: ++ dev->mtable_size = sizeof(dev->m_table); ++ break; ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ dev->mtable_size = sizeof(dev->m_table_v2); ++ break; ++ default: ++ dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id); ++ } ++ ++ dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); ++ if (!dev->buf) ++ return -ENOMEM; ++ } ++ ++ phys_addr = virt_to_phys(dev->buf); ++ hi = upper_32_bits(phys_addr); ++ low = lower_32_bits(phys_addr); ++ ++ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); ++ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); ++ ++ return 0; ++} ++ ++int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); ++ ++ ret = amd_pmf_set_dram_addr(dev, true); ++ if (ret) ++ return ret; ++ ++ /* ++ * Start collecting the metrics data after a small delay ++ * or else, we might end up getting stale values from PMFW. ++ */ ++ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); ++ ++ return 0; ++} ++ ++void amd_pmf_set_device(struct device *p_device) ++{ ++ pmf_device = p_device; ++} ++ ++static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) ++{ ++ switch (pdev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ return 0; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev, ++ struct amd_pmf_npu_metrics *data) ++{ ++ struct amd_pmf_metrics_iod *curr, *prev; ++ u64 val; ++ int i; ++ ++ curr = &dev->mtable_v3.iod; ++ prev = &dev->prev_metrics.iod; ++ ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_temp_acc, prev->npu_temp_acc); ++ data->npu_temp = amd_pmf_q10_acc_to_int(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_power_acc, prev->npu_power_acc); ++ data->npu_power = amd_pmf_q10_acc_to_mW(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npuhclk_freq_eff_acc, ++ prev->npuhclk_freq_eff_acc); ++ data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->aieclk_freq_eff_acc, ++ prev->aieclk_freq_eff_acc); ++ data->npuclk_freq = amd_pmf_q10_acc_to_int(val); ++ ++ for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) { ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_busy_acc[i], ++ prev->npu_busy_acc[i]); ++ data->npu_busy[i] = amd_pmf_q10_acc_to_int(val); ++ } ++} ++ ++static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) ++{ ++ int ret, i; ++ ++ guard(mutex)(&dev->metrics_mutex); ++ ++ ret = is_npu_metrics_supported(dev); ++ if (ret) ++ return ret; ++ ++ memset(data, 0, sizeof(*data)); ++ ++ switch (dev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ ret = amd_pmf_set_dram_addr(dev, true); ++ if (ret) ++ return ret; ++ ++ memset(dev->buf, 0, dev->mtable_size); ++ ++ /* Send SMU command to get NPU metrics */ ++ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); ++ if (ret) { ++ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); ++ return ret; ++ } ++ ++ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); ++ ++ data->npuclk_freq = dev->m_table_v2.npuclk_freq; ++ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) ++ data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; ++ data->npu_power = dev->m_table_v2.npu_power; ++ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; ++ data->npu_reads = dev->m_table_v2.npu_reads; ++ data->npu_writes = dev->m_table_v2.npu_writes; ++ break; ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ ret = amd_pmf_get_metrics_table_log_sample(dev); ++ if (ret) ++ return ret; ++ ++ memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3)); ++ ++ /* ++ * Ignore the first sample, as previous metrics is uninitialized (zero) ++ * Metrics are calculated as: ++ * metrics = current_metrics - previous_metrics ++ * Skipping the initial sample ensures accurate delta calculations. ++ */ ++ if (!dev->npu_metrics_have_prev) { ++ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); ++ dev->npu_metrics_have_prev = true; ++ return 0; ++ } ++ ++ amd_pmf_calculate_acc_npu_metrics(dev, data); ++ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); ++ break; ++ } ++ ++ return 0; ++} ++ ++int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) ++{ ++ struct amd_pmf_dev *pdev; ++ ++ if (!info) ++ return -EINVAL; ++ ++ if (!pmf_device) ++ return -ENODEV; ++ ++ pdev = dev_get_drvdata(pmf_device); ++ if (!pdev) ++ return -ENODEV; ++ ++ return amd_pmf_get_smu_metrics(pdev, info); ++} ++EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); +diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h +--- a/drivers/platform/x86/amd/pmf/pmf.h ++++ b/drivers/platform/x86/amd/pmf/pmf.h +@@ -14,10 +14,13 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include + #include ++#include + + #define POLICY_BUF_MAX_SZ 0x4b000 + #define POLICY_SIGN_COOKIE 0x31535024 +@@ -28,6 +31,11 @@ + #define AMD_CPU_ID_PS 0x14e8 + #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 + #define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b ++ ++/* Aliases required by PCI_DEVICE_DATA() macro naming convention */ ++#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB ++#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS + + struct cookie_header { + u32 sign; +@@ -70,6 +78,10 @@ struct cookie_header { + #define SET_PMF_PPT 0x25 + #define SET_PMF_PPT_APU_ONLY 0x26 + ++/* Message IDs for 1AH_M80H platform */ ++#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E ++#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F ++ + /* OS slider update notification */ + #define DC_BEST_PERF 0 + #define DC_BETTER_PERF 1 +@@ -130,6 +142,14 @@ struct cookie_header { + #define GET_CMD true + + #define METRICS_TABLE_ID 7 ++#define BIOS_OUTPUT_MAX 10 ++ ++extern int metrics_table_loop_ms; ++ ++#define AMD_PMF_METRIC_CORE_TYPE_MAX 4 ++#define AMD_PMF_METRIC_CCX_MAX 4 ++#define AMD_PMF_NUM_CLK_DPM_LEVELS 8 ++#define AMD_PMF_NUM_MAX_CORES 12 + + typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); + +@@ -242,6 +262,199 @@ struct apmf_fan_idx { + u32 fan_ctl_idx; + } __packed; + ++struct amd_pmf_metrics_iod { ++ u32 counter_acc; ++ /* Set Voltage */ ++ u64 vddcr_set_voltage; ++ u64 vddcr_soc_set_voltage; ++ u64 vddcr_npu_set_voltage; ++ u64 vddcr_lp_set_voltage; ++ u64 vddcr_gfx_set_voltage; ++ u64 vdd_misc_set_voltage; ++ /* Telemetry Voltages */ ++ u64 vddcr_telemetry_voltage; ++ u64 vddcr_soc_telemetry_voltage; ++ u64 vddcr_npu_telemetry_voltage; ++ u64 vddcr_lp_telemetry_voltage; ++ u64 vddcr_gfx_telemetry_voltage; ++ u64 vdd_misc_telemetry_voltage; ++ /* Telemetry Powers */ ++ u64 vddcr_telemetry_power; ++ u64 vddcr_soc_telemetry_power; ++ u64 vddcr_npu_telemetry_power; ++ u64 vddcr_lp_telemetry_power; ++ u64 vddcr_gfx_telemetry_power; ++ u64 vdd_misc_telemetry_power; ++ /* Throttlers - Fast PPT */ ++ u32 fppt_fused_limit; ++ u32 fppt_max_irm_limit; ++ u32 fppt_max_pbo_limit; ++ u32 fppt_limit; ++ u64 fppt_value_acc; ++ u32 fppt_residency_acc; ++ /* Throttlers - Slow PPT */ ++ u32 sppt_fused_limit; ++ u32 sppt_max_irm_limit; ++ u32 sppt_max_pbo_limit; ++ u32 sppt_limit; ++ u64 sppt_value_acc; ++ u32 sppt_residency_acc; ++ /* Throttlers - STAPM */ ++ u32 spl_fused_limit; ++ u32 spl_max_irm_limit; ++ u32 spl_max_pbo_limit; ++ u32 spl_limit; ++ u64 spl_value_acc; ++ u32 spl_residency_acc; ++ /* Throttlers - TDC VDDCR */ ++ u32 tdc_vddcr_fused_limit; ++ u32 tdc_vddcr_max_irm_limit; ++ u32 tdc_vddcr_max_pbo_limit; ++ u32 tdc_vddcr_limit; ++ u64 tdc_vddcr_value_acc; ++ u32 tdc_vddcr_residency_acc; ++ /* Throttlers - TDC VDDCR_SOC */ ++ u32 tdc_vddcr_soc_fused_limit; ++ u32 tdc_vddcr_soc_max_irm_limit; ++ u32 tdc_vddcr_soc_max_pbo_limit; ++ u32 tdc_vddcr_soc_limit; ++ u64 tdc_vddcr_soc_value_acc; ++ u32 tdc_vddcr_soc_residency_acc; ++ /* Throttlers - TDC VDDCR_NPU */ ++ u32 tdc_vddcr_npu_fused_limit; ++ u32 tdc_vddcr_npu_max_irm_limit; ++ u32 tdc_vddcr_npu_max_pbo_limit; ++ u32 tdc_vddcr_npu_limit; ++ u64 tdc_vddcr_npu_value_acc; ++ u32 tdc_vddcr_npu_residency_acc; ++ /* Throttlers - TDC VDDCR_LP */ ++ u32 tdc_vddcr_lp_fused_limit; ++ u32 tdc_vddcr_lp_max_irm_limit; ++ u32 tdc_vddcr_lp_max_pbo_limit; ++ u32 tdc_vddcr_lp_limit; ++ u64 tdc_vddcr_lp_value_acc; ++ u32 tdc_vddcr_lp_residency_acc; ++ /* Throttlers - TDC VDDCR_GFX */ ++ u32 tdc_vddcr_gfx_fused_limit; ++ u32 tdc_vddcr_gfx_max_irm_limit; ++ u32 tdc_vddcr_gfx_max_pbo_limit; ++ u32 tdc_vddcr_gfx_limit; ++ u64 tdc_vddcr_gfx_value_acc; ++ u32 tdc_vddcr_gfx_residency_acc; ++ /* Throttlers - EDC VDDCR */ ++ u32 edc_vddcr_fused_limit; ++ u32 edc_vddcr_max_irm_limit; ++ u32 edc_vddcr_max_pbo_limit; ++ u32 edc_vddcr_limit; ++ /* Throttlers - Thermal */ ++ u32 thm_fused_limit; ++ u32 thm_limit; ++ u64 thm_value_acc; ++ u32 thm_residency_acc; ++ u32 prochot_residency_acc; ++ u64 gfx_temp_acc; ++ u64 soc_temp_acc; ++ u32 p3t_fused_limit; ++ u64 p3t_value_acc; ++ /* Power */ ++ u64 system_power_acc; ++ u64 apu_power_acc; ++ u64 dgpu_power_acc; ++ u64 npu_power_acc; ++ /* Frequencies */ ++ u64 fclk_freq_eff_acc; ++ u64 memclk_freq_eff_acc; ++ u64 lclk_freq_eff_acc; ++ u64 gfxclk_freq_eff_acc; ++ u64 socclk_freq_eff_acc; ++ u64 vclk_freq_eff_acc; ++ u64 vpeclk_freq_eff_acc; ++ u64 aieclk_freq_eff_acc; ++ u64 npuhclk_freq_eff_acc; ++ /* Bandwidth */ ++ u64 dram_read_bandwidth; ++ u64 dram_write_bandwidth; ++ /* Activity Monitors */ ++ u64 gfx_busy_acc; ++ u64 vcn_busy_acc; ++ u64 npu_busy_acc[3]; ++ /* STT Limits */ ++ u32 stt_min_limit; ++ u64 stt_apu_hotspot_temp_acc; ++ u64 stt_hs2_hotspot_temp_acc; ++ u32 stt_apu_temp_limit; ++ u64 stt_apu_skin_temp_acc; ++ /* Residencies */ ++ u64 cpuoff_residency_ccx0; ++ u64 cpuoff_residency_ccx1; ++ u64 cpuoff_residency_ccx2; ++ u64 cpuoff_residency_ccx3; ++ /* DF-pstates */ ++ u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ /* System */ ++ u8 gfx_disabled; ++ u8 spare2[3]; ++ u32 gfxclk_fmax; ++ u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ /* Overclock Capable */ ++ u8 cpu_precise_and_direct_oc_capable; ++ u8 gfx_precise_and_direct_oc_capable; ++ u8 pbo_basic_oc_capable; ++ u8 pbo_advanced_oc_capable; ++ u8 pbo_nitro_oc_capable; ++ u8 memory_and_fabric_oc_capable; ++ u8 misc_oc_capable; ++ u8 extreme_cold_oc_capable; ++ u8 down_config_control_capable; ++ u8 spare0[3]; ++ /* Overclock Status */ ++ u32 fit_limit_scalar; ++ u8 ln2_enabled; ++ u8 cpu_precise_and_direct_oc_enabled; ++ u8 gfx_precise_and_direct_oc_enabled; ++ u8 spare1[2]; ++ /* Voltage Guardband in PSM count */ ++ s8 psm_guardband[5][5][3]; ++ s32 core_power_limit_offset; ++ u32 max_freq_offset[5]; ++ u64 npu_temp_acc; ++ u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 cclk_fboost; ++ /* PMF */ ++ u32 pmf_fast_apu_ppt_limit; ++ u64 pmf_fast_apu_ppt_value_acc; ++ u32 pmf_fast_apu_ppt_residency_acc; ++ u32 pmf_slow_apu_ppt_limit; ++ u64 pmf_slow_apu_ppt_value_acc; ++ u32 pmf_slow_apu_ppt_residency_acc; ++ u32 pmf_fast_spm_limit; ++ u64 pmf_fast_spm_value_acc; ++ u32 pmf_fast_spm_residency_acc; ++ u32 pmf_slow_spm_limit; ++ u64 pmf_slow_spm_value_acc; ++ u32 pmf_slow_spm_residency_acc; ++ u32 spare3[5]; ++} __packed __aligned(4); ++ ++struct amd_pmf_metrics_ccx { ++ u64 core_c0[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_cc6[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_freq[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_freqeff[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_temp[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_power[AMD_PMF_NUM_MAX_CORES]; ++} __packed __aligned(4); ++ ++struct amd_pmf_metrics_v3 { ++ struct amd_pmf_metrics_iod iod; ++ struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX]; ++} __packed __aligned(4); ++ + struct smu_pmf_metrics_v2 { + u16 core_frequency[16]; /* MHz */ + u16 core_power[16]; /* mW */ +@@ -391,6 +604,19 @@ struct pmf_cbi_ring_buffer { + int tail; + }; + ++/* SoC-specific SMU mailbox register offsets */ ++struct amd_pmf_smu_regs { ++ u32 msg_reg; ++ u32 resp_reg; ++ u32 arg_reg[3]; ++}; ++ ++struct amd_pmf_arg_data { ++ u32 lo; ++ u32 hi; ++ u32 size; ++}; ++ + struct amd_pmf_dev { + void __iomem *regbase; + void __iomem *smu_virt_addr; +@@ -442,6 +668,14 @@ struct amd_pmf_dev { + struct pmf_cbi_ring_buffer cbi_buf; + struct mutex cbi_mutex; /* Protects ring buffer access */ + struct mutex metrics_mutex; ++ u32 bios_output[BIOS_OUTPUT_MAX]; ++ const struct amd_pmf_smu_regs *smu_regs; ++ void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */ ++ phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */ ++ struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */ ++ struct amd_pmf_arg_data dram_addr; ++ struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */ ++ bool npu_metrics_have_prev; + }; + + struct apmf_sps_prop_granular_v2 { +@@ -680,14 +914,6 @@ enum system_state { + SYSTEM_STATE_MAX, + }; + +-enum ta_slider { +- TA_BEST_BATTERY, +- TA_BETTER_BATTERY, +- TA_BETTER_PERFORMANCE, +- TA_BEST_PERFORMANCE, +- TA_MAX, +-}; +- + struct amd_pmf_pb_bitmap { + const char *name; + u32 bit_mask; +@@ -719,20 +945,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = { + {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)}, + }; + +-enum platform_type { +- PTYPE_UNKNOWN = 0, +- LID_CLOSE, +- CLAMSHELL, +- FLAT, +- TENT, +- STAND, +- TABLET, +- BOOK, +- PRESENTATION, +- PULL_FWD, +- PTYPE_INVALID = 0xf, +-}; +- + /* Command ids for TA communication */ + enum ta_pmf_command { + TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE, +@@ -871,6 +1083,10 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer); + int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag); + u32 fixp_q88_fromint(u32 val); + int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev); ++void amd_pmf_set_device(struct device *p_device); ++ ++/* Metrics layer */ ++int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev); + + /* SPS Layer */ + int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf); +@@ -923,9 +1139,19 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq + void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); + int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); ++u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index); + + int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); + void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); + int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); + ++/* Util Layer */ ++#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT) ++int amd_pmf_cdev_register(struct amd_pmf_dev *dev); ++void amd_pmf_cdev_unregister(void); ++#else ++static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; } ++static inline void amd_pmf_cdev_unregister(void) {} ++#endif ++ + #endif /* PMF_H */ +diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c +--- a/drivers/platform/x86/amd/pmf/spc.c ++++ b/drivers/platform/x86/amd/pmf/spc.c +@@ -10,6 +10,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -17,61 +18,7 @@ + #include "pmf.h" + + #ifdef CONFIG_AMD_PMF_DEBUG +-static const char *platform_type_as_str(u16 platform_type) +-{ +- switch (platform_type) { +- case CLAMSHELL: +- return "CLAMSHELL"; +- case FLAT: +- return "FLAT"; +- case TENT: +- return "TENT"; +- case STAND: +- return "STAND"; +- case TABLET: +- return "TABLET"; +- case BOOK: +- return "BOOK"; +- case PRESENTATION: +- return "PRESENTATION"; +- case PULL_FWD: +- return "PULL_FWD"; +- default: +- return "UNKNOWN"; +- } +-} +- +-static const char *laptop_placement_as_str(u16 device_state) +-{ +- switch (device_state) { +- case ON_TABLE: +- return "ON_TABLE"; +- case ON_LAP_MOTION: +- return "ON_LAP_MOTION"; +- case IN_BAG: +- return "IN_BAG"; +- case OUT_OF_BAG: +- return "OUT_OF_BAG"; +- default: +- return "UNKNOWN"; +- } +-} +- +-static const char *ta_slider_as_str(unsigned int state) +-{ +- switch (state) { +- case TA_BEST_PERFORMANCE: +- return "PERFORMANCE"; +- case TA_BETTER_PERFORMANCE: +- return "BALANCED"; +- case TA_BEST_BATTERY: +- return "POWER_SAVER"; +- default: +- return "Unknown TA Slider State"; +- } +-} +- +-static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) ++u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + { + switch (index) { + case 0 ... 1: +@@ -82,13 +29,15 @@ static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int + return 0; + } + } ++EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); + + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) + { + int i; + + dev_dbg(dev->dev, "==== TA inputs START ====\n"); +- dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider)); ++ dev_dbg(dev->dev, "Slider State: %s\n", ++ amd_pmf_get_slider_position(in->ev_info.power_slider)); + dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source)); + dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage); + dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design); +@@ -102,9 +51,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * + dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open"); + dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away"); + dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light); +- dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type)); ++ dev_dbg(dev->dev, "Platform type: %s\n", ++ amd_pmf_get_platform_type(in->ev_info.platform_type)); + dev_dbg(dev->dev, "Laptop placement: %s\n", +- laptop_placement_as_str(in->ev_info.device_state)); ++ amd_pmf_get_laptop_placement(in->ev_info.device_state)); + for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) + dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1, + amd_pmf_get_ta_custom_bios_inputs(in, i)); +@@ -287,14 +237,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_ + switch (dev->current_profile) { + case PLATFORM_PROFILE_PERFORMANCE: + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: +- val = TA_BEST_PERFORMANCE; ++ val = AMD_PMF_TA_BEST_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: +- val = TA_BETTER_PERFORMANCE; ++ val = AMD_PMF_TA_BETTER_PERFORMANCE; + break; + case PLATFORM_PROFILE_LOW_POWER: + case PLATFORM_PROFILE_QUIET: +- val = TA_BEST_BATTERY; ++ val = AMD_PMF_TA_BEST_BATTERY; + break; + default: + dev_err(dev->dev, "Unknown Platform Profile.\n"); +diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c +--- a/drivers/platform/x86/amd/pmf/tee-if.c ++++ b/drivers/platform/x86/amd/pmf/tee-if.c +@@ -8,7 +8,9 @@ + * Author: Shyam Sundar S K + */ + ++#include + #include ++#include + #include + #include + #include "pmf.h" +@@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx) + + static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action) + { +- u32 bios_idx; ++ int bios_idx; ++ int ret; + + bios_idx = amd_pmf_get_bios_output_idx(action->action_index); ++ if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) { ++ dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx); ++ return; ++ } + +- amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); ++ ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); ++ if (!ret) ++ pdev->bios_output[bios_idx] = action->value; + } + + static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out) +diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/pmf/util.c +@@ -0,0 +1,153 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * AMD Platform Management Framework Util Layer ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * Authors: Shyam Sundar S K ++ * Sanket Goswami ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmf.h" ++ ++static struct amd_pmf_dev *pmf_dev_handle; ++static DEFINE_MUTEX(pmf_util_lock); ++ ++static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info) ++{ ++ struct ta_pmf_shared_memory *ta_sm = NULL; ++ struct ta_pmf_enact_table *in = NULL; ++ int idx; ++ ++ if (!pdev || !info) ++ return -EINVAL; ++ ++ if (!pdev->shbuf) ++ return -EINVAL; ++ ++ ta_sm = pdev->shbuf; ++ in = &ta_sm->pmf_input.enact_table; ++ ++ /* Set size */ ++ info->size = sizeof(*info); ++ ++ /* PMF Feature support flags */ ++ if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE)) ++ info->features_supported |= AMD_PMF_FEAT_AUTO_MODE; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) ++ info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER; ++ if (pdev->smart_pc_enabled) ++ info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC)) ++ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC)) ++ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC; ++ ++ /* Device States */ ++ info->platform_type = in->ev_info.platform_type; ++ info->laptop_placement = in->ev_info.device_state; ++ info->lid_state = in->ev_info.lid_state; ++ info->user_presence = in->ev_info.user_present; ++ info->slider_position = in->ev_info.power_slider; ++ ++ /* Thermal and Power Metrics */ ++ info->power_source = in->ev_info.power_source; ++ info->skin_temp = in->ev_info.skin_temperature; ++ info->gfx_busy = in->ev_info.gfx_busy; ++ info->ambient_light = in->ev_info.ambient_light; ++ info->avg_c0_residency = in->ev_info.avg_c0residency; ++ info->max_c0_residency = in->ev_info.max_c0residency; ++ info->socket_power = in->ev_info.socket_power; ++ ++ /* Custom BIOS input parameters */ ++ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) ++ info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx); ++ ++ /* BIOS output parameters */ ++ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) ++ info->bios_output[idx] = pdev->bios_output[idx]; ++ ++ return 0; ++} ++ ++static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ struct amd_pmf_dev *pdev = filp->private_data; ++ void __user *argp = (void __user *)arg; ++ struct amd_pmf_info info = {}; ++ size_t copy_size; ++ __u64 user_size; ++ int ret; ++ ++ if (cmd != IOCTL_AMD_PMF_POPULATE_DATA) ++ return -ENOTTY; ++ ++ /* First read just the size field from userspace */ ++ if (copy_from_user(&user_size, argp, sizeof(user_size))) ++ return -EFAULT; ++ ++ guard(mutex)(&pmf_util_lock); ++ ret = amd_pmf_populate_data(pdev, &info); ++ if (ret) ++ return ret; ++ ++ copy_size = min_t(size_t, user_size, sizeof(info)); ++ ++ /* Set actual size being copied */ ++ info.size = copy_size; ++ ++ if (copy_to_user(argp, &info, copy_size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int amd_pmf_open(struct inode *inode, struct file *filp) ++{ ++ guard(mutex)(&pmf_util_lock); ++ if (!pmf_dev_handle) ++ return -ENODEV; ++ ++ filp->private_data = pmf_dev_handle; ++ return 0; ++} ++ ++static const struct file_operations pmf_if_ops = { ++ .owner = THIS_MODULE, ++ .open = amd_pmf_open, ++ .unlocked_ioctl = amd_pmf_set_ioctl, ++}; ++ ++static struct miscdevice amd_pmf_util_if = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = "amdpmf_interface", ++ .fops = &pmf_if_ops, ++}; ++ ++int amd_pmf_cdev_register(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ guard(mutex)(&pmf_util_lock); ++ pmf_dev_handle = dev; ++ ret = misc_register(&amd_pmf_util_if); ++ if (ret) ++ pmf_dev_handle = NULL; ++ ++ return ret; ++} ++ ++void amd_pmf_cdev_unregister(void) ++{ ++ guard(mutex)(&pmf_util_lock); ++ if (pmf_dev_handle) ++ misc_deregister(&amd_pmf_util_if); ++ pmf_dev_handle = NULL; ++} +diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c +--- a/drivers/platform/x86/asus-armoury.c ++++ b/drivers/platform/x86/asus-armoury.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -93,6 +94,8 @@ struct asus_armoury_priv { + + u32 mini_led_dev_id; + u32 gpu_mux_dev_id; ++ ++ bool requires_fan_curve; + }; + + static struct asus_armoury_priv asus_armoury = { +@@ -216,6 +219,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr, + u32 result; + int err; + ++ /* On some models, PPT changes require an active fan curve */ ++ if (asus_armoury.requires_fan_curve) { ++ switch (dev_id) { ++ case ASUS_WMI_DEVID_PPT_PL1_SPL: ++ case ASUS_WMI_DEVID_PPT_PL2_SPPT: ++ case ASUS_WMI_DEVID_PPT_PL3_FPPT: ++ case ASUS_WMI_DEVID_PPT_APU_SPPT: ++ case ASUS_WMI_DEVID_PPT_PLAT_SPPT: ++ if (!asus_wmi_custom_fan_curve_is_enabled()) { ++ pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n"); ++ return -EBUSY; ++ } ++ break; ++ } ++ } ++ + /* + * Prevent developers from bricking devices or issuing dangerous + * commands that can be difficult or impossible to recover from. +@@ -989,10 +1008,11 @@ static int asus_fw_attr_add(void) + /* Init / exit ****************************************************************/ + + /* Set up the min/max and defaults for ROG tunables */ +-static void init_rog_tunables(void) ++static int init_rog_tunables(void) + { + const struct power_limits *ac_limits, *dc_limits; +- struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL; ++ struct rog_tunables *ac_rog_tunables __free(kfree) = NULL; ++ struct rog_tunables *dc_rog_tunables __free(kfree) = NULL; + const struct power_data *power_data; + const struct dmi_system_id *dmi_id; + +@@ -1000,24 +1020,25 @@ static void init_rog_tunables(void) + dmi_id = dmi_first_match(power_limits); + if (!dmi_id) { + pr_warn("No matching power limits found for this system\n"); +- return; ++ return 0; + } + + /* Get the power data for this system */ + power_data = dmi_id->driver_data; + if (!power_data) { + pr_info("No power data available for this system\n"); +- return; ++ return 0; + } + ++ asus_armoury.requires_fan_curve = power_data->requires_fan_curve; ++ + /* Initialize AC power tunables */ + ac_limits = power_data->ac_data; + if (ac_limits) { +- ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); ++ ac_rog_tunables = kzalloc_obj(*ac_rog_tunables); + if (!ac_rog_tunables) +- goto err_nomem; ++ return -ENOMEM; + +- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables; + ac_rog_tunables->power_limits = ac_limits; + + /* Set initial AC values */ +@@ -1060,13 +1081,10 @@ static void init_rog_tunables(void) + /* Initialize DC power tunables */ + dc_limits = power_data->dc_data; + if (dc_limits) { +- dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); +- if (!dc_rog_tunables) { +- kfree(ac_rog_tunables); +- goto err_nomem; +- } ++ dc_rog_tunables = kzalloc_obj(*dc_rog_tunables); ++ if (!dc_rog_tunables) ++ return -ENOMEM; + +- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables; + dc_rog_tunables->power_limits = dc_limits; + + /* Set initial DC values */ +@@ -1106,15 +1124,16 @@ static void init_rog_tunables(void) + pr_debug("No DC PPT limits defined\n"); + } + +- return; ++ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = no_free_ptr(ac_rog_tunables); ++ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(dc_rog_tunables); + +-err_nomem: +- pr_err("Failed to allocate memory for tunables\n"); ++ return 0; + } + + static int __init asus_fw_init(void) + { + char *wmi_uid; ++ int err; + + wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); + if (!wmi_uid) +@@ -1127,10 +1146,21 @@ static int __init asus_fw_init(void) + if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) + return -ENODEV; + +- init_rog_tunables(); ++ err = init_rog_tunables(); ++ if (err) ++ return err; + + /* Must always be last step to ensure data is available */ +- return asus_fw_attr_add(); ++ err = asus_fw_attr_add(); ++ if (err) ++ goto err_free_tunables; ++ ++ return 0; ++ ++err_free_tunables: ++ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); ++ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); ++ return err; + } + + static void __exit asus_fw_exit(void) +diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h +--- a/drivers/platform/x86/asus-armoury.h ++++ b/drivers/platform/x86/asus-armoury.h +@@ -369,6 +369,38 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA401KM"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .nv_dynamic_boost_max = 15, ++ .nv_dynamic_boost_min = 5, ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .nv_tgp_max = 95, ++ .nv_tgp_min = 55, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .dc_data = &(struct power_limits) { ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl2_sppt_max = 44, ++ .ppt_pl2_sppt_min = 31, ++ .ppt_pl3_fppt_max = 65, ++ .ppt_pl3_fppt_min = 45, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401UM"), +@@ -754,6 +786,40 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA608WV"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .nv_dynamic_boost_max = 25, ++ .nv_dynamic_boost_min = 5, ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .nv_tgp_max = 115, ++ .nv_tgp_min = 55, ++ .ppt_pl1_spl_max = 90, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_max = 90, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 90, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .dc_data = &(struct power_limits) { ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_def = 54, ++ .ppt_pl2_sppt_max = 65, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 65, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA617NS"), +@@ -909,6 +975,32 @@ static const struct dmi_system_id power_limits[] = { + .requires_fan_curve = true, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 85, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 45, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 60, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX607VU"), +@@ -2079,6 +2171,35 @@ static const struct dmi_system_id power_limits[] = { + .requires_fan_curve = true, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G635LX"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_def = 140, ++ .ppt_pl1_spl_max = 175, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 175, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 80, ++ .nv_tgp_max = 150, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 55, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 70, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G713PV"), +@@ -2322,6 +2443,35 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "HN7306EA"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_def = 60, ++ .ppt_pl1_spl_max = 85, ++ .ppt_pl2_sppt_min = 40, ++ .ppt_pl2_sppt_def = 70, ++ .ppt_pl2_sppt_max = 95, ++ .ppt_pl3_fppt_min = 50, ++ .ppt_pl3_fppt_def = 85, ++ .ppt_pl3_fppt_max = 115, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 60, ++ .ppt_pl2_sppt_min = 40, ++ .ppt_pl2_sppt_def = 55, ++ .ppt_pl2_sppt_max = 70, ++ .ppt_pl3_fppt_min = 50, ++ .ppt_pl3_fppt_def = 65, ++ .ppt_pl3_fppt_max = 85, ++ }, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "RC71"), +diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c +--- a/drivers/platform/x86/asus-laptop.c ++++ b/drivers/platform/x86/asus-laptop.c +@@ -42,8 +42,6 @@ + #define ASUS_LAPTOP_VERSION "0.42" + + #define ASUS_LAPTOP_NAME "Asus Laptop Support" +-#define ASUS_LAPTOP_CLASS "hotkey" +-#define ASUS_LAPTOP_DEVICE_NAME "Hotkey" + #define ASUS_LAPTOP_FILE KBUILD_MODNAME + #define ASUS_LAPTOP_PREFIX "\\_SB.ATKD." + +@@ -1524,9 +1522,8 @@ static void asus_acpi_notify(acpi_handle handle, u32 event, void *data) + + /* TODO Find a better way to handle events count. */ + count = asus->event_count[event % 128]++; +- acpi_bus_generate_netlink_event(asus->device->pnp.device_class, +- dev_name(&asus->device->dev), event, +- count); ++ acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev), ++ event, count); + + if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX) + event = ATKD_BRNUP; +@@ -1840,8 +1837,6 @@ static int asus_acpi_probe(struct platform_device *pdev) + if (!asus) + return -ENOMEM; + asus->handle = device->handle; +- strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME); +- strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS); + asus->device = device; + + asus_dmi_check(); +diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c +--- a/drivers/platform/x86/asus-nb-wmi.c ++++ b/drivers/platform/x86/asus-nb-wmi.c +@@ -632,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { + { KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */ + { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ + { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ ++ { KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */ + { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ + { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ + { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -4073,6 +4073,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) + return 0; + } + ++/* ++ * Returns true if at least one custom fan curve is active ++ * ++ * Used by asus-armoury to check if PPT writes will be accepted by the BIOS ++ * on models that require an active fan curve for TDP changes. ++ */ ++bool asus_wmi_custom_fan_curve_is_enabled(void) ++{ ++ struct fan_curve_data *curves; ++ struct asus_wmi *asus; ++ ++ guard(spinlock_irqsave)(&asus_ref.lock); ++ asus = asus_ref.asus; ++ if (!asus) ++ return false; ++ ++ curves = asus->custom_fan_curves; ++ ++ return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) || ++ (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) || ++ (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled); ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI"); ++ + /* Throttle thermal policy ****************************************************/ + static int throttle_thermal_policy_write(struct asus_wmi *asus) + { +diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c +--- a/drivers/platform/x86/dell/dell-smbios-base.c ++++ b/drivers/platform/x86/dell/dell-smbios-base.c +@@ -40,7 +40,7 @@ struct smbios_device { + struct list_head list; + struct device *device; + int priority; +- int (*call_fn)(struct calling_interface_buffer *arg); ++ smbios_callback_fn_t call_fn; + }; + + struct smbios_call { +@@ -146,7 +146,7 @@ int dell_smbios_error(int value) + } + EXPORT_SYMBOL_GPL(dell_smbios_error); + +-int dell_smbios_register_device(struct device *d, int priority, void *call_fn) ++int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn) + { + struct smbios_device *priv; + +@@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer) + goto out_smbios_call; + } + +- ret = selected->call_fn(buffer); ++ ret = selected->call_fn(selected->device, buffer); + + out_smbios_call: + mutex_unlock(&smbios_mutex); +diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c +--- a/drivers/platform/x86/dell/dell-smbios-smm.c ++++ b/drivers/platform/x86/dell/dell-smbios-smm.c +@@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy) + } + } + +-static int dell_smbios_smm_call(struct calling_interface_buffer *input) ++static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input) + { + struct smi_cmd command; + size_t size; +@@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input) + } + + /* When enabled this indicates that SMM won't work */ +-static bool test_wsmt_enabled(void) ++static bool test_wsmt_enabled(struct device *dev) + { + struct calling_interface_token *wsmt; + +@@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void) + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = wsmt->location; + buffer->output[0] = 99; +- dell_smbios_smm_call(buffer); ++ dell_smbios_smm_call(dev, buffer); + if (buffer->output[0] == 99) + return true; + +@@ -109,7 +109,7 @@ int init_dell_smbios_smm(void) + + dmi_walk(find_cmd_address, NULL); + +- if (test_wsmt_enabled()) { ++ if (test_wsmt_enabled(&platform_device->dev)) { + pr_debug("Disabling due to WSMT enabled\n"); + ret = -ENODEV; + goto fail_wsmt; +diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c +--- a/drivers/platform/x86/dell/dell-smbios-wmi.c ++++ b/drivers/platform/x86/dell/dell-smbios-wmi.c +@@ -6,21 +6,20 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +-#include + #include + #include + #include ++#include + #include + #include + #include + #include "dell-smbios.h" + #include "dell-wmi-descriptor.h" + +-static DEFINE_MUTEX(call_mutex); +-static DEFINE_MUTEX(list_mutex); + static int wmi_supported; + + struct misc_bios_flags_structure { +@@ -32,21 +31,16 @@ struct misc_bios_flags_structure { + #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" + + struct wmi_smbios_priv { ++ struct mutex call_lock; /* Protects the content of the SMBIOS buffer */ + struct dell_wmi_smbios_buffer *buf; +- struct list_head list; + struct wmi_device *wdev; + struct device *child; + u64 req_buf_size; + struct miscdevice char_dev; + }; +-static LIST_HEAD(wmi_list); + +-static inline struct wmi_smbios_priv *get_first_smbios_priv(void) +-{ +- return list_first_entry_or_null(&wmi_list, +- struct wmi_smbios_priv, +- list); +-} ++static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */ ++static struct wmi_smbios_priv *chardev_priv; + + static int run_smbios_call(struct wmi_device *wdev) + { +@@ -80,50 +74,36 @@ static int run_smbios_call(struct wmi_device *wdev) + return 0; + } + +-static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) ++static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer) + { +- struct wmi_smbios_priv *priv; ++ struct wmi_smbios_priv *priv = dev_get_drvdata(dev); + size_t difference; + size_t size; + int ret; + +- mutex_lock(&call_mutex); +- priv = get_first_smbios_priv(); +- if (!priv) { +- ret = -ENODEV; +- goto out_wmi_call; +- } +- + size = sizeof(struct calling_interface_buffer); + difference = priv->req_buf_size - sizeof(u64) - size; + ++ guard(mutex)(&priv->call_lock); ++ + memset(&priv->buf->ext, 0, difference); + memcpy(&priv->buf->std, buffer, size); + ret = run_smbios_call(priv->wdev); + memcpy(buffer, &priv->buf->std, size); +-out_wmi_call: +- mutex_unlock(&call_mutex); + + return ret; + } + +-static int dell_smbios_wmi_open(struct inode *inode, struct file *filp) +-{ +- struct wmi_smbios_priv *priv; +- +- priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev); +- filp->private_data = priv; +- +- return nonseekable_open(inode, filp); +-} +- + static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length, + loff_t *offset) + { +- struct wmi_smbios_priv *priv = filp->private_data; ++ guard(rwsem_read)(&chardev_lock); + +- return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size, +- sizeof(priv->req_buf_size)); ++ if (!chardev_priv) ++ return -ENODEV; ++ ++ return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size, ++ sizeof(chardev_priv->req_buf_size)); + } + + static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, +@@ -167,22 +147,22 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, + static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { + struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg; +- struct wmi_smbios_priv *priv = filp->private_data; +- long ret; + + if (cmd != DELL_WMI_SMBIOS_CMD) + return -ENOIOCTLCMD; + +- mutex_lock(&call_mutex); +- ret = dell_smbios_wmi_do_ioctl(priv, input); +- mutex_unlock(&call_mutex); ++ guard(rwsem_read)(&chardev_lock); + +- return ret; ++ if (!chardev_priv) ++ return -ENODEV; ++ ++ guard(mutex)(&chardev_priv->call_lock); ++ ++ return dell_smbios_wmi_do_ioctl(chardev_priv, input); + } + + static const struct file_operations dell_smbios_wmi_fops = { + .owner = THIS_MODULE, +- .open = dell_smbios_wmi_open, + .read = dell_smbios_wmi_read, + .unlocked_ioctl = dell_smbios_wmi_ioctl, + .compat_ioctl = compat_ptr_ioctl, +@@ -195,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data) + misc_deregister(char_dev); + } + ++static void dell_smbios_wmi_clear_chardev(void *data) ++{ ++ guard(rwsem_write)(&chardev_lock); ++ ++ chardev_priv = NULL; ++} ++ + static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv) + { + int ret; + ++ scoped_guard(rwsem_write, &chardev_lock) { ++ /* We can only have a single chardev at a time */ ++ if (chardev_priv) ++ return -EBUSY; ++ ++ chardev_priv = priv; ++ } ++ ++ ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL); ++ if (ret < 0) ++ return ret; ++ + priv->char_dev.minor = MISC_DYNAMIC_MINOR; + priv->char_dev.name = "wmi/dell-smbios"; + priv->char_dev.fops = &dell_smbios_wmi_fops; +@@ -254,31 +253,20 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) + if (!priv->buf) + return -ENOMEM; + ++ ret = devm_mutex_init(&wdev->dev, &priv->call_lock); ++ if (ret) ++ return ret; ++ + ret = dell_smbios_wmi_register_chardev(priv); + if (ret) + return ret; + +- ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); +- if (ret) +- return ret; +- +- mutex_lock(&list_mutex); +- list_add_tail(&priv->list, &wmi_list); +- mutex_unlock(&list_mutex); +- +- return 0; ++ return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); + } + + static void dell_smbios_wmi_remove(struct wmi_device *wdev) + { +- struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); +- +- mutex_lock(&call_mutex); +- mutex_lock(&list_mutex); +- list_del(&priv->list); +- mutex_unlock(&list_mutex); + dell_smbios_unregister_device(&wdev->dev); +- mutex_unlock(&call_mutex); + } + + static const struct wmi_device_id dell_smbios_wmi_id_table[] = { +@@ -316,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = { + .probe = dell_smbios_wmi_probe, + .remove = dell_smbios_wmi_remove, + .id_table = dell_smbios_wmi_id_table, ++ .no_singleton = true, + }; + + int init_dell_smbios_wmi(void) +diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h +--- a/drivers/platform/x86/dell/dell-smbios.h ++++ b/drivers/platform/x86/dell/dell-smbios.h +@@ -47,6 +47,8 @@ + + struct notifier_block; + ++typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer); ++ + struct calling_interface_token { + u16 tokenID; + u16 location; +@@ -64,7 +66,7 @@ struct calling_interface_structure { + struct calling_interface_token tokens[]; + } __packed; + +-int dell_smbios_register_device(struct device *d, int priority, void *call_fn); ++int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn); + void dell_smbios_unregister_device(struct device *d); + + int dell_smbios_error(int value); +diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c +--- a/drivers/platform/x86/dell/dell-wmi-ddv.c ++++ b/drivers/platform/x86/dell/dell-wmi-ddv.c +@@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met + static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, + u32 arg, struct dell_wmi_buffer **result) + { +- struct dell_wmi_buffer *buffer; + struct wmi_buffer output; + size_t buffer_size; + int ret; + +- ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer)); ++ ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer)); + if (ret < 0) + return ret; + +- buffer = output.data; +- if (!le32_to_cpu(buffer->raw_size)) { +- ret = -ENODATA; ++ struct dell_wmi_buffer *buffer __free(kfree) = output.data; + +- goto err_free; +- } ++ if (!le32_to_cpu(buffer->raw_size)) ++ return -ENODATA; + + buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size)); + if (buffer_size > output.length) { + dev_warn(&wdev->dev, + FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n", + buffer_size, output.length); +- ret = -EMSGSIZE; +- +- goto err_free; ++ return -EMSGSIZE; + } + +- *result = buffer; ++ *result = no_free_ptr(buffer); + + return 0; +- +-err_free: +- kfree(output.data); +- +- return ret; + } + + static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, +diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c +--- a/drivers/platform/x86/eeepc-laptop.c ++++ b/drivers/platform/x86/eeepc-laptop.c +@@ -34,8 +34,6 @@ + #define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" + #define EEEPC_LAPTOP_FILE "eeepc" + +-#define EEEPC_ACPI_CLASS "hotkey" +-#define EEEPC_ACPI_DEVICE_NAME "Hotkey" + #define EEEPC_ACPI_HID "ASUS010" + + MODULE_AUTHOR("Corentin Chary, Eric Cooper"); +@@ -1214,8 +1212,7 @@ static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data) + if (event > ACPI_MAX_SYS_NOTIFY) + return; + count = eeepc->event_count[event % 128]++; +- acpi_bus_generate_netlink_event(device->pnp.device_class, +- dev_name(&device->dev), event, ++ acpi_bus_generate_netlink_event("hotkey", dev_name(&device->dev), event, + count); + + /* Brightness events are special */ +@@ -1376,8 +1373,6 @@ static int eeepc_acpi_probe(struct platform_device *pdev) + if (!eeepc) + return -ENOMEM; + eeepc->handle = device->handle; +- strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); +- strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS); + eeepc->device = device; + + platform_set_drvdata(pdev, eeepc); +diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c +--- a/drivers/platform/x86/fujitsu-laptop.c ++++ b/drivers/platform/x86/fujitsu-laptop.c +@@ -56,7 +56,6 @@ + + #define FUJITSU_LCD_N_LEVELS 8 + +-#define ACPI_FUJITSU_CLASS "fujitsu" + #define ACPI_FUJITSU_BL_HID "FUJ02B1" + #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver" + #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1" +@@ -466,7 +465,7 @@ static int acpi_fujitsu_bl_input_setup(struct device *dev) + snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0", + acpi_device_hid(device)); + +- priv->input->name = acpi_device_name(device); ++ priv->input->name = ACPI_FUJITSU_BL_DEVICE_NAME; + priv->input->phys = priv->phys; + priv->input->id.bustype = BUS_HOST; + priv->input->id.product = 0x06; +@@ -546,13 +545,11 @@ static int acpi_fujitsu_bl_probe(struct platform_device *pdev) + return -ENOMEM; + + fujitsu_bl = priv; +- strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); + + platform_set_drvdata(pdev, priv); + +- pr_info("ACPI: %s [%s]\n", +- acpi_device_name(device), acpi_device_bid(device)); ++ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_BL_DEVICE_NAME, ++ acpi_device_bid(device)); + + if (get_max_brightness(&pdev->dev) <= 0) + priv->max_brightness = FUJITSU_LCD_N_LEVELS; +@@ -681,7 +678,7 @@ static int acpi_fujitsu_laptop_input_setup(struct device *dev) + snprintf(priv->phys, sizeof(priv->phys), "%s/input0", + acpi_device_hid(device)); + +- priv->input->name = acpi_device_name(device); ++ priv->input->name = ACPI_FUJITSU_LAPTOP_DEVICE_NAME; + priv->input->phys = priv->phys; + priv->input->id.bustype = BUS_HOST; + +@@ -1012,9 +1009,6 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) + WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended."); + fext = &pdev->dev; + +- strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); +- + platform_set_drvdata(pdev, priv); + + /* kfifo */ +@@ -1024,8 +1018,8 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) + if (ret) + return ret; + +- pr_info("ACPI: %s [%s]\n", +- acpi_device_name(device), acpi_device_bid(device)); ++ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_LAPTOP_DEVICE_NAME, ++ acpi_device_bid(device)); + + while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 && + i++ < MAX_HOTKEY_RINGBUFFER_SIZE) +diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c +--- a/drivers/platform/x86/fujitsu-tablet.c ++++ b/drivers/platform/x86/fujitsu-tablet.c +@@ -22,8 +22,6 @@ + + #define MODULENAME "fujitsu-tablet" + +-#define ACPI_FUJITSU_CLASS "fujitsu" +- + #define INVERT_TABLET_MODE_BIT 0x01 + #define INVERT_DOCK_STATE_BIT 0x02 + #define FORCE_TABLET_MODE_IF_UNDOCK 0x04 +@@ -160,6 +158,7 @@ static struct { + struct fujitsu_config config; + unsigned long prev_keymask; + ++ char name[17]; + char phys[21]; + + int irq; +@@ -458,14 +457,10 @@ static int acpi_fujitsu_probe(struct platform_device *pdev) + if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) + return -ENODEV; + +- sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev)); +- sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS); ++ scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev)); ++ scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev)); + +- snprintf(fujitsu.phys, sizeof(fujitsu.phys), +- "%s/input0", acpi_device_hid(adev)); +- +- error = input_fujitsu_setup(&pdev->dev, +- acpi_device_name(adev), fujitsu.phys); ++ error = input_fujitsu_setup(&pdev->dev, fujitsu.name, fujitsu.phys); + if (error) + return error; + +diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c +--- a/drivers/platform/x86/hp/hp-wmi.c ++++ b/drivers/platform/x86/hp/hp-wmi.c +@@ -14,6 +14,8 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include ++#include + #include + #include + #include +@@ -23,6 +25,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -58,6 +61,12 @@ enum hp_ec_offsets { + #define HP_FAN_SPEED_AUTOMATIC 0x00 + #define HP_POWER_LIMIT_DEFAULT 0x00 + #define HP_POWER_LIMIT_NO_CHANGE 0xFF ++#define HPWMI_MUX_MODE_UMA BIT(0) ++#define HPWMI_MUX_MODE_HYBRID BIT(1) ++#define HPWMI_MUX_MODE_DISCRETE BIT(2) ++#define HPWMI_MUX_MODE_OPTIMUS BIT(3) ++#define HPWMI_MUX_MODE_MASK GENMASK(6, 0) ++#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE) + + #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required + +@@ -133,24 +142,41 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { + .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, + }; + ++struct hp_wmi_fan_profile_params { ++ int (*get_fan_speed)(int fan); ++ bool fan_table; ++}; ++ + struct hp_wmi_board_params { + const struct thermal_profile_params *thermal_profile; ++ const struct hp_wmi_fan_profile_params *fan_profile; ++}; ++ ++static int hp_wmi_get_fan_speed_victus_s(int fan); ++ ++static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = { ++ .get_fan_speed = hp_wmi_get_fan_speed_victus_s, ++ .fan_table = true, + }; + + static const struct hp_wmi_board_params victus_s_board_params = { + .thermal_profile = &victus_s_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_board_params = { + .thermal_profile = &omen_v1_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_legacy_board_params = { + .thermal_profile = &omen_v1_legacy_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { + .thermal_profile = &omen_v1_no_ec_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params *active_board_params; +@@ -181,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = { + "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", + "88F7", "88FD", "88FE", "88FF", + "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", +- "8A15", "8A42", ++ "8A15", "8A42", "8A43", + "8BAD", + "8C58", + "8E41", +@@ -217,6 +243,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") }, ++ .driver_data = (void *)&victus_s_board_params, ++ }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, + .driver_data = (void *)&omen_v1_legacy_board_params, +@@ -225,6 +255,14 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") }, ++ .driver_data = (void *)&omen_v1_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") }, ++ .driver_data = (void *)&omen_v1_board_params, ++ }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, + .driver_data = (void *)&omen_v1_board_params, +@@ -233,6 +271,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, + .driver_data = (void *)&victus_s_board_params, + }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") }, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, ++ }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, + .driver_data = (void *)&victus_s_board_params, +@@ -289,6 +331,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") }, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, ++ }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, +@@ -360,6 +406,7 @@ enum hp_wmi_commandtype { + HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_SYSTEM_DEVICE_MODE = 0x40, + HPWMI_THERMAL_PROFILE_QUERY = 0x4c, ++ HPWMI_GRAPHICS_MUX_QUERY = 0x52, + }; + + struct victus_power_limits { +@@ -528,9 +575,9 @@ struct hp_wmi_hwmon_priv { + struct mutex lock; /* protects mode, pwm */ + u8 min_rpm; + u8 max_rpm; +- int gpu_delta; + u8 mode; +- u8 pwm; ++ u8 cpu_pwm; ++ u8 gpu_pwm; + struct delayed_work keep_alive_dwork; + }; + +@@ -857,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) + return enabled; + } + +-static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) ++static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv) + { + u8 fan_speed[2]; +- int gpu_speed, ret; ++ int ret; + +- fan_speed[CPU_FAN] = speed; +- fan_speed[GPU_FAN] = speed; ++ if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC) ++ fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC; ++ else ++ fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv); + +- /* +- * GPU fan speed is always a little higher than CPU fan speed, we fetch +- * this delta value from the fan table during hwmon init. +- * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to +- * automatic mode. +- */ +- if (speed != HP_FAN_SPEED_AUTOMATIC) { +- gpu_speed = speed + priv->gpu_delta; +- fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX); +- } ++ if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC) ++ fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC; ++ else ++ fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv); + + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) +@@ -891,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) + + static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) + { +- return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); ++ priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC; ++ priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC; ++ return hp_wmi_fan_speed_set(priv); + } + + static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) +@@ -1152,12 +1197,134 @@ static int camera_shutter_input_setup(void) + return err; + } + ++static const u8 mux_bitmask_map[] = { ++ [0] = HPWMI_MUX_MODE_HYBRID, ++ [1] = HPWMI_MUX_MODE_DISCRETE, ++ [2] = HPWMI_MUX_MODE_OPTIMUS, ++ [3] = HPWMI_MUX_MODE_UMA, ++}; ++ ++static int hp_wmi_get_mux_supported_modes(u8 *supported) ++{ ++ u8 legacy_buffer[4] = {}; ++ u8 buffer[128] = {}; ++ u32 req_packet = 0; ++ int ret; ++ ++ if (!supported) ++ return -EINVAL; ++ ++ /* Try modern BIOS design data query (128-byte buffer) */ ++ ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, ++ buffer, zero_if_sup(req_packet), sizeof(buffer)); ++ if (ret == 0) { ++ *supported = buffer[7]; ++ return 0; ++ } ++ ++ /* ++ * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub. ++ * If the modern query is not supported, check if the MUX query endpoint ++ * responds to a read request. If it succeeds, the hardware has MUX ++ * capability but lacks the mode map, defaulting to Hybrid + Discrete. ++ */ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, ++ legacy_buffer, sizeof(legacy_buffer), 0); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ *supported = HPWMI_MUX_LEGACY_MASK; ++ return 0; ++} ++ ++static int hp_wmi_get_mux_mode(u8 *mode) ++{ ++ u8 buffer[4] = {}; ++ int ret; ++ ++ if (!mode) ++ return -EINVAL; ++ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, ++ buffer, sizeof(buffer), sizeof(buffer)); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ /* Mask the highest bit, which might be used as a BIOS status flag */ ++ *mode = buffer[0] & HPWMI_MUX_MODE_MASK; ++ return 0; ++} ++ ++static int hp_wmi_set_mux_mode(u8 mode) ++{ ++ u8 buffer[4] = { mode, 0x00, 0x00, 0x00 }; ++ int ret; ++ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE, ++ buffer, sizeof(buffer), sizeof(buffer)); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static ssize_t gpu_mux_mode_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ u8 mode; ++ int ret; ++ ++ ret = hp_wmi_get_mux_mode(&mode); ++ if (ret) ++ return ret; ++ ++ return sysfs_emit(buf, "%u\n", mode); ++} ++ ++static ssize_t gpu_mux_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ u32 requested; ++ u8 supported; ++ int ret; ++ ++ ret = kstrtou32(buf, 0, &requested); ++ if (ret) ++ return ret; ++ if (requested >= ARRAY_SIZE(mux_bitmask_map)) ++ return -EINVAL; ++ ++ ret = hp_wmi_get_mux_supported_modes(&supported); ++ if (ret) ++ return ret; ++ ++ /* Verify if the requested mode is allowed by the hardware mask */ ++ if (!(supported & mux_bitmask_map[requested])) ++ return -EOPNOTSUPP; ++ ++ ret = hp_wmi_set_mux_mode(requested); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ + static DEVICE_ATTR_RO(display); + static DEVICE_ATTR_RO(hddtemp); + static DEVICE_ATTR_RW(als); + static DEVICE_ATTR_RO(dock); + static DEVICE_ATTR_RO(tablet); + static DEVICE_ATTR_RW(postcode); ++static DEVICE_ATTR_RW(gpu_mux_mode); + + static struct attribute *hp_wmi_attrs[] = { + &dev_attr_display.attr, +@@ -1166,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { + &dev_attr_dock.attr, + &dev_attr_tablet.attr, + &dev_attr_postcode.attr, ++ &dev_attr_gpu_mux_mode.attr, + NULL, + }; + ATTRIBUTE_GROUPS(hp_wmi); +@@ -1817,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) + return is_victus_s_board; + } + ++static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void) ++{ ++ if (!active_board_params) ++ return NULL; ++ ++ return active_board_params->fan_profile; ++} ++ ++static bool hp_wmi_fan_control_supported(void) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ return params && params->get_fan_speed; ++} ++ ++static bool hp_wmi_fan_table_supported(void) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ return params && params->fan_table; ++} ++ ++static int hp_wmi_get_active_fan_speed(int fan) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ if (!params || !params->get_fan_speed) ++ return -EOPNOTSUPP; ++ ++ return params->get_fan_speed(fan); ++} ++ + static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, + bool *ppab_enable, + u8 *dstate, +@@ -2436,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) + + switch (priv->mode) { + case PWM_MODE_MAX: +- if (is_victus_s_thermal_profile()) { ++ if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; +@@ -2448,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; + case PWM_MODE_MANUAL: +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; +- ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); ++ ret = hp_wmi_fan_speed_set(priv); + if (ret < 0) + return ret; + mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; + case PWM_MODE_AUTO: +- if (is_victus_s_thermal_profile()) { ++ if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; +@@ -2481,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, + { + switch (type) { + case hwmon_pwm: +- if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) ++ if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) + return 0; + return 0644; + case hwmon_fan: +- if (is_victus_s_thermal_profile()) { +- if (hp_wmi_get_fan_speed_victus_s(channel) >= 0) ++ if (hp_wmi_fan_control_supported()) { ++ if (hp_wmi_get_active_fan_speed(channel) >= 0) + return 0444; + } else { + if (hp_wmi_get_fan_speed(channel) >= 0) +@@ -2510,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + priv = dev_get_drvdata(dev); + switch (type) { + case hwmon_fan: +- if (is_victus_s_thermal_profile()) +- ret = hp_wmi_get_fan_speed_victus_s(channel); ++ if (hp_wmi_fan_control_supported()) ++ ret = hp_wmi_get_active_fan_speed(channel); + else + ret = hp_wmi_get_fan_speed(channel); + if (ret < 0) +@@ -2520,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + return 0; + case hwmon_pwm: + if (attr == hwmon_pwm_input) { +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + +- rpm = hp_wmi_get_fan_speed_victus_s(channel); ++ rpm = hp_wmi_get_active_fan_speed(channel); + if (rpm < 0) + return rpm; + *val = rpm_to_pwm(rpm / 100, priv); +@@ -2550,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) + { + struct hp_wmi_hwmon_priv *priv; +- int rpm; ++ int cpu_rpm, gpu_rpm; + + priv = dev_get_drvdata(dev); + guard(mutex)(&priv->lock); + switch (type) { + case hwmon_pwm: + if (attr == hwmon_pwm_input) { +- if (!is_victus_s_thermal_profile()) ++ int rpm; ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + /* PWM input is invalid when not in manual mode */ + if (priv->mode != PWM_MODE_MANUAL) +@@ -2566,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + /* ensure PWM input is within valid fan speeds */ + rpm = pwm_to_rpm(val, priv); + rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); +- priv->pwm = rpm_to_pwm(rpm, priv); ++ if (channel == CPU_FAN) ++ priv->cpu_pwm = rpm_to_pwm(rpm, priv); ++ else if (channel == GPU_FAN) ++ priv->gpu_pwm = rpm_to_pwm(rpm, priv); + return hp_wmi_apply_fan_settings(priv); + } + switch (val) { +@@ -2574,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + priv->mode = PWM_MODE_MAX; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_MANUAL: +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + /* + * When switching to manual mode, set fan speed to + * current RPM values to ensure a smooth transition. + */ +- rpm = hp_wmi_get_fan_speed_victus_s(channel); +- if (rpm < 0) +- return rpm; +- priv->pwm = rpm_to_pwm(rpm / 100, priv); ++ cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN); ++ if (cpu_rpm < 0) ++ return cpu_rpm; ++ gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN); ++ if (gpu_rpm < 0) ++ return gpu_rpm; ++ priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv); ++ priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv); + priv->mode = PWM_MODE_MANUAL; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_AUTO: +@@ -2599,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + + static const struct hwmon_channel_info * const info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), +- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), ++ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT), + NULL + }; + +@@ -2640,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) + struct victus_s_fan_table *fan_table; + u8 min_rpm, max_rpm; + u8 cpu_rpm, gpu_rpm, noise_db; +- int gpu_delta, i, num_entries, ret; ++ int i, num_entries, ret; + size_t header_size, entry_size; + + /* Default behaviour on hwmon init is automatic mode */ + priv->mode = PWM_MODE_AUTO; + +- /* Bypass all non-Victus S devices */ +- if (!is_victus_s_thermal_profile()) ++ /* Bypass devices without fan control support. */ ++ if (!hp_wmi_fan_table_supported()) + return 0; + + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, +@@ -2686,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) + if (min_rpm == U8_MAX || max_rpm == 0) + return -EINVAL; + +- gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; + priv->min_rpm = min_rpm; + priv->max_rpm = max_rpm; +- priv->gpu_delta = gpu_delta; + + return 0; + } +diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c +--- a/drivers/platform/x86/huawei-wmi.c ++++ b/drivers/platform/x86/huawei-wmi.c +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev) + + /* Fn lock */ + ++/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */ ++#define FN_LOCK_ACPI_OFF 1 ++#define FN_LOCK_ACPI_ON 2 ++#define FN_LOCK_ACPI_STAT_OK 0 ++ ++/* ++ * Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct ++ * ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control ++ * Fn-lock via EC registers 0x6B (read) and 0x6C (write). ++ * ++ * GFRS response buffer layout: ++ * byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success) ++ * byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on) ++ * ++ * SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)): ++ * Value is read from byte[2] of the integer argument, so it must be ++ * passed as (value << 16): ++ * (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C) ++ * (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C) ++ */ ++ ++static int huawei_acpi_fn_lock_get(int *on) ++{ ++ union acpi_object acpi_arg; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ ++ acpi_arg.type = ACPI_TYPE_INTEGER; ++ acpi_arg.integer.value = 0; ++ ++ status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ union acpi_object *obj __free(kfree) = output.pointer; ++ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) ++ return -ENODATA; ++ ++ /* byte[0] = STAT, byte[1] = fn-lock state */ ++ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) ++ return -EIO; ++ ++ switch (obj->buffer.pointer[1]) { ++ case FN_LOCK_ACPI_OFF: ++ if (on) ++ *on = 0; ++ break; ++ case FN_LOCK_ACPI_ON: ++ if (on) ++ *on = 1; ++ break; ++ default: ++ return -ENODATA; ++ } ++ ++ return 0; ++} ++ ++static int huawei_acpi_fn_lock_set(int on) ++{ ++ union acpi_object acpi_arg; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ ++ /* ++ * SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02). ++ * on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off) ++ * on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on) ++ */ ++ acpi_arg.type = ACPI_TYPE_INTEGER; ++ acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16; ++ ++ status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ union acpi_object *obj __free(kfree) = output.pointer; ++ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1) ++ return -ENODATA; ++ ++ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) ++ return -EIO; ++ ++ return 0; ++} ++ + static int huawei_wmi_fn_lock_get(int *on) + { + u8 ret[0x100] = { 0 }; + int err, i; + ++ /* Newer models: use direct ACPI \GFRS method */ ++ if (acpi_has_method(NULL, "\\GFRS")) ++ return huawei_acpi_fn_lock_get(on); ++ ++ /* WMI fallback */ + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; +@@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on) + { + union hwmi_arg arg; + ++ /* Newer models: use direct ACPI \SFRS method */ ++ if (acpi_has_method(NULL, "\\SFRS")) ++ return huawei_acpi_fn_lock_set(on); ++ ++ /* WMI fallback */ + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + +diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c +--- a/drivers/platform/x86/intel/int1092/intel_sar.c ++++ b/drivers/platform/x86/intel/int1092/intel_sar.c +@@ -91,10 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob + item->package.count <= data->total_dev_mode) + return AE_ERROR; + +- data->device_mode_info = devm_kmalloc_array(&context->sar_device->dev, +- data->total_dev_mode, +- sizeof(*data->device_mode_info), +- GFP_KERNEL); ++ data->device_mode_info = devm_kcalloc(&context->sar_device->dev, ++ data->total_dev_mode, ++ sizeof(*data->device_mode_info), ++ GFP_KERNEL); + if (!data->device_mode_info) + return AE_ERROR; + +diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c +--- a/drivers/platform/x86/intel/pmc/core.c ++++ b/drivers/platform/x86/intel/pmc/core.c +@@ -1583,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel + { + const u8 *lpm_indices; + int num_maps, mode_offset = 0; +- int ret, lpm_size; ++ int ret = 0, lpm_size; + u8 mode; + + lpm_indices = pmc->map->lpm_reg_index; +diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig +--- a/drivers/platform/x86/lenovo/Kconfig ++++ b/drivers/platform/x86/lenovo/Kconfig +@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA + To compile this driver as a module, choose M here: the module + will be called lenovo-wmi-camera. + ++config LENOVO_YB9_KBDOCK ++ tristate "Lenovo Yoga Book 9 keyboard dock detection" ++ depends on ACPI_WMI ++ depends on DMI ++ depends on INPUT ++ help ++ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 ++ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to ++ either screen; this driver reports SW_TABLET_MODE input events based ++ on the attachment state and exposes the raw position in sysfs. ++ ++ To compile this driver as a module, choose M here: the module will be ++ called lenovo-yb9-kbdock. ++ + config LENOVO_YMC + tristate "Lenovo Yoga Tablet Mode Control" + depends on ACPI_WMI +diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile +--- a/drivers/platform/x86/lenovo/Makefile ++++ b/drivers/platform/x86/lenovo/Makefile +@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o + obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o + + lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o ++lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o + lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o + lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o + lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o +diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c +--- a/drivers/platform/x86/lenovo/ideapad-laptop.c ++++ b/drivers/platform/x86/lenovo/ideapad-laptop.c +@@ -2564,8 +2564,8 @@ module_init(ideapad_laptop_init) + + static void __exit ideapad_laptop_exit(void) + { +- ideapad_wmi_driver_unregister(); + platform_driver_unregister(&ideapad_acpi_driver); ++ ideapad_wmi_driver_unregister(); + } + module_exit(ideapad_laptop_exit) + +diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c +--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c ++++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,6 +68,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -186,6 +188,7 @@ enum tpacpi_hkey_event_t { + TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ + TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */ + TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ ++ TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */ + TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */ + TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */ + +@@ -309,6 +312,7 @@ struct tp_acpi_drv_struct { + + struct ibm_struct { + char *name; ++ acpi_device_class device_class; + + int (*read) (struct seq_file *); + int (*write) (char *); +@@ -374,6 +378,8 @@ static struct { + u32 has_adaptive_kbd:1; + u32 kbd_lang:1; + u32 trackpoint_doubletap_enable:1; ++ u32 usbc_security_supported:1; ++ bool usbc_security_enabled; + struct quirk_entry *quirks; + } tp_features; + +@@ -838,9 +844,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) + } + + ibm->acpi->device->driver_data = ibm; +- scnprintf(acpi_device_class(ibm->acpi->device), +- sizeof(acpi_device_class(ibm->acpi->device)), +- "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name); ++ scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s", ++ TPACPI_ACPI_EVENT_PREFIX, ibm->name); + + status = acpi_install_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, dispatch_acpi_notify, ibm); +@@ -3869,7 +3874,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + pr_err("unknown HKEY notification event %d\n", event); + /* forward it to userspace, maybe it knows how to handle it */ + acpi_bus_generate_netlink_event( +- ibm->acpi->device->pnp.device_class, ++ ibm->device_class, + dev_name(&ibm->acpi->device->dev), + event, 0); + return; +@@ -3949,7 +3954,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + /* netlink events */ + if (send_acpi_ev) { + acpi_bus_generate_netlink_event( +- ibm->acpi->device->pnp.device_class, ++ ibm->device_class, + dev_name(&ibm->acpi->device->dev), + event, hkey); + } +@@ -8840,6 +8845,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { + TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */ + TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */ + TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ ++ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ + TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ + TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */ + TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */ +@@ -11285,6 +11291,114 @@ static struct ibm_struct hwdd_driver_data = { + .name = "hwdd", + }; + ++/************************************************************************* ++ * USB-C Security subdriver ++ * ++ * HKEY.USCS(0) is a read-only ACPI method; its argument is ignored. ++ * It always returns: ++ * bit 16 - USB-C security capability present on this SKU or not ++ * bit 0 - USB-C Security state (enable or disable) ++ * ++ * Hotkey ++ * ------ ++ * 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event. ++ * The driver reads back the new state and notifies the sysfs attribute. ++ */ ++ ++/* USCS() return word bit layout */ ++#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */ ++#define USCS_STATUS_BIT BIT(0) /* current security state */ ++ ++/* Protects USCS() ACPI method calls in usbc_security_query() */ ++static DEFINE_MUTEX(usbc_security_mutex); ++ ++/** ++ * usbc_security_query - read current USB-C security state via USCS() ++ * @enabled: out - true when security is ON (data connections blocked) ++ * ++ * Returns: ++ * 0 success, @enabled contains the current state ++ * -EIO ACPI evaluation failed ++ * -ENODEV capability bit absent; feature not present on this SKU* ++ */ ++static int usbc_security_query(bool *enabled) ++{ ++ int status; ++ ++ guard(mutex)(&usbc_security_mutex); ++ if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0)) ++ return -EIO; ++ ++ if (!(status & USCS_CAP_BIT)) { ++ pr_debug("USCS cap bit absent (raw=0x%x)\n", status); ++ return -ENODEV; ++ } ++ ++ *enabled = status & USCS_STATUS_BIT; ++ return 0; ++} ++ ++/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */ ++static ssize_t usb_c_security_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "%s\n", ++ str_enabled_disabled(tp_features.usbc_security_enabled)); ++} ++ ++static DEVICE_ATTR_RO(usb_c_security); ++ ++static struct attribute *usbc_security_attributes[] = { ++ &dev_attr_usb_c_security.attr, ++ NULL, ++}; ++ ++static umode_t usbc_security_attr_is_visible(struct kobject *kobj, ++ struct attribute *attr, int n) ++{ ++ return tp_features.usbc_security_supported ? attr->mode : 0; ++} ++ ++static const struct attribute_group usbc_security_attr_group = { ++ .is_visible = usbc_security_attr_is_visible, ++ .attrs = usbc_security_attributes, ++}; ++ ++static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm) ++{ ++ int err; ++ ++ if (!acpi_has_method(hkey_handle, "USCS")) ++ return -ENODEV; ++ ++ err = usbc_security_query(&tp_features.usbc_security_enabled); ++ if (err == -ENODEV) ++ return 0; ++ if (err) ++ return err; ++ ++ tp_features.usbc_security_supported = true; ++ return 0; ++} ++ ++/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */ ++static bool tpacpi_usbc_security_hotkey(void) ++{ ++ if (!tp_features.usbc_security_supported) ++ return false; ++ ++ if (usbc_security_query(&tp_features.usbc_security_enabled)) ++ return false; ++ ++ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security"); ++ return true; ++} ++ ++static struct ibm_struct usbc_security_driver_data = { ++ .name = "usbc_security", ++}; ++ + /* --------------------------------------------------------------------- */ + + static struct attribute *tpacpi_driver_attributes[] = { +@@ -11345,6 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = { + &dprc_attr_group, + &auxmac_attr_group, + &hwdd_attr_group, ++ &usbc_security_attr_group, + NULL, + }; + +@@ -11499,6 +11614,8 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) + case TP_HKEY_EV_PROFILE_TOGGLE2: + platform_profile_cycle(); + return true; ++ case TP_HKEY_EV_USB_C_SECURITY: ++ return tpacpi_usbc_security_hotkey(); + } + + return false; +@@ -11964,6 +12081,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { + .init = tpacpi_hwdd_init, + .data = &hwdd_driver_data, + }, ++ { ++ .init = tpacpi_usbc_security_init, ++ .data = &usbc_security_driver_data, ++ }, + }; + + static int __init set_ibm_param(const char *val, const struct kernel_param *kp) +diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/lenovo/yb9-kbdock.c +@@ -0,0 +1,323 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Lenovo Yoga Book 9 keyboard-dock detection ++ * ++ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically ++ * attaches to the bottom screen in one of two positions. The EC tracks ++ * attachment state in a 2-bit field called BKBD and signals changes via WMI ++ * event 0xEB on the WM10 ACPI device (_UID "GMZN"). ++ * ++ * BKBD values: ++ * 0 = keyboard detached ++ * 1 = keyboard docked on the top half of the bottom screen ++ * 2 = keyboard docked on the bottom half of the bottom screen ++ * 3 = reserved / not observed ++ * ++ * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): ++ * ++ * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) ++ * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. ++ * The notify callback receives BKBD as an integer; no separate query needed. ++ * ++ * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) ++ * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) ++ * WmiDataId(2) uint32 Status = BKBD value ++ * Used on probe and resume to read initial state. ++ * ++ * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI ++ * event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev ++ * and registers a notifier_block to receive those events, eliminating the ++ * need for shared global state or a mutex. ++ * ++ * SW_TABLET_MODE=1 is reported when the keyboard is detached; ++ * SW_TABLET_MODE=0 when docked in either position (keyboard present). ++ * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". ++ * ++ * Copyright (C) 2026 Dave Carey ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" ++#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" ++ ++/* BKBD encoding */ ++#define BKBD_DETACHED 0 ++ ++/* LENOVO_FEATURE_STATUS_DATA feature selector */ ++#define YB9_FEATURE_STATUS_ID 0x00060000u ++ ++/* ++ * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}. ++ * IDs is always 0x00060000; Status holds the BKBD value (0–3). ++ */ ++struct lenovo_feature_status { ++ __le32 id; ++ __le32 status; ++} __packed; ++ ++/* ------------------------------------------------------------------ ++ * Notifier chain — event driver fires it, block driver listens ++ * ------------------------------------------------------------------ */ ++ ++static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head); ++ ++static void devm_yb9_kbdock_unregister_notifier(void *data) ++{ ++ struct notifier_block *nb = data; ++ ++ blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb); ++} ++ ++static int devm_yb9_kbdock_register_notifier(struct device *dev, ++ struct notifier_block *nb) ++{ ++ int ret; ++ ++ ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb); ++ if (ret < 0) ++ return ret; ++ ++ return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb); ++} ++ ++/* ------------------------------------------------------------------ ++ * Block WMI driver — LENOVO_FEATURE_STATUS_DATA ++ * (owns input_dev, sysfs, PM resume) ++ * ------------------------------------------------------------------ */ ++ ++struct yb9_kbdock_data { ++ struct wmi_device *wdev; ++ struct input_dev *input_dev; ++ struct notifier_block nb; ++ spinlock_t lock; /* protects input_report_switch + input_sync */ ++}; ++ ++static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd) ++{ ++ struct wmi_buffer out; ++ int ret; ++ ++ ret = wmidev_query_block(d->wdev, 0, &out, ++ sizeof(struct lenovo_feature_status)); ++ if (ret) ++ return ret; ++ ++ struct lenovo_feature_status *fs __free(kfree) = out.data; ++ ++ if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID) ++ return -EIO; ++ ++ *bkbd = le32_to_cpu(fs->status); ++ return 0; ++} ++ ++static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd) ++{ ++ int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; ++ ++ spin_lock(&d->lock); ++ input_report_switch(d->input_dev, SW_TABLET_MODE, tablet); ++ input_sync(d->input_dev); ++ spin_unlock(&d->lock); ++ dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet); ++} ++ ++static int yb9_kbdock_sync(struct yb9_kbdock_data *d) ++{ ++ u32 bkbd; ++ int ret; ++ ++ ret = yb9_kbdock_query(d, &bkbd); ++ if (ret) ++ return ret; ++ ++ yb9_kbdock_report(d, bkbd); ++ return 0; ++} ++ ++static int yb9_kbdock_nb_call(struct notifier_block *nb, ++ unsigned long bkbd, void *unused) ++{ ++ struct yb9_kbdock_data *d = ++ container_of(nb, struct yb9_kbdock_data, nb); ++ ++ yb9_kbdock_report(d, bkbd); ++ return NOTIFY_DONE; ++} ++ ++static ssize_t keyboard_position_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct yb9_kbdock_data *d = dev_get_drvdata(dev); ++ u32 bkbd; ++ int ret; ++ ++ ret = yb9_kbdock_query(d, &bkbd); ++ if (ret) ++ return ret; ++ return sysfs_emit(buf, "%u\n", bkbd); ++} ++static DEVICE_ATTR_RO(keyboard_position); ++ ++static const struct attribute * const yb9_kbdock_attrs[] = { ++ &dev_attr_keyboard_position.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(yb9_kbdock); ++ ++static int yb9_kbdock_resume(struct device *dev) ++{ ++ struct yb9_kbdock_data *d = dev_get_drvdata(dev); ++ ++ return yb9_kbdock_sync(d); ++} ++static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); ++ ++static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) ++{ ++ struct yb9_kbdock_data *d; ++ struct input_dev *input_dev; ++ int ret; ++ ++ d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL); ++ if (!d) ++ return -ENOMEM; ++ ++ d->wdev = wdev; ++ spin_lock_init(&d->lock); ++ ++ input_dev = devm_input_allocate_device(&wdev->dev); ++ if (!input_dev) ++ return -ENOMEM; ++ ++ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; ++ input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0"; ++ input_dev->id.bustype = BUS_HOST; ++ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); ++ ++ ret = input_register_device(input_dev); ++ if (ret) ++ return ret; ++ ++ d->input_dev = input_dev; ++ d->nb.notifier_call = yb9_kbdock_nb_call; ++ ++ ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb); ++ if (ret) ++ return ret; ++ ++ dev_set_drvdata(&wdev->dev, d); ++ return yb9_kbdock_sync(d); ++} ++ ++static const struct wmi_device_id yb9_kbdock_block_id_table[] = { ++ { .guid_string = YB9_KBDOCK_QUERY_GUID }, ++ { } ++}; ++ ++static struct wmi_driver yb9_kbdock_block_driver = { ++ .driver = { ++ .name = "lenovo-yb9-kbdock", ++ .dev_groups = yb9_kbdock_groups, ++ .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), ++ }, ++ .id_table = yb9_kbdock_block_id_table, ++ .no_singleton = true, ++ .probe = yb9_kbdock_block_probe, ++}; ++ ++/* ------------------------------------------------------------------ ++ * Event WMI driver — LENOVO_BTKBD_EVENT ++ * (fires the notifier chain on each WMI event) ++ * ------------------------------------------------------------------ */ ++ ++static void yb9_kbdock_notify_new(struct wmi_device *wdev, ++ const struct wmi_buffer *data) ++{ ++ /* ++ * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer ++ * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). ++ * Short-buffer guard is handled by .min_event_size below. ++ */ ++ u32 bkbd = le32_to_cpu(*(const __le32 *)data->data); ++ ++ blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL); ++} ++ ++static const struct wmi_device_id yb9_kbdock_event_id_table[] = { ++ { .guid_string = YB9_KBDOCK_EVENT_GUID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); ++ ++static struct wmi_driver yb9_kbdock_event_driver = { ++ .driver = { ++ .name = "lenovo-yb9-kbdock-event", ++ }, ++ .id_table = yb9_kbdock_event_id_table, ++ .no_singleton = true, ++ .notify_new = yb9_kbdock_notify_new, ++ .min_event_size = sizeof(__le32), ++}; ++ ++/* ------------------------------------------------------------------ ++ * Module init / exit ++ * ------------------------------------------------------------------ */ ++ ++static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = { ++ { ++ /* Lenovo Yoga Book 9 14IAH10 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), ++ }, ++ }, ++ { } ++}; ++ ++static int __init yb9_kbdock_init(void) ++{ ++ int ret; ++ ++ if (!dmi_check_system(yb9_kbdock_dmi_table)) ++ return -ENODEV; ++ ++ ret = wmi_driver_register(&yb9_kbdock_event_driver); ++ if (ret) ++ return ret; ++ ++ ret = wmi_driver_register(&yb9_kbdock_block_driver); ++ if (ret) { ++ wmi_driver_unregister(&yb9_kbdock_event_driver); ++ return ret; ++ } ++ ++ return 0; ++} ++module_init(yb9_kbdock_init); ++ ++static void __exit yb9_kbdock_exit(void) ++{ ++ wmi_driver_unregister(&yb9_kbdock_block_driver); ++ wmi_driver_unregister(&yb9_kbdock_event_driver); ++} ++module_exit(yb9_kbdock_exit); ++ ++MODULE_AUTHOR("Dave Carey "); ++MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c +--- a/drivers/platform/x86/lenovo/ymc.c ++++ b/drivers/platform/x86/lenovo/ymc.c +@@ -28,6 +28,22 @@ static bool force; + module_param(force, bool, 0444); + MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); + ++static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { ++ { ++ /* ++ * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the ++ * yb9-kbdock driver. Suppress lenovo-ymc on this machine to ++ * avoid userspace seeing two input nodes that both advertise ++ * the SW_TABLET_MODE capability. ++ */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), ++ }, ++ }, ++ { } ++}; ++ + static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { + { + .matches = { +@@ -107,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) + struct input_dev *input_dev; + int err; + ++ if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) ++ return -ENODEV; ++ + if (!dmi_check_system(allowed_chasis_types_dmi_table)) { + if (force) + dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); +diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c +--- a/drivers/platform/x86/lg-laptop.c ++++ b/drivers/platform/x86/lg-laptop.c +@@ -8,8 +8,11 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include + #include + #include ++#include ++#include + #include + #include + #include +@@ -17,10 +20,12 @@ + #include + #include + #include ++#include + #include + #include + #include + #include ++#include + + #include + +@@ -57,13 +62,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); + #define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E8 + #define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E8 + +-#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248" +-#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2" +-#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6" +-#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B" +-#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D" +-#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210" +-#define WMI_EVENT_GUID WMI_EVENT_GUID0 ++#define LG_NOTIFY_TABLET_MODE_OFF 0x50 ++#define LG_NOTIFY_TABLET_MODE_ON 0x51 ++#define LG_NOTIFY_HOTKEY 0x80 ++#define LG_NOTIFY_THERMAL 0x81 ++#define LG_NOTIFY_MISC 0x82 ++ ++#define LG_OREP_READ_EC 0 ++#define LG_OREP_WRITE_EC 1 ++#define LG_OREP_DEBUG 2 ++#define LG_OREP_UPDATE_SYSTEM_STATE 3 ++#define LG_OREP_INTERCEPT_WMI_EVENTS 4 ++#define LG_OREP_WAKE_ON_LAN 6 + + #define SB_GGOV_METHOD "\\_SB.GGOV" + #define GOV_TLED 0x2020008 +@@ -78,26 +88,29 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); + #define WMBB_USB_CHARGE 0x10B + #define WMBB_BATT_LIMIT 0x10C + ++#define KBD_LED_BRIGHTNESS_MASK GENMASK(4, 0) ++#define KBD_LED_BRIGHTNESS_OFF 0x0 ++#define KBD_LED_BRIGHTNESS_HALF 0x2 ++#define KBD_LED_BRIGHTNESS_FULL 0x4 ++#define KBD_LED_MODE_MASK GENMASK(6, 5) ++#define KBD_LED_MODE_OFF 0x0 ++#define KBD_LED_MODE_ON 0x1 ++#define KBD_LED_STATUS BIT(7) ++#define KBD_LED_MAGIC_MASK GENMASK(15, 8) ++/* Exact purpose is unknown, maybe some sort of brightness limit? */ ++#define KBD_LED_MAGIC 0x05 ++ + #define FAN_MODE_LOWER GENMASK(1, 0) + #define FAN_MODE_UPPER GENMASK(5, 4) + + #define PLATFORM_NAME "lg-laptop" + +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID0); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID1); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID2); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID3); +-MODULE_ALIAS("wmi:" WMI_METHOD_WMAB); +-MODULE_ALIAS("wmi:" WMI_METHOD_WMBB); ++struct lg_wmab_buffer_result { ++ __le32 value; ++ __le32 status; ++} __packed; + + static struct platform_device *pf_device; +-static struct input_dev *wmi_input_dev; +- +-static u32 inited; +-#define INIT_INPUT_WMI_0 0x01 +-#define INIT_INPUT_WMI_2 0x02 +-#define INIT_INPUT_ACPI 0x04 +-#define INIT_SPARSE_KEYMAP 0x80 + + static int battery_limit_use_wmbb; + static bool kbd_backlight_available; +@@ -105,16 +118,65 @@ static struct led_classdev kbd_backlight; + static enum led_brightness get_kbd_backlight_level(struct device *dev); + + static const struct key_entry wmi_keymap[] = { +- {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ +- {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */ +- {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ +- {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing +- * this key both sends an event and +- * changes backlight level. +- */ ++ /* Placeholder value send by multiple hotkeys handled by ACPI */ ++ { KE_IGNORE, 0x0, { KEY_UNKNOWN }}, ++ /* LG control panel */ ++ { KE_KEY, 0x70, { KEY_F15 }}, ++ /* Touchpad toggle */ ++ { KE_KEY, 0x74, { KEY_F21 }}, ++ /* Mute Audio, already handled by ACPI */ ++ { KE_IGNORE, 0x78, { KEY_MUTE }}, ++ /* Read mode */ ++ { KE_KEY, 0xf020000, { KEY_F14 }}, ++ /* Open settings */ ++ { KE_KEY, 0xf070002, { KEY_CONFIG }}, ++ /* Keyboard backlight - pressing this key both sends an event and changes backlight level */ ++ { KE_KEY, 0x10000000, { KEY_F16 }}, ++ /* Hotkey combination pressed */ ++ { KE_IGNORE, 0x30010000, { KEY_UNKNOWN }}, ++ /* Hotkey combination released */ ++ { KE_IGNORE, 0x30010001, { KEY_UNKNOWN }}, ++ /* Change fan mode */ ++ { KE_KEY, 0x30010051, { KEY_PERFORMANCE }}, ++ /* Disable camera */ ++ { KE_KEY, 0x40020000, { KEY_CAMERA_ACCESS_TOGGLE }}, ++ /* Mute microphone */ ++ { KE_KEY, 0x40020001, { KEY_MICMUTE }}, ++ /* Fn-Lock */ ++ { KE_KEY, 0x40030001, { KEY_FN_ESC }}, + {KE_END, 0} + }; + ++static int lg_laptop_execute_orep(acpi_handle handle, u64 command, u64 value, ++ unsigned long long *result) ++{ ++ union acpi_object objs[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = command, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = value, ++ }, ++ } ++ }; ++ struct acpi_object_list args = { ++ .count = ARRAY_SIZE(objs), ++ .pointer = objs, ++ }; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, "OREP", &args, result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ return 0; ++} ++ + static int ggov(u32 arg0) + { + union acpi_object args[1]; +@@ -155,30 +217,97 @@ static int ggov(u32 arg0) + return res; + } + +-static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2) ++static int lg_wmab_get(struct device *dev, u32 method, u32 *result) + { +- union acpi_object args[3]; +- acpi_status status; +- struct acpi_object_list arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ union acpi_object in[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = method, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = WM_GET, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = 0, ++ }, ++ }, ++ }; ++ struct lg_wmab_buffer_result *buffer_result; ++ struct acpi_object_list input = { ++ .count = ARRAY_SIZE(in), ++ .pointer = in, ++ }; ++ acpi_status status; + +- args[0].type = ACPI_TYPE_INTEGER; +- args[0].integer.value = method; +- args[1].type = ACPI_TYPE_INTEGER; +- args[1].integer.value = arg1; +- args[2].type = ACPI_TYPE_INTEGER; +- args[2].integer.value = arg2; ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, &buffer); ++ if (ACPI_FAILURE(status)) ++ return -EIO; + +- arg.count = 3; +- arg.pointer = args; ++ union acpi_object *obj __free(kfree) = buffer.pointer; + +- status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer); +- if (ACPI_FAILURE(status)) { +- dev_err(dev, "WMAB: call failed.\n"); +- return NULL; ++ if (!obj) ++ return -ENODATA; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_INTEGER: ++ *result = obj->integer.value; ++ return 0; ++ case ACPI_TYPE_BUFFER: ++ if (obj->buffer.length != sizeof(*buffer_result)) ++ return -EPROTO; ++ ++ buffer_result = (struct lg_wmab_buffer_result *)obj->buffer.pointer; ++ if (get_unaligned_le32(&buffer_result->status)) ++ return -EIO; ++ ++ *result = get_unaligned_le32(&buffer_result->value); ++ return 0; ++ default: ++ return -EPROTO; + } ++} + +- return buffer.pointer; ++static int lg_wmab_set(struct device *dev, u32 method, u32 arg) ++{ ++ union acpi_object in[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = method, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = WM_SET, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = arg, ++ }, ++ }, ++ }; ++ struct acpi_object_list input = { ++ .count = ARRAY_SIZE(in), ++ .pointer = in, ++ }; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, NULL); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ return 0; + } + + static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2) +@@ -212,76 +341,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u + return (union acpi_object *)buffer.pointer; + } + +-static void wmi_notify(union acpi_object *obj, void *context) +-{ +- long data = (long)context; +- unsigned int brightness; +- +- pr_debug("event guid %li\n", data); +- if (!obj) +- return; +- +- if (obj->type == ACPI_TYPE_INTEGER) { +- int eventcode = obj->integer.value; +- struct key_entry *key; +- +- if (eventcode == 0x10000000) { +- if (kbd_backlight_available) { +- brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); +- led_classdev_notify_brightness_hw_changed(&kbd_backlight, +- brightness); +- } +- } else { +- key = sparse_keymap_entry_from_scancode( +- wmi_input_dev, eventcode); +- if (key && key->type == KE_KEY) +- sparse_keymap_report_entry(wmi_input_dev, +- key, 1, true); +- } +- } +- +- pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type, +- obj->integer.value); +-} +- +-static void wmi_input_setup(void) +-{ +- acpi_status status; +- +- wmi_input_dev = input_allocate_device(); +- if (wmi_input_dev) { +- wmi_input_dev->name = "LG WMI hotkeys"; +- wmi_input_dev->phys = "wmi/input0"; +- wmi_input_dev->id.bustype = BUS_HOST; +- +- if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) || +- input_register_device(wmi_input_dev)) { +- pr_info("Cannot initialize input device"); +- input_free_device(wmi_input_dev); +- return; +- } +- +- inited |= INIT_SPARSE_KEYMAP; +- status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify, +- (void *)0); +- if (ACPI_SUCCESS(status)) +- inited |= INIT_INPUT_WMI_0; +- +- status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify, +- (void *)2); +- if (ACPI_SUCCESS(status)) +- inited |= INIT_INPUT_WMI_2; +- } else { +- pr_info("Cannot allocate input device"); +- } +-} +- + static ssize_t fan_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) + { + unsigned long value; +- union acpi_object *r; + int ret; + + ret = kstrtoul(buffer, 10, &value); +@@ -290,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, + if (value >= 3) + return -EINVAL; + +- r = lg_wmab(dev, WM_FAN_MODE, WM_SET, +- FIELD_PREP(FAN_MODE_LOWER, value) | +- FIELD_PREP(FAN_MODE_UPPER, value)); +- kfree(r); ++ ret = lg_wmab_set(dev, WM_FAN_MODE, FIELD_PREP(FAN_MODE_LOWER, value) | ++ FIELD_PREP(FAN_MODE_UPPER, value)); ++ if (ret < 0) ++ return ret; + + return count; + } +@@ -301,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev, + static ssize_t fan_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int mode; +- union acpi_object *r; ++ u32 mode; ++ int ret; + +- r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_FAN_MODE, &mode); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value); +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", mode); ++ return sysfs_emit(buffer, "%lu\n", FIELD_GET(FAN_MODE_LOWER, mode)); + } + + static ssize_t usb_charge_store(struct device *dev, +@@ -366,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, + const char *buffer, size_t count) + { + bool value; +- union acpi_object *r; + int ret; + + ret = kstrtobool(buffer, &value); + if (ret) + return ret; + +- r = lg_wmab(dev, WM_READER_MODE, WM_SET, value); +- if (!r) +- return -EIO; ++ ret = lg_wmab_set(dev, WM_READER_MODE, value); ++ if (ret < 0) ++ return ret; + +- kfree(r); + return count; + } + + static ssize_t reader_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int status; +- union acpi_object *r; ++ u32 status; ++ int ret; + +- r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_READER_MODE, &status); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- status = !!r->integer.value; +- +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", status); ++ return sysfs_emit(buffer, "%d\n", !!status); + } + + static ssize_t fn_lock_store(struct device *dev, +@@ -408,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, + const char *buffer, size_t count) + { + bool value; +- union acpi_object *r; + int ret; + + ret = kstrtobool(buffer, &value); + if (ret) + return ret; + +- r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value); +- if (!r) +- return -EIO; ++ ret = lg_wmab_set(dev, WM_FN_LOCK, value); ++ if (ret < 0) ++ return ret; + +- kfree(r); + return count; + } + + static ssize_t fn_lock_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int status; +- union acpi_object *r; ++ u32 status; ++ int ret; + +- r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_FN_LOCK, &status); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_BUFFER) { +- kfree(r); +- return -EIO; +- } +- +- status = !!r->buffer.pointer[0]; +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", status); ++ return sysfs_emit(buffer, "%d\n", !!status); + } + + static ssize_t charge_control_end_threshold_store(struct device *dev, +@@ -458,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, + if (value == 100 || value == 80) { + union acpi_object *r; + +- if (battery_limit_use_wmbb) ++ if (battery_limit_use_wmbb) { + r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value); +- else +- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value); +- if (!r) +- return -EIO; ++ if (!r) ++ return -EIO; ++ ++ kfree(r); ++ } else { ++ ret = lg_wmab_set(&pf_device->dev, WM_BATT_LIMIT, value); ++ if (ret < 0) ++ return ret; ++ } + +- kfree(r); + return count; + } + +@@ -476,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, + struct device_attribute *attr, + char *buf) + { +- unsigned int status; + union acpi_object *r; ++ u32 status; ++ int ret; + + if (battery_limit_use_wmbb) { + r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0); +@@ -490,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device, + } + + status = r->buffer.pointer[0x10]; ++ kfree(r); + } else { +- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0); +- if (!r) +- return -EIO; +- +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- status = r->integer.value; ++ ret = lg_wmab_get(&pf_device->dev, WM_BATT_LIMIT, &status); ++ if (ret < 0) ++ return ret; + } +- kfree(r); ++ + if (status != 80 && status != 100) + status = 0; + +@@ -568,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { + static void tpad_led_set(struct led_classdev *cdev, + enum led_brightness brightness) + { +- union acpi_object *r; +- +- r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF); +- kfree(r); ++ lg_wmab_set(cdev->dev->parent, WM_TLED, brightness > LED_OFF); + } + + static enum led_brightness tpad_led_get(struct led_classdev *cdev) +@@ -584,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); + static void kbd_backlight_set(struct led_classdev *cdev, + enum led_brightness brightness) + { +- u32 val; +- union acpi_object *r; ++ /* Must always be written */ ++ u32 value = KBD_LED_STATUS; ++ u32 mode, bright; + +- val = 0x22; +- if (brightness <= LED_OFF) +- val = 0; +- if (brightness >= LED_FULL) +- val = 0x24; +- r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val); +- kfree(r); ++ if (brightness <= LED_OFF) { ++ mode = KBD_LED_MODE_OFF; ++ bright = KBD_LED_BRIGHTNESS_OFF; ++ } else { ++ mode = KBD_LED_MODE_ON; ++ if (brightness >= LED_FULL) ++ bright = KBD_LED_BRIGHTNESS_FULL; ++ else ++ bright = KBD_LED_BRIGHTNESS_HALF; ++ } ++ ++ value |= FIELD_PREP(KBD_LED_BRIGHTNESS_MASK, bright); ++ value |= FIELD_PREP(KBD_LED_MODE_MASK, mode); ++ ++ lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, value); + } + + static enum led_brightness get_kbd_backlight_level(struct device *dev) + { +- union acpi_object *r; +- int val; ++ u32 value; ++ int ret; + +- r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0); +- +- if (!r) ++ ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value); ++ if (ret < 0) + return LED_OFF; + +- if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) { +- kfree(r); ++ if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC) + return LED_OFF; +- } + +- switch (r->buffer.pointer[0] & 0x27) { +- case 0x24: +- val = LED_FULL; +- break; +- case 0x22: +- val = LED_HALF; +- break; ++ if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF) ++ return LED_OFF; ++ ++ switch (FIELD_GET(KBD_LED_BRIGHTNESS_MASK, value)) { ++ case KBD_LED_BRIGHTNESS_FULL: ++ return LED_FULL; ++ case KBD_LED_BRIGHTNESS_HALF: ++ return LED_HALF; + default: +- val = LED_OFF; ++ return LED_OFF; + } +- +- kfree(r); +- +- return val; + } + + static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) +@@ -634,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) + + static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); + +-static void wmi_input_destroy(void) +-{ +- if (inited & INIT_INPUT_WMI_2) +- wmi_remove_notify_handler(WMI_EVENT_GUID2); +- +- if (inited & INIT_INPUT_WMI_0) +- wmi_remove_notify_handler(WMI_EVENT_GUID0); +- +- if (inited & INIT_SPARSE_KEYMAP) +- input_unregister_device(wmi_input_dev); +- +- inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); +-} +- + static struct platform_driver pf_driver = { + .driver = { + .name = PLATFORM_NAME, + } + }; + ++static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data) ++{ ++ union acpi_object objs[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = value, ++ }, ++ } ++ }; ++ struct acpi_object_list args = { ++ .count = ARRAY_SIZE(objs), ++ .pointer = objs, ++ }; ++ unsigned long long result; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, "_WED", &args, &result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ if (result > U32_MAX) ++ return -EPROTO; ++ ++ *data = result; ++ ++ return 0; ++} ++ ++static void lg_laptop_handle_input_event(struct input_dev *input_dev, u32 value, u32 data) ++{ ++ unsigned int kbd_brightness; ++ ++ switch (value) { ++ case LG_NOTIFY_HOTKEY: ++ sparse_keymap_report_event(input_dev, data, 1, true); ++ break; ++ case LG_NOTIFY_THERMAL: ++ /* Currently not supported */ ++ break; ++ case LG_NOTIFY_MISC: ++ switch (data) { ++ case 0x10000000: ++ if (!kbd_backlight_available) ++ break; ++ ++ kbd_brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); ++ led_classdev_notify_brightness_hw_changed(&kbd_backlight, kbd_brightness); ++ break; ++ default: ++ sparse_keymap_report_event(input_dev, data, 1, true); ++ } ++ break; ++ default: ++ break; ++ } ++} ++ ++static void lg_laptop_notify_handler(acpi_handle handle, u32 value, void *context) ++{ ++ struct input_dev *input_dev = context; ++ u32 data; ++ int ret; ++ ++ switch (value) { ++ case LG_NOTIFY_TABLET_MODE_OFF: ++ case LG_NOTIFY_TABLET_MODE_ON: ++ /* Already handled by intel-hid */ ++ return; ++ case LG_NOTIFY_HOTKEY: ++ case LG_NOTIFY_THERMAL: ++ case LG_NOTIFY_MISC: ++ ret = lg_laptop_get_event_data(handle, value, &data); ++ if (ret < 0) { ++ dev_notice(input_dev->dev.parent, "Failed to get event data: %d\n", ret); ++ return; ++ } ++ ++ dev_dbg(input_dev->dev.parent, "Received event %u (%u)\n", value, data); ++ ++ lg_laptop_handle_input_event(input_dev, value, data); ++ return; ++ default: ++ dev_notice(input_dev->dev.parent, "Received unknown event %u\n", value); ++ } ++}; ++ ++static void lg_laptop_remove_notify_handler(void *context) ++{ ++ acpi_handle handle = context; ++ ++ acpi_remove_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler); ++} ++ ++static void lg_laptop_reenable_wmi_events(void *context) ++{ ++ acpi_handle handle = context; ++ unsigned long long dummy; ++ ++ lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 0, &dummy); ++} ++ ++static int lg_laptop_input_init(struct device *dev, acpi_handle handle) ++{ ++ struct input_dev *input_dev; ++ unsigned long long result; ++ acpi_status status; ++ int ret; ++ ++ if (!acpi_has_method(handle, "_WED")) ++ return 0; ++ ++ input_dev = devm_input_allocate_device(dev); ++ if (!input_dev) ++ return -ENOMEM; ++ ++ input_dev->name = "LG WMI hotkeys"; ++ input_dev->phys = "wmi/input0"; ++ input_dev->id.bustype = BUS_HOST; ++ ret = sparse_keymap_setup(input_dev, wmi_keymap, NULL); ++ if (ret < 0) ++ return ret; ++ ++ ret = input_register_device(input_dev); ++ if (ret < 0) ++ return ret; ++ ++ status = acpi_install_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler, ++ input_dev); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ ret = devm_add_action_or_reset(dev, lg_laptop_remove_notify_handler, handle); ++ if (ret < 0) ++ return ret; ++ ++ if (acpi_has_method(handle, "OREP")) { ++ ret = lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 1, &result); ++ if (ret < 0) ++ return ret; ++ if (result) ++ return -EIO; ++ ++ ret = devm_add_action_or_reset(dev, lg_laptop_reenable_wmi_events, handle); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ + static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address, + size_t size, u64 value) + { +@@ -865,10 +1036,6 @@ static int acpi_probe(struct platform_device *pdev) + if (year >= 2019) + battery_limit_use_wmbb = 1; + +- ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); +- if (ret) +- goto out_platform_device; +- + /* LEDs are optional */ + ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); + if (ret < 0) +@@ -878,7 +1045,14 @@ static int acpi_probe(struct platform_device *pdev) + + devm_led_classdev_register(&pdev->dev, &tpad_led); + +- wmi_input_setup(); ++ ret = lg_laptop_input_init(&pdev->dev, device->handle); ++ if (ret < 0) ++ goto out_platform_device; ++ ++ ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); ++ if (ret) ++ goto out_platform_device; ++ + battery_hook_register(&battery_hook); + + return 0; +@@ -895,7 +1069,6 @@ static void acpi_remove(struct platform_device *pdev) + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + + battery_hook_unregister(&battery_hook); +- wmi_input_destroy(); + platform_device_unregister(pf_device); + pf_device = NULL; + platform_driver_unregister(&pf_driver); +diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c +--- a/drivers/platform/x86/msi-ec.c ++++ b/drivers/platform/x86/msi-ec.c +@@ -1055,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = { + + static const char * const ALLOWED_FW_13[] __initconst = { + "1594EMS1.109", // MSI Prestige 16 Studio A13VE ++ "17L1EMS1.107", // MSI Katana GF76 11UEK + NULL + }; + +@@ -1130,6 +1131,86 @@ static struct msi_ec_conf CONF13 __initdata = { + }, + }; + ++static const char * const ALLOWED_FW_14[] __initconst = { ++ "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) ++ "182LIMS1.108", // Vector A18 HX A9WHG ++ "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG ++ "182KIMS1.113", // Raider A18 HX A7VIG ++ NULL ++}; ++ ++static struct msi_ec_conf CONF14 __initdata = { ++ .allowed_fw = ALLOWED_FW_14, ++ .charge_control = { ++ .address = 0xd7, ++ .offset_start = 0x8a, ++ .offset_end = 0x80, ++ .range_min = 0x8a, ++ .range_max = 0xe4, ++ }, ++ .webcam = { ++ .address = 0x2e, ++ .block_address = 0x2f, ++ .bit = 1, ++ }, ++ .fn_win_swap = { ++ .address = 0xe8, ++ .bit = 4, ++ }, ++ .cooler_boost = { ++ .address = 0x98, ++ .bit = 7, ++ }, ++ .shift_mode = { ++ .address = 0xd2, ++ .modes = { ++ { SM_ECO_NAME, 0xc2 }, ++ { SM_COMFORT_NAME, 0xc1 }, ++ { SM_TURBO_NAME, 0xc4 }, ++ MSI_EC_MODE_NULL ++ }, ++ }, ++ .super_battery = { ++ .address = 0xeb, ++ .mask = 0x0f, ++ }, ++ .fan_mode = { ++ .address = 0xd4, ++ .modes = { ++ { FM_AUTO_NAME, 0x0d }, ++ { FM_SILENT_NAME, 0x1d }, ++ { FM_ADVANCED_NAME, 0x8d }, ++ MSI_EC_MODE_NULL ++ }, ++ }, ++ .cpu = { ++ .rt_temp_address = 0x68, ++ .rt_fan_speed_address = 0x71, ++ .rt_fan_speed_base_min = 0x00, ++ .rt_fan_speed_base_max = 0x96, ++ .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, ++ .bs_fan_speed_base_min = 0x00, ++ .bs_fan_speed_base_max = 0x0f, ++ }, ++ .gpu = { ++ .rt_temp_address = 0x80, ++ .rt_fan_speed_address = 0x89, ++ }, ++ .leds = { ++ .micmute_led_address = 0x2c, ++ .mute_led_address = 0x2d, ++ .bit = 1, ++ }, ++ .kbd_bl = { ++ .bl_mode_address = MSI_EC_ADDR_UNSUPP, ++ .bl_modes = { 0x00, 0x08 }, ++ .max_mode = 1, ++ .bl_state_address = MSI_EC_ADDR_UNSUPP, ++ .state_base_value = 0x80, ++ .max_state = 3, ++ }, ++}; ++ + static struct msi_ec_conf *CONFIGS[] __initdata = { + &CONF0, + &CONF1, +@@ -1145,6 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = { + &CONF11, + &CONF12, + &CONF13, ++ &CONF14, + NULL + }; + +diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c +--- a/drivers/platform/x86/msi-wmi.c ++++ b/drivers/platform/x86/msi-wmi.c +@@ -46,6 +46,12 @@ enum msi_scancodes { + WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ + WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ + WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ ++ /* MSI Claw keys */ ++ CLAW_KEY_VOLUMEDOWN = 0x21, ++ CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ ++ CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ ++ CLAW_KEY_VOLUMEUP = 0x32, ++ CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ + }; + static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, +@@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, + { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + ++ /* These are MSI Claw keys, used for MSI M-Center in Windows */ ++ { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, ++ ++ /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ ++ { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, ++ { KE_IGNORE, CLAW_KEY_QUICK_LONG }, ++ { KE_IGNORE, CLAW_KEY_VOLUMEUP }, ++ { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, ++ + { KE_END, 0 } + }; + +@@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = { + + static void msi_wmi_notify(union acpi_object *obj, void *context) + { +- struct key_entry *key; ++ struct key_entry *key = NULL; ++ int eventcode = 0; + +- if (obj && obj->type == ACPI_TYPE_INTEGER) { +- int eventcode = obj->integer.value; ++ if (!obj) ++ return; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_INTEGER: ++ eventcode = obj->integer.value; + pr_debug("Eventcode: 0x%x\n", eventcode); +- key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, +- eventcode); +- if (!key) { +- pr_info("Unknown key pressed - %x\n", eventcode); ++ break; ++ case ACPI_TYPE_BUFFER: ++ /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ ++ if (obj->buffer.length < 2) ++ return; ++ ++ /* pointer[0] is key ID, pointer[1] is active state. We don't get release ++ * events, so ignore the active state and treat as autorelease. ++ */ ++ eventcode = obj->buffer.pointer[0]; ++ pr_debug("Eventcode: 0x%x\n", eventcode); ++ break; ++ default: ++ pr_info("Unknown event received\n"); ++ return; ++ } ++ ++ key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); ++ ++ if (!key) { ++ pr_info("Unknown key pressed - 0x%x\n", eventcode); ++ return; ++ } ++ ++ if (event_wmi->quirk_last_pressed) { ++ ktime_t cur = ktime_get_real(); ++ ktime_t diff = ktime_sub(cur, last_pressed); ++ /* Ignore event if any event happened in a 50 ms ++ * timeframe -> Key press may result in 10-20 GPEs ++ */ ++ if (ktime_to_us(diff) < 1000 * 50) { ++ pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", ++ key->code, ktime_to_us(diff)); + return; + } ++ last_pressed = cur; ++ } + +- if (event_wmi->quirk_last_pressed) { +- ktime_t cur = ktime_get_real(); +- ktime_t diff = ktime_sub(cur, last_pressed); +- /* Ignore event if any event happened in a 50 ms +- timeframe -> Key press may result in 10-20 GPEs */ +- if (ktime_to_us(diff) < 1000 * 50) { +- pr_debug("Suppressed key event 0x%X - " +- "Last press was %lld us ago\n", +- key->code, ktime_to_us(diff)); +- return; +- } +- last_pressed = cur; +- } ++ /* Brightness is served via acpi video driver */ ++ if (key->type == KE_KEY && ++ (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || ++ key->code == MSI_KEY_BRIGHTNESSDOWN))) ++ return; + +- if (key->type == KE_KEY && +- /* Brightness is served via acpi video driver */ +- (backlight || +- (key->code != MSI_KEY_BRIGHTNESSUP && +- key->code != MSI_KEY_BRIGHTNESSDOWN))) { +- pr_debug("Send key: 0x%X - Input layer keycode: %d\n", +- key->code, key->keycode); +- sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, +- true); +- } +- } else +- pr_info("Unknown event received\n"); ++ pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); ++ sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); + } + + static int __init msi_wmi_backlight_setup(void) +diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c +--- a/drivers/platform/x86/oxpec.c ++++ b/drivers/platform/x86/oxpec.c +@@ -289,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = { + }, + .driver_data = (void *)oxp_x1, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"), ++ }, ++ .driver_data = (void *)oxp_fly, ++ }, + {}, + }; + +diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c +--- a/drivers/platform/x86/panasonic-laptop.c ++++ b/drivers/platform/x86/panasonic-laptop.c +@@ -159,8 +159,6 @@ MODULE_LICENSE("GPL"); + #define ECO_MODE_ON 0x80 + + #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" +-#define ACPI_PCC_DEVICE_NAME "Hotkey" +-#define ACPI_PCC_CLASS "pcc" + + #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" + +@@ -1026,8 +1024,6 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev) + pcc->device = device; + pcc->handle = device->handle; + device->driver_data = pcc; +- strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_PCC_CLASS); + + result = acpi_pcc_init_input(pcc); + if (result) { +diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c +--- a/drivers/platform/x86/redmi-wmi.c ++++ b/drivers/platform/x86/redmi-wmi.c +@@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = { + {KE_KEY, 0x00011801, {KEY_ASSISTANT}}, + {KE_KEY, 0x00011901, {KEY_ASSISTANT}}, + +- /* Keyboard backlight */ +- {KE_IGNORE, 0x00000501, {}}, +- {KE_IGNORE, 0x00800501, {}}, +- {KE_IGNORE, 0x00050501, {}}, +- {KE_IGNORE, 0x000a0501, {}}, ++ /* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */ ++ {KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}}, + + /* Xiaomi G Command Center */ + {KE_KEY, 0x00010a01, {KEY_VENDOR}}, + +- /* OEM preset power mode */ +- {KE_IGNORE, 0x00011601, {}}, +- {KE_IGNORE, 0x00021601, {}}, +- {KE_IGNORE, 0x00031601, {}}, +- {KE_IGNORE, 0x00041601, {}}, ++ /* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */ ++ {KE_KEY, 0x00011601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00021601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00031601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00041601, {KEY_PERFORMANCE}}, + +- /* Fn Lock state */ +- {KE_IGNORE, 0x00000701, {}}, +- {KE_IGNORE, 0x00010701, {}}, ++ /* Fn Lock state: 1=on 0=off */ ++ {KE_KEY, 0x00000701, {KEY_FN_ESC}}, ++ {KE_KEY, 0x00010701, {KEY_FN_ESC}}, + + /* Fn+`/1/2/3/4 */ + {KE_KEY, 0x00011101, {KEY_F13}}, +diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c +--- a/drivers/platform/x86/samsung-galaxybook.c ++++ b/drivers/platform/x86/samsung-galaxybook.c +@@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = { + { "SAM0428" }, + { "SAM0429" }, + { "SAM0430" }, ++ { "SAMB430" }, + {} + }; + MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids); +diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c +--- a/drivers/platform/x86/sony-laptop.c ++++ b/drivers/platform/x86/sony-laptop.c +@@ -1264,7 +1264,7 @@ static void sony_nc_notify(acpi_handle ah, u32 event, void *data) + ev_type = HOTKEY; + sony_laptop_report_input_event(real_ev); + } +- acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class, ++ acpi_bus_generate_netlink_event("sony/hotkey", + dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev); + } + +@@ -3157,8 +3157,6 @@ static int sony_nc_probe(struct platform_device *pdev) + return -ENODEV; + + sony_nc_acpi_device = device; +- strscpy(acpi_device_class(device), "sony/hotkey"); +- + sony_nc_acpi_handle = device->handle; + + /* read device status */ +@@ -4523,7 +4521,6 @@ static int sony_pic_probe(struct platform_device *pdev) + return -ENODEV; + + spic_dev.acpi_dev = device; +- strscpy(acpi_device_class(device), "sony/hotkey"); + sony_pic_detect_device_type(&spic_dev); + mutex_init(&spic_dev.lock); + +diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c +--- a/drivers/platform/x86/topstar-laptop.c ++++ b/drivers/platform/x86/topstar-laptop.c +@@ -299,8 +299,6 @@ static int topstar_acpi_probe(struct platform_device *pdev) + + platform_set_drvdata(pdev, topstar); + +- strscpy(acpi_device_name(device), "Topstar TPSACPI"); +- strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); + topstar->device = device; + + err = topstar_acpi_init(topstar); +diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c +--- a/drivers/platform/x86/toshiba_acpi.c ++++ b/drivers/platform/x86/toshiba_acpi.c +@@ -2505,8 +2505,7 @@ static void toshiba_acpi_kbd_bl_work(struct work_struct *work) + LED_FULL : LED_OFF); + + /* Emulate the keyboard backlight event */ +- acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class, +- dev_name(&toshiba_acpi->acpi_dev->dev), ++ acpi_bus_generate_netlink_event("", dev_name(&toshiba_acpi->acpi_dev->dev), + 0x92, 0); + } + +@@ -3250,8 +3249,7 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data) + break; + } + +- acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class, +- dev_name(&acpi_dev->dev), ++ acpi_bus_generate_netlink_event("", dev_name(&acpi_dev->dev), + event, (event == 0x80) ? + dev->last_key_event : 0); + } +diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c +--- a/drivers/platform/x86/toshiba_bluetooth.c ++++ b/drivers/platform/x86/toshiba_bluetooth.c +@@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, bt_dev); + + result = toshiba_bluetooth_sync_status(bt_dev); +- if (result) { +- kfree(bt_dev); +- return result; +- } ++ if (result) ++ goto err_free_bt_dev; + + bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", + &pdev->dev, +diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c +--- a/drivers/platform/x86/toshiba_haps.c ++++ b/drivers/platform/x86/toshiba_haps.c +@@ -136,9 +136,7 @@ static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data) + + pr_debug("Received event: 0x%x\n", event); + +- acpi_bus_generate_netlink_event(device->pnp.device_class, +- dev_name(&device->dev), +- event, 0); ++ acpi_bus_generate_netlink_event("", dev_name(&device->dev), event, 0); + } + + static void toshiba_haps_remove(struct platform_device *pdev) +diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c +--- a/drivers/platform/x86/uniwill/uniwill-acpi.c ++++ b/drivers/platform/x86/uniwill/uniwill-acpi.c +@@ -95,6 +95,9 @@ + + #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 + ++#define EC_ADDR_SCREEN_STATUS 0x0466 ++#define SCREEN_SUSPENDED BIT(6) ++ + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C + + #define EC_ADDR_SECOND_FAN_RPM_2 0x046D +@@ -109,9 +112,35 @@ + + #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 + ++#define EC_ADDR_OEM_9 0x0726 ++#define AC_AUTO_BOOT_ENABLE BIT(3) ++ + #define EC_ADDR_PROJECT_ID 0x0740 ++#define PROJECT_ID_NONE 0x00 ++#define PROJECT_ID_GI 0x01 ++#define PROJECT_ID_GJ 0x02 ++#define PROJECT_ID_GK 0x03 ++#define PROJECT_ID_GICN 0x04 ++#define PROJECT_ID_GJCN 0x05 ++#define PROJECT_ID_GK5CN_X 0x06 ++#define PROJECT_ID_GK7CN_S 0x07 ++#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08 ++#define PROJECT_ID_PF 0x09 ++#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A ++#define PROJECT_ID_IDP 0x0B ++#define PROJECT_ID_IDY_6Y 0x0C ++#define PROJECT_ID_IDY_7Y 0x0D ++#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E ++#define PROJECT_ID_CML_GAMING 0x0F ++#define PROJECT_ID_GK7NXXR 0x10 ++#define PROJECT_ID_GM5MU1Y 0x11 + #define PROJECT_ID_PH4TRX1 0x12 ++#define PROJECT_ID_PH4TUX1 0x13 ++#define PROJECT_ID_PH4TQX1 0x14 + #define PROJECT_ID_PH6TRX1 0x15 ++#define PROJECT_ID_PH6TQXX 0x16 ++#define PROJECT_ID_PHXAXXX 0x17 ++#define PROJECT_ID_PHXPXXX 0x18 + + #define EC_ADDR_AP_OEM 0x0741 + #define ENABLE_MANUAL_CTRL BIT(0) +@@ -214,6 +243,7 @@ + #define FAN_TABLE_OFFICE_MODE BIT(2) + #define FAN_V3 BIT(3) + #define DEFAULT_MODE BIT(4) ++#define ENABLE_CHINA_MODE BIT(6) + + #define EC_ADDR_PL1_SETTING 0x0783 + +@@ -225,11 +255,12 @@ + #define FAN_CURVE_LENGTH 5 + + #define EC_ADDR_KBD_STATUS 0x078C +-#define KBD_WHITE_ONLY BIT(0) // ~single color +-#define KBD_SINGLE_COLOR_OFF BIT(1) ++/* Unreliable on some models, use the device descriptor instead. */ ++#define KBD_WHITE_ONLY BIT(0) ++#define KBD_POWER_OFF BIT(1) + #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) + #define KBD_APPLY BIT(4) +-#define KBD_BRIGHTNESS GENMASK(7, 5) ++#define KBD_BRIGHTNESS_MASK GENMASK(7, 5) + + #define EC_ADDR_FAN_CTRL 0x078E + #define FAN3P5 BIT(1) +@@ -318,7 +349,9 @@ + #define FAN_TABLE_LENGTH 16 + + #define LED_CHANNELS 3 +-#define LED_MAX_BRIGHTNESS 200 ++ ++#define KBD_LED_CHANNELS 3 ++#define KBD_LED_MAX_INTENSITY 50 + + #define UNIWILL_FEATURE_FN_LOCK BIT(0) + #define UNIWILL_FEATURE_SUPER_KEY BIT(1) +@@ -333,6 +366,9 @@ + #define UNIWILL_FEATURE_SECONDARY_FAN BIT(9) + #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) + #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) ++#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) ++#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) ++#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14) + + enum usb_c_power_priority_options { + USB_C_POWER_PRIORITY_CHARGING = 0, +@@ -344,6 +380,7 @@ struct uniwill_data { + acpi_handle handle; + struct regmap *regmap; + unsigned int features; ++ u8 project_id; + struct acpi_battery_hook hook; + struct mutex battery_lock; /* Protects the list of currently registered batteries */ + union { +@@ -357,11 +394,25 @@ struct uniwill_data { + bool last_fn_lock_state; + bool last_super_key_enable_state; + bool last_touchpad_toggle_enable_state; ++ bool last_usb_powershare_high_state; + struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ + struct list_head batteries; + struct mutex led_lock; /* Protects writes to the lightbar registers */ ++ u8 lightbar_max_brightness; + struct led_classdev_mc led_mc_cdev; + struct mc_subled led_mc_subled_info[LED_CHANNELS]; ++ bool kbd_led_single_color; ++ u8 kbd_led_max_brightness; ++ unsigned int last_kbd_status; ++ union { ++ struct { ++ /* Protects writes to the RGB keyboard backlight registers */ ++ struct mutex kbd_rgb_led_lock; ++ struct led_classdev_mc kbd_led_mc_cdev; ++ struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS]; ++ }; ++ struct led_classdev kbd_led_cdev; ++ }; + struct mutex input_lock; /* Protects input sequence during notify */ + struct input_dev *input_device; + struct notifier_block nb; +@@ -376,6 +427,9 @@ struct uniwill_battery_entry { + + struct uniwill_device_descriptor { + unsigned int features; ++ bool kbd_led_single_color; ++ u8 kbd_led_max_brightness; ++ u8 lightbar_max_brightness; + /* Executed during driver probing */ + int (*probe)(struct uniwill_data *data); + }; +@@ -427,6 +481,9 @@ static const struct key_entry uniwill_keymap[] = { + { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, + { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + ++ /* Reported when the EC changed the keyboard backlight brightness */ ++ { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }}, ++ + /* Reported when the user wants to toggle the microphone mute status */ + { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, + +@@ -435,11 +492,6 @@ static const struct key_entry uniwill_keymap[] = { + + /* Reported when the user wants to toggle the brightness of the keyboard */ + { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, + + /* FIXME: find out the exact meaning of those events */ + { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, +@@ -448,6 +500,9 @@ static const struct key_entry uniwill_keymap[] = { + /* Reported when the user wants to toggle the benchmark mode status */ + { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + ++ /* Reported when the screen is enabled/disabled during resume/suspend */ ++ { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }}, ++ + /* Reported when the user wants to toggle the webcam */ + { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, + +@@ -540,6 +595,7 @@ static const struct regmap_bus uniwill_ec_bus = { + static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case EC_ADDR_OEM_9: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: + case EC_ADDR_LIGHTBAR_AC_RED: +@@ -547,6 +603,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_LIGHTBAR_AC_BLUE: + case EC_ADDR_BIOS_OEM: + case EC_ADDR_TRIGGER: ++ case EC_ADDR_RGB_RED: ++ case EC_ADDR_RGB_GREEN: ++ case EC_ADDR_RGB_BLUE: ++ case EC_ADDR_BIOS_OEM_2: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: +@@ -574,6 +635,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_SECOND_FAN_RPM_1: + case EC_ADDR_SECOND_FAN_RPM_2: + case EC_ADDR_BAT_ALERT: ++ case EC_ADDR_OEM_9: + case EC_ADDR_PROJECT_ID: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: +@@ -583,8 +645,14 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: ++ case EC_ADDR_SUPPORT_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: ++ case EC_ADDR_RGB_RED: ++ case EC_ADDR_RGB_GREEN: ++ case EC_ADDR_RGB_BLUE: ++ case EC_ADDR_BIOS_OEM_2: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: +@@ -616,8 +684,10 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: ++ case EC_ADDR_SUPPORT_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_USB_C_POWER_PRIORITY: +@@ -1077,6 +1147,109 @@ static int usb_c_power_priority_init(struct uniwill_data *data) + return 0; + } + ++static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ unsigned int regval; ++ bool enable; ++ int ret; ++ ++ ret = kstrtobool(buf, &enable); ++ if (ret < 0) ++ return ret; ++ ++ if (enable) ++ regval = AC_AUTO_BOOT_ENABLE; ++ else ++ regval = 0; ++ ++ ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ unsigned int regval; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_OEM_9, ®val); ++ if (ret < 0) ++ return ret; ++ ++ return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE)); ++} ++ ++static DEVICE_ATTR_RW(ac_auto_boot); ++ ++static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status) ++{ ++ unsigned int value; ++ ++ if (status) ++ value = TRIGGER_USB_CHARGING; ++ else ++ value = 0; ++ ++ /* ++ * Normaly this RMW-sequence could also trigger the super key toggle, ++ * but the EC seems to take care that those bits are always read as 0. ++ */ ++ return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value); ++} ++ ++static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ bool enable; ++ int ret; ++ ++ ret = kstrtobool(buf, &enable); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_write_usb_powershare_high(data, enable); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status) ++{ ++ unsigned int value; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value); ++ if (ret < 0) ++ return ret; ++ ++ *status = !!(value & TRIGGER_USB_CHARGING); ++ ++ return 0; ++} ++ ++static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ bool status; ++ int ret; ++ ++ ret = uniwill_read_usb_powershare_high(data, &status); ++ if (ret < 0) ++ return ret; ++ ++ return sysfs_emit(buf, "%d\n", status); ++} ++ ++static DEVICE_ATTR_RW(usb_powershare_high); ++ + static struct attribute *uniwill_attrs[] = { + /* Keyboard-related */ + &dev_attr_fn_lock.attr, +@@ -1088,6 +1261,8 @@ static struct attribute *uniwill_attrs[] = { + /* Power-management-related */ + &dev_attr_ctgp_offset.attr, + &dev_attr_usb_c_power_priority.attr, ++ &dev_attr_ac_auto_boot.attr, ++ &dev_attr_usb_powershare_high.attr, + NULL + }; + +@@ -1127,6 +1302,16 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a + return attr->mode; + } + ++ if (attr == &dev_attr_ac_auto_boot.attr) { ++ if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT)) ++ return attr->mode; ++ } ++ ++ if (attr == &dev_attr_usb_powershare_high.attr) { ++ if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return attr->mode; ++ } ++ + return 0; + } + +@@ -1332,7 +1517,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br + + for (int i = 0; i < LED_CHANNELS; i++) { + /* Prevent the brightness values from overflowing */ +- value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); ++ value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + if (ret < 0) + return ret; +@@ -1401,14 +1586,14 @@ static int uniwill_led_init(struct uniwill_data *data) + return ret; + + data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; +- data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; ++ data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; + data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; + data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; + + if (value & LIGHTBAR_S0_OFF) + data->led_mc_cdev.led_cdev.brightness = 0; + else +- data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; ++ data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; + + for (int i = 0; i < LED_CHANNELS; i++) { + data->led_mc_subled_info[i].color_index = color_indices[i]; +@@ -1421,7 +1606,7 @@ static int uniwill_led_init(struct uniwill_data *data) + * Make sure that the initial intensity value is not greater than + * the maximum brightness. + */ +- value = min(LED_MAX_BRIGHTNESS, value); ++ value = min(data->lightbar_max_brightness, value); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + if (ret < 0) + return ret; +@@ -1441,6 +1626,246 @@ static int uniwill_led_init(struct uniwill_data *data) + &init_data); + } + ++static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) ++{ ++ struct led_classdev *led_cdev; ++ int ret; ++ ++ if (data->kbd_led_single_color) ++ led_cdev = &data->kbd_led_cdev; ++ else ++ led_cdev = &data->kbd_led_mc_cdev.led_cdev; ++ ++ guard(mutex)(&led_cdev->led_access); ++ ++ /* Sync the LED brightness with the actual hardware state */ ++ ret = led_update_brightness(led_cdev); ++ if (ret < 0) ++ return ret; ++ ++ led_classdev_notify_brightness_hw_changed(led_cdev, brightness); ++ ++ return 0; ++} ++ ++#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF) ++ ++static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness) ++{ ++ /* KBD_POWER_OFF is always implicitly cleared */ ++ unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY; ++ ++ /* We must ensure that the "apply" bit is always written */ ++ return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval); ++} ++ ++static int uniwill_kbd_led_read_brightness(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness); ++} ++ ++static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); ++ ++ return uniwill_kbd_led_write_brightness(data, brightness); ++} ++ ++static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); ++ ++ return uniwill_kbd_led_read_brightness(data); ++} ++ ++static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = { ++ EC_ADDR_RGB_RED, ++ EC_ADDR_RGB_GREEN, ++ EC_ADDR_RGB_BLUE, ++}; ++ ++static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); ++ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); ++ unsigned int min_intensity = 0; ++ unsigned int regval; ++ int ret; ++ ++ guard(mutex)(&data->kbd_rgb_led_lock); ++ ++ /* ++ * The EC interprets a RGB value of 0x000000 as a command to restore ++ * the device-specfic default RGB value. Work around this by writing ++ * a RGB value of 0x010101 (faint white) instead. ++ */ ++ if (data->kbd_led_mc_subled_info[0].intensity == 0 && ++ data->kbd_led_mc_subled_info[1].intensity == 0 && ++ data->kbd_led_mc_subled_info[2].intensity == 0) ++ min_intensity = 1; ++ ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity); ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); ++ if (ret < 0) ++ return ret; ++ ++ return uniwill_kbd_led_write_brightness(data, brightness); ++} ++ ++static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); ++ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); ++ ++ return uniwill_kbd_led_read_brightness(data); ++} ++ ++static int uniwill_white_kbd_led_init(struct uniwill_data *data) ++{ ++ struct led_init_data init_data = { ++ .default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT, ++ .devicename = DRIVER_NAME, ++ .devname_mandatory = true, ++ }; ++ ++ data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; ++ data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; ++ data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; ++ data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; ++ data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; ++ ++ return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); ++} ++ ++static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) ++{ ++ unsigned int color_indices[KBD_LED_CHANNELS] = { ++ LED_COLOR_ID_RED, ++ LED_COLOR_ID_GREEN, ++ LED_COLOR_ID_BLUE, ++ }; ++ struct led_init_data init_data = { ++ .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT, ++ .devicename = DRIVER_NAME, ++ .devname_mandatory = true, ++ }; ++ bool intensity_all_zeros = true; ++ bool needs_trigger = false; ++ unsigned int regval; ++ int ret; ++ ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; ++ ++ ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], ®val); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Make sure that the initial intensity value is not greater than ++ * the maximum intensity. ++ */ ++ if (regval > KBD_LED_MAX_INTENSITY) { ++ regval = KBD_LED_MAX_INTENSITY; ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); ++ if (ret < 0) ++ return ret; ++ ++ needs_trigger = true; ++ } ++ ++ if (regval) ++ intensity_all_zeros = false; ++ ++ data->kbd_led_mc_subled_info[i].intensity = regval; ++ data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY; ++ data->kbd_led_mc_subled_info[i].channel = i; ++ } ++ ++ /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */ ++ if (intensity_all_zeros) { ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ data->kbd_led_mc_subled_info[i].intensity = 1; ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1); ++ if (ret < 0) ++ return ret; ++ } ++ ++ needs_trigger = true; ++ } ++ ++ if (needs_trigger) { ++ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, ++ RGB_APPLY_COLOR); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock); ++ if (ret < 0) ++ return ret; ++ ++ data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; ++ data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; ++ data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; ++ data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; ++ data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; ++ data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info; ++ data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS; ++ ++ return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev, ++ &init_data); ++} ++ ++static int uniwill_kbd_led_init(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); ++ if (ret < 0) ++ return ret; ++ ++ if (!(regval & CHINA_MODE)) { ++ ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ regval |= KBD_APPLY; ++ regval &= ~KBD_POWER_OFF; ++ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); ++ if (ret < 0) ++ return ret; ++ ++ if (data->kbd_led_single_color) ++ return uniwill_white_kbd_led_init(data); ++ ++ return uniwill_rgb_kbd_led_init(data); ++} ++ + static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) + { + /* 0 means "charging threshold not active" */ +@@ -1789,6 +2214,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action + sysfs_notify(&data->dev->kobj, NULL, "fn_lock"); + + return NOTIFY_OK; ++ case UNIWILL_OSD_KB_LED_LEVEL0: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 0)); ++ case UNIWILL_OSD_KB_LED_LEVEL1: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 1)); ++ case UNIWILL_OSD_KB_LED_LEVEL2: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 2)); ++ case UNIWILL_OSD_KB_LED_LEVEL3: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 3)); ++ case UNIWILL_OSD_KB_LED_LEVEL4: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 4)); + default: + mutex_lock(&data->input_lock); + sparse_keymap_report_event(data->input_device, action, 1, true); +@@ -1842,6 +2292,7 @@ static int uniwill_ec_init(struct uniwill_data *data) + if (ret < 0) + return ret; + ++ data->project_id = value; + dev_dbg(data->dev, "Project ID: %u\n", value); + + ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); +@@ -1885,6 +2336,9 @@ static int uniwill_probe(struct platform_device *pdev) + return ret; + + data->features = device_descriptor.features; ++ data->kbd_led_single_color = device_descriptor.kbd_led_single_color; ++ data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; ++ data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; + + /* + * Some devices might need to perform some device-specific initialization steps +@@ -1905,6 +2359,10 @@ static int uniwill_probe(struct platform_device *pdev) + if (ret < 0) + return ret; + ++ ret = uniwill_kbd_led_init(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_hwmon_init(data); + if (ret < 0) + return ret; +@@ -1976,6 +2434,43 @@ static int uniwill_suspend_battery(struct uniwill_data *data) + return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); + } + ++static int uniwill_suspend_kbd_led(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Save the current keyboard backlight settings in order to restore them ++ * during resume. We cannot use the regmap code for that since this register ++ * needs to be declared as volatile because the brightness can be changed ++ * by the EC. ++ */ ++ data->last_kbd_status = regval; ++ FIELD_MODIFY(KBD_BRIGHTNESS_MASK, ®val, 0); ++ regval |= KBD_APPLY | KBD_POWER_OFF; ++ ++ return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); ++} ++ ++static int uniwill_suspend_usb_powershare(struct uniwill_data *data) ++{ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return 0; ++ ++ /* ++ * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it ++ * ourselves. ++ */ ++ return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state); ++} ++ + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) + { + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) +@@ -2006,6 +2501,14 @@ static int uniwill_suspend(struct device *dev) + if (ret < 0) + return ret; + ++ ret = uniwill_suspend_kbd_led(data); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_suspend_usb_powershare(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_suspend_nvidia_ctgp(data); + if (ret < 0) + return ret; +@@ -2052,6 +2555,31 @@ static int uniwill_resume_battery(struct uniwill_data *data) + return 0; + } + ++static int uniwill_resume_kbd_led(struct uniwill_data *data) ++{ ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY); ++ if (ret < 0) ++ return ret; ++ ++ if (data->kbd_led_single_color) ++ return 0; ++ ++ return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); ++} ++ ++static int uniwill_resume_usb_powershare(struct uniwill_data *data) ++{ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return 0; ++ ++ return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state); ++} ++ + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) + { + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) +@@ -2096,6 +2624,14 @@ static int uniwill_resume(struct device *dev) + if (ret < 0) + return ret; + ++ ret = uniwill_resume_kbd_led(data); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_resume_usb_powershare(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_resume_nvidia_ctgp(data); + if (ret < 0) + return ret; +@@ -2126,13 +2662,30 @@ static struct platform_driver uniwill_driver = { + .shutdown = uniwill_shutdown, + }; + ++static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_GPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | ++ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++ .kbd_led_single_color = false, ++ .kbd_led_max_brightness = 4, ++}; ++ + static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { + .features = UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, ++ .lightbar_max_brightness = 36, + }; + + static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { +@@ -2156,6 +2709,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, ++ .lightbar_max_brightness = 200, + }; + + /* +@@ -2198,15 +2752,6 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + }; + +-static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata = { +- .features = UNIWILL_FEATURE_FN_LOCK | +- UNIWILL_FEATURE_SUPER_KEY | +- UNIWILL_FEATURE_BATTERY_CHARGE_MODES | +- UNIWILL_FEATURE_CPU_TEMP | +- UNIWILL_FEATURE_PRIMARY_FAN | +- UNIWILL_FEATURE_SECONDARY_FAN, +-}; +- + static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | +@@ -2218,6 +2763,30 @@ static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __ini + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + }; + ++static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++}; ++ ++static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_GPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++}; ++ + static int phxtxx1_probe(struct uniwill_data *data) + { + unsigned int value; +@@ -2277,7 +2846,37 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { + UNIWILL_FEATURE_PRIMARY_FAN, + }; + ++static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++ .kbd_led_single_color = true, ++ .kbd_led_max_brightness = 2, ++}; ++ + static const struct dmi_system_id uniwill_dmi_table[] __initconst = { ++ { ++ .ident = "AiStone X4SP4NAL", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "AiStone"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"), ++ }, ++ .driver_data = &x4sp4nal_descriptor, ++ }, ++ { ++ .ident = "MACHENIKE L16 Pro", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"), ++ }, ++ .driver_data = &machenike_l16p_descriptor, ++ }, + { + .ident = "XMG FUSION 15 (L19)", + .matches = { +@@ -2310,6 +2909,13 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + }, + .driver_data = &lapqc71a_lapqc71b_descriptor, + }, ++ { ++ .ident = "Avell A60 MUV", ++ .matches = { ++ DMI_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), ++ }, ++ .driver_data = &lapqc71a_lapqc71b_descriptor, ++ }, + { + .ident = "Intel NUC x15", + .matches = { +@@ -2388,7 +2994,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", +@@ -2396,7 +3002,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", +@@ -2404,7 +3010,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", +@@ -2412,7 +3018,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", +@@ -2420,7 +3026,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", +@@ -2428,7 +3034,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", +@@ -2436,7 +3042,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", +@@ -2444,7 +3050,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", +@@ -2452,7 +3058,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", +@@ -2460,7 +3066,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Polaris 15 Gen1 AMD", +@@ -2620,7 +3226,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", +@@ -2628,7 +3234,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", +@@ -2636,7 +3242,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", +@@ -2644,7 +3250,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", +@@ -2652,7 +3258,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 AMD", +@@ -2660,7 +3266,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 Intel", +@@ -2668,7 +3274,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 Intel", +@@ -2676,7 +3282,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Book BA15 Gen10 AMD", +@@ -2745,6 +3351,12 @@ static int __init uniwill_init(void) + if (force) { + /* Assume that the device supports all features except the charge limit */ + device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; ++ /* Some models only have a (white) single color keyboard backlight */ ++ device_descriptor.kbd_led_single_color = false; ++ /* Some models only support 3 brightness levels */ ++ device_descriptor.kbd_led_max_brightness = 4; ++ /* Some models only support 36 brightness levels per color component */ ++ device_descriptor.lightbar_max_brightness = 200; + pr_warn("Enabling potentially unsupported features\n"); + } + +diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h +--- a/drivers/platform/x86/uniwill/uniwill-wmi.h ++++ b/drivers/platform/x86/uniwill/uniwill-wmi.h +@@ -113,6 +113,8 @@ + + #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 + ++#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC ++ + #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF + + #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 +diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c +--- a/drivers/platform/x86/xo15-ebook.c ++++ b/drivers/platform/x86/xo15-ebook.c +@@ -19,13 +19,10 @@ + + #define MODULE_NAME "xo15-ebook" + +-#define XO15_EBOOK_CLASS MODULE_NAME + #define XO15_EBOOK_TYPE_UNKNOWN 0x00 + #define XO15_EBOOK_NOTIFY_STATUS 0x80 + +-#define XO15_EBOOK_SUBCLASS "ebook" + #define XO15_EBOOK_HID "XO15EBK" +-#define XO15_EBOOK_DEVICE_NAME "EBook Switch" + + MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver"); + MODULE_LICENSE("GPL"); +@@ -105,12 +102,9 @@ static int ebook_switch_probe(struct platform_device *pdev) + if (!id) + return dev_err_probe(dev, -ENODEV, "Unsupported hid\n"); + +- strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME); +- strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS); +- + snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id); + +- input->name = acpi_device_name(device); ++ input->name = "EBook Switch"; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +--- a/drivers/power/supply/surface_battery.c ++++ b/drivers/power/supply/surface_battery.c +@@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { + }; + + static const struct ssam_device_id surface_battery_match[] = { +- { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, +- { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, +- { }, ++ { ++ SSAM_SDEV(BAT, SAM, 0x01, 0x00), ++ .driver_data = (unsigned long)&spwr_psy_props_bat1, ++ }, { ++ SSAM_SDEV(BAT, KIP, 0x01, 0x00), ++ .driver_data = (unsigned long)&spwr_psy_props_bat2_sb3, ++ }, ++ { } + }; + MODULE_DEVICE_TABLE(ssam, surface_battery_match); + +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +--- a/drivers/power/supply/surface_charger.c ++++ b/drivers/power/supply/surface_charger.c +@@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { + }; + + static const struct ssam_device_id surface_ac_match[] = { +- { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, +- { }, ++ { ++ SSAM_SDEV(BAT, SAM, 0x01, 0x01), ++ .driver_data = (unsigned long)&spwr_psy_props_adp1, ++ }, ++ { } + }; + MODULE_DEVICE_TABLE(ssam, surface_ac_match); + +diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h +--- a/include/linux/amd-pmf-io.h ++++ b/include/linux/amd-pmf-io.h +@@ -52,15 +52,6 @@ struct amd_sfh_info { + u32 laptop_placement; + }; + +-enum laptop_placement { +- LP_UNKNOWN = 0, +- ON_TABLE, +- ON_LAP_MOTION, +- IN_BAG, +- OUT_OF_BAG, +- LP_UNDEFINED, +-}; +- + /** + * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver + * @npuclk_freq: NPU clock frequency [MHz] +@@ -69,6 +60,7 @@ enum laptop_placement { + * @mpnpuclk_freq: MPNPU [MHz] + * @npu_reads: NPU read bandwidth [MB/sec] + * @npu_writes: NPU write bandwidth [MB/sec] ++ * @npu_temp: NPU temperature [C] + */ + struct amd_pmf_npu_metrics { + u16 npuclk_freq; +@@ -77,6 +69,7 @@ struct amd_pmf_npu_metrics { + u16 mpnpuclk_freq; + u16 npu_reads; + u16 npu_writes; ++ u16 npu_temp; + }; + + int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op); +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); + int asus_hid_register_listener(struct asus_hid_listener *cdev); + void asus_hid_unregister_listener(struct asus_hid_listener *cdev); + int asus_hid_event(enum asus_hid_event event); ++bool asus_wmi_custom_fan_curve_is_enabled(void); + #else + static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) + { +@@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event) + { + return -ENODEV; + } ++static inline bool asus_wmi_custom_fan_curve_is_enabled(void) ++{ ++ return false; ++} + #endif + + #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ +diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h +new file mode 100644 +--- /dev/null ++++ b/include/uapi/linux/amd-pmf.h +@@ -0,0 +1,204 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ ++/* ++ * AMD Platform Management Framework (PMF) UAPI Header ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * This file defines the user-space API for interacting with the AMD PMF ++ * driver. It provides ioctl interfaces to query platform-specific metrics ++ * such as power source, slider position, platform type, laptop placement, ++ * and various BIOS input/output parameters. ++ */ ++ ++#ifndef _UAPI_LINUX_AMD_PMF_H ++#define _UAPI_LINUX_AMD_PMF_H ++ ++#include ++#include ++#include ++ ++/** ++ * AMD_PMF_IOC_MAGIC - Magic number for AMD PMF ioctl commands ++ * ++ * This magic number uniquely identifies AMD PMF ioctl operations. ++ */ ++#define AMD_PMF_IOC_MAGIC 'p' ++ ++/** ++ * IOCTL_AMD_PMF_POPULATE_DATA - ioctl command to retrieve PMF metrics data ++ * ++ * This ioctl command is used to populate the amd_pmf_info structure ++ * with the requested PMF metrics information. ++ */ ++#define IOCTL_AMD_PMF_POPULATE_DATA _IOWR(AMD_PMF_IOC_MAGIC, 0x00, __u64) ++ ++#define AMD_PMF_BIOS_PARAMS_MAX 10 ++ ++/* AMD PMF feature flags - bitmask indicating supported features */ ++#define AMD_PMF_FEAT_AUTO_MODE BIT(0) ++#define AMD_PMF_FEAT_STATIC_POWER_SLIDER BIT(1) ++#define AMD_PMF_FEAT_POLICY_BUILDER BIT(2) ++#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3) ++#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4) ++ ++/** ++ * enum amd_pmf_laptop_placement - Describes the physical placement of the laptop ++ * @AMD_PMF_LP_UNKNOWN: Placement cannot be determined ++ * @AMD_PMF_ON_TABLE: Laptop is placed on a stable surface like a table or desk ++ * @AMD_PMF_ON_LAP_MOTION: Laptop is on a lap with detected motion ++ * @AMD_PMF_IN_BAG: Laptop is detected to be inside a bag or case ++ * @AMD_PMF_OUT_OF_BAG: Laptop has been removed from bag or case ++ * @AMD_PMF_LP_UNDEFINED: Placement state is undefined ++ * ++ * This enumeration represents the physical placement state of the laptop ++ * as detected by platform sensors. Used for adaptive power management ++ * and thermal policies. ++ */ ++enum amd_pmf_laptop_placement { ++ AMD_PMF_LP_UNKNOWN, ++ AMD_PMF_ON_TABLE, ++ AMD_PMF_ON_LAP_MOTION, ++ AMD_PMF_IN_BAG, ++ AMD_PMF_OUT_OF_BAG, ++ AMD_PMF_LP_UNDEFINED, ++}; ++ ++/** ++ * enum amd_pmf_ta_slider - Trusted Application power slider positions ++ * @AMD_PMF_TA_BEST_BATTERY: Maximum battery savings, minimal performance ++ * @AMD_PMF_TA_BETTER_BATTERY: Balanced towards battery life ++ * @AMD_PMF_TA_BETTER_PERFORMANCE: Balanced towards performance ++ * @AMD_PMF_TA_BEST_PERFORMANCE: Maximum performance, higher power consumption ++ * @AMD_PMF_TA_MAX: Sentinel value indicating maximum enum value ++ * ++ * This enumeration defines the power slider positions used by the ++ * AMD PMF Trusted Application for dynamic power management decisions. ++ * These correspond to the Windows power slider UI positions. ++ */ ++enum amd_pmf_ta_slider { ++ AMD_PMF_TA_BEST_BATTERY, ++ AMD_PMF_TA_BETTER_BATTERY, ++ AMD_PMF_TA_BETTER_PERFORMANCE, ++ AMD_PMF_TA_BEST_PERFORMANCE, ++ AMD_PMF_TA_MAX, ++}; ++ ++/** ++ * enum amd_pmf_platform_type - Describes the physical form factor orientation ++ * @AMD_PMF_PTYPE_UNKNOWN: Platform type cannot be determined ++ * @AMD_PMF_LID_CLOSE: Laptop lid is closed ++ * @AMD_PMF_CLAMSHELL: Traditional laptop mode with keyboard and screen ++ * @AMD_PMF_FLAT: Device is lying flat on a surface ++ * @AMD_PMF_TENT: Device is in tent mode (keyboard folded back, standing) ++ * @AMD_PMF_STAND: Device is propped up in stand orientation ++ * @AMD_PMF_TABLET: Device is in tablet mode with keyboard hidden ++ * @AMD_PMF_BOOK: Device is in book reading orientation ++ * @AMD_PMF_PRESENTATION: Device is in presentation mode ++ * @AMD_PMF_PULL_FWD: Screen is pulled forward towards user ++ * @AMD_PMF_PTYPE_INVALID: Invalid platform type marker ++ * ++ * This enumeration describes the current physical orientation or form ++ * factor of convertible/2-in-1 devices. Used for optimizing power and ++ * thermal management based on device posture. ++ */ ++enum amd_pmf_platform_type { ++ AMD_PMF_PTYPE_UNKNOWN, ++ AMD_PMF_LID_CLOSE, ++ AMD_PMF_CLAMSHELL, ++ AMD_PMF_FLAT, ++ AMD_PMF_TENT, ++ AMD_PMF_STAND, ++ AMD_PMF_TABLET, ++ AMD_PMF_BOOK, ++ AMD_PMF_PRESENTATION, ++ AMD_PMF_PULL_FWD, ++ AMD_PMF_PTYPE_INVALID = 0xf, ++}; ++ ++static inline const char *amd_pmf_get_platform_type(unsigned int platform_type) ++{ ++ switch (platform_type) { ++ case AMD_PMF_CLAMSHELL: ++ return "CLAMSHELL"; ++ case AMD_PMF_LID_CLOSE: ++ return "LID_CLOSE"; ++ case AMD_PMF_FLAT: ++ return "FLAT"; ++ case AMD_PMF_TENT: ++ return "TENT"; ++ case AMD_PMF_STAND: ++ return "STAND"; ++ case AMD_PMF_TABLET: ++ return "TABLET"; ++ case AMD_PMF_BOOK: ++ return "BOOK"; ++ case AMD_PMF_PRESENTATION: ++ return "PRESENTATION"; ++ case AMD_PMF_PULL_FWD: ++ return "PULL_FWD"; ++ default: ++ return "UNKNOWN"; ++ } ++} ++ ++static inline const char *amd_pmf_get_laptop_placement(unsigned int device_state) ++{ ++ switch (device_state) { ++ case AMD_PMF_ON_TABLE: ++ return "ON_TABLE"; ++ case AMD_PMF_ON_LAP_MOTION: ++ return "ON_LAP_MOTION"; ++ case AMD_PMF_IN_BAG: ++ return "IN_BAG"; ++ case AMD_PMF_OUT_OF_BAG: ++ return "OUT_OF_BAG"; ++ default: ++ return "UNKNOWN"; ++ } ++} ++ ++static inline const char *amd_pmf_get_slider_position(unsigned int state) ++{ ++ switch (state) { ++ case AMD_PMF_TA_BEST_PERFORMANCE: ++ return "PERFORMANCE"; ++ case AMD_PMF_TA_BETTER_PERFORMANCE: ++ return "BALANCED"; ++ case AMD_PMF_TA_BEST_BATTERY: ++ return "POWER_SAVER"; ++ case AMD_PMF_TA_BETTER_BATTERY: ++ return "BALANCED_BATTERY"; ++ default: ++ return "Unknown TA Slider State"; ++ } ++} ++ ++struct amd_pmf_info { ++ __u64 size; ++ ++ /* Feature info */ ++ __u32 features_supported; ++ ++ /* Power and state info */ ++ __u32 platform_type; ++ __u32 power_source; ++ __u32 laptop_placement; ++ __u32 lid_state; ++ __u32 user_presence; ++ __u32 slider_position; ++ ++ /* Thermal and power metrics */ ++ __s32 skin_temp; ++ __u32 gfx_busy; ++ __s32 ambient_light; ++ __u32 avg_c0_residency; ++ __u32 max_c0_residency; ++ __u32 socket_power; ++ ++ /* BIOS parameters */ ++ __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; ++ __u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX]; ++}; ++ ++#endif /* _UAPI_LINUX_AMD_PMF_H */ diff --git a/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..37ef2d1c37da1e969fda09e034130544712beb08 GIT binary patch literal 594 zcmV-Y0Bq&{l;!2eCFpt8mOCdQnXT`E<5=dMN>X>2(#NpeBrp5!6R z+gFl8iMxaYi*vBP$G8Zr1~={6wt#nxvBapzE7IUxpRz;gHY%v8lrxN~8?Jp5liE;%hNw1%x^L2mxV0 zocrrwqF~}!Hi=T0W4c%#GiKyeUb1iEK~B0ewaS$tdM4FzWjm!`|c#O}uH(G1H>oFS!z#;W*;U`WQ zDjA=6+9(zAVV&KNE!F(v$a;UK(uwk54HhDho5EsNoqF*^JGWM1?Yb!ZK=1G5VN~IG zokh7ki*ftSFNQ`rG(6lGPx# literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch b/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch new file mode 100644 index 0000000..029ebd2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch @@ -0,0 +1,34 @@ +diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c +index 1111111..2222222 100644 +--- a/drivers/platform/x86/amd/pmf/util.c ++++ b/drivers/platform/x86/amd/pmf/util.c +@@ -79,9 +79,9 @@ static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info * + + static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { +- struct amd_pmf_dev *pdev = filp->private_data; + void __user *argp = (void __user *)arg; + struct amd_pmf_info info = {}; ++ struct amd_pmf_dev *pdev; + size_t copy_size; + __u64 user_size; + int ret; +@@ -94,6 +94,10 @@ static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long + return -EFAULT; + + guard(mutex)(&pmf_util_lock); ++ pdev = pmf_dev_handle; ++ if (!pdev) ++ return -ENODEV; ++ + ret = amd_pmf_populate_data(pdev, &info); + if (ret) + return ret; +@@ -115,7 +119,6 @@ static int amd_pmf_open(struct inode *inode, struct file *filp) + if (!pmf_dev_handle) + return -ENODEV; + +- filp->private_data = pmf_dev_handle; + return 0; + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch.sig b/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..6bc45ffb7a97b2dcbf0e57527c8e21121903ac33 GIT binary patch literal 594 zcmV-Y0BYI{rc}F z$#0sUD*U&t&W%dTT2m=!x)(Qi0!hB^o76Zw|3=7_u0S?(DwBO}l;YSnsCY>ORkeyW zSl%*)73SPln!ek#@3b`;G_no!(V3DbnU6vxJYuX!&Y3ToQ)YR2om355@@M+VnJnqm zCAI)nh9u%;*~5YG+|d2L1KE=JfILDhBW2Yt7Gm>rH95b|D0yaUv$fzP7IYKIY2oBQ z2?2LT;kkuIjTh0JkvghMwQ@Llw7r*?C(qaJHd9ydM!ebpzlWYv-PBjGS&aa|LtQgY6tB|AD@(Sy zNYX($ycelcciImh7z~apRm&Qc2q|VTI=FEqjtoUPV=cZ(LIYbX`^J0T9zgWwz*H_r z#b7%+0uj-;wIg|uO67R$5@8OECGK$skf{UT7qDb`Ig)nFcDjVp3VExjlw6b$wEHes go!=}-A^~_&l!&+4Tqq5>9`s{yg9Fck7;0C)Fw2G$3jhEB literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch new file mode 100644 index 0000000..b7d82cc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch @@ -0,0 +1,240 @@ +diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c +--- a/drivers/platform/x86/amd/pmf/spc.c ++++ b/drivers/platform/x86/amd/pmf/spc.c +@@ -17,7 +17,6 @@ + #include + #include "pmf.h" + +-#ifdef CONFIG_AMD_PMF_DEBUG + u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + { + switch (index) { +@@ -31,6 +30,7 @@ u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + } + EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); + ++#ifdef CONFIG_AMD_PMF_DEBUG + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) + { + int i; +diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c +--- a/drivers/platform/x86/x86-android-tablets/core.c ++++ b/drivers/platform/x86/x86-android-tablets/core.c +@@ -156,7 +156,6 @@ static struct platform_device **pdevs; + static struct serdev_device **serdevs; + static const struct software_node **gpio_button_swnodes; + static const struct software_node **swnode_group; +-static const struct software_node **gpiochip_node_group; + static void (*exit_handler)(void); + + static __init struct i2c_adapter * +@@ -362,6 +361,15 @@ static const struct software_node *cherryview_gpiochip_node_group[] = { + NULL + }; + ++const struct software_node crystalcove_gpiochip_node = { ++ .name = "INT33FD:00", ++}; ++ ++static const struct software_node *crystalcove_gpiochip_node_group[] = { ++ &crystalcove_gpiochip_node, ++ NULL ++}; ++ + static void gpio_secondary_unset(void *data) + { + struct device *dev = data; +@@ -377,27 +385,28 @@ static void gpio_secondary_unregister_node_group(void *data) + software_node_unregister_node_group(nodes); + } + +-static int gpio_secondary_fwnode_init(struct device *parent) ++static int gpio_secondary_fwnode_init(struct device *parent, ++ const struct software_node * const *node_group) + { + const struct software_node *const *swnode; + struct fwnode_handle *fwnode; + struct device *phys_dev; + int ret; + +- if (!gpiochip_node_group) ++ if (!node_group) + return 0; + +- ret = software_node_register_node_group(gpiochip_node_group); ++ ret = software_node_register_node_group(node_group); + if (ret) + return ret; + + ret = devm_add_action_or_reset(parent, + gpio_secondary_unregister_node_group, +- gpiochip_node_group); ++ (void *)node_group); + if (ret) + return ret; + +- for (swnode = gpiochip_node_group; *swnode; swnode++) { ++ for (swnode = node_group; *swnode; swnode++) { + struct device *dev __free(put_device) = + acpi_bus_find_device_by_name((*swnode)->name); + if (!dev) +@@ -460,6 +469,7 @@ static void x86_android_tablet_remove(struct platform_device *pdev) + + static __init int x86_android_tablet_probe(struct platform_device *pdev) + { ++ const struct software_node * const *gpiochip_node_group; + const struct x86_dev_info *dev_info; + const struct dmi_system_id *id; + int i, ret = 0; +@@ -491,12 +501,20 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) + break; + } + +- ret = gpio_secondary_fwnode_init(&pdev->dev); ++ ret = gpio_secondary_fwnode_init(&pdev->dev, gpiochip_node_group); + if (ret) { + x86_android_tablet_remove(pdev); + return ret; + } + ++ if (dev_info->has_crystalcove) { ++ ret = gpio_secondary_fwnode_init(&pdev->dev, crystalcove_gpiochip_node_group); ++ if (ret) { ++ x86_android_tablet_remove(pdev); ++ return ret; ++ } ++ } ++ + ret = software_node_register_node_group(dev_info->swnode_group); + if (ret) { + x86_android_tablet_remove(pdev); +diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c +--- a/drivers/platform/x86/x86-android-tablets/lenovo.c ++++ b/drivers/platform/x86/x86-android-tablets/lenovo.c +@@ -61,13 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { + .initial_brightness = 128, + }; + +-static const struct software_node arizona_gpiochip_node = { +- .name = "arizona", +-}; +- +-static const struct software_node crystalcove_gpiochip_node = { +- .name = "gpio_crystalcove", +-}; + + /* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */ + +@@ -416,15 +409,17 @@ static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __ini + + #define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" + ++static const struct software_node lenovo_yoga_tab2_830_1050_wm5102; ++ + static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("reset-gpios", + &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", +- &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), ++ &lenovo_yoga_tab2_830_1050_wm5102, 2, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", +- &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW), ++ &lenovo_yoga_tab2_830_1050_wm5102, 4, GPIO_ACTIVE_LOW), + { } + }; + +@@ -432,14 +427,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { + .properties = lenovo_yoga_tab2_830_1050_wm1502_props, + }; + +-static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { +- &crystalcove_gpiochip_node, +- &arizona_gpiochip_node, +- &lenovo_yoga_tab2_830_1050_wm5102, +- &generic_lipo_hv_4v35_battery_node, +- NULL +-}; +- + static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); + static void lenovo_yoga_tab2_830_1050_exit(void); + +@@ -455,8 +442,9 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { + .pdev_info = lenovo_yoga_tab2_830_1050_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, +- .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, ++ .swnode_group = generic_lipo_hv_4v35_battery_swnodes, + .modules = lenovo_yoga_tab2_modules, ++ .has_crystalcove = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .init = lenovo_yoga_tab2_830_1050_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +@@ -800,8 +788,9 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { + .pdev_info = lenovo_yoga_tab2_1380_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, +- .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, ++ .swnode_group = generic_lipo_hv_4v35_battery_swnodes, + .modules = lenovo_yoga_tab2_modules, ++ .has_crystalcove = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .init = lenovo_yoga_tab2_1380_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +@@ -985,13 +974,15 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = { + }, + }; + ++static const struct software_node lenovo_yt3_wm5102; ++ + static const struct property_entry lenovo_yt3_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", + &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH), +- PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), ++ PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &lenovo_yt3_wm5102, 2, GPIO_ACTIVE_HIGH), + { } + }; + +@@ -1000,11 +991,6 @@ static const struct software_node lenovo_yt3_wm5102 = { + .name = "wm5102", + }; + +-static const struct software_node *lenovo_yt3_swnodes[] = { +- &arizona_gpiochip_node, +- &lenovo_yt3_wm5102, +- NULL +-}; + + static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { + { +@@ -1072,7 +1058,6 @@ const struct x86_dev_info lenovo_yt3_info __initconst = { + .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients), + .spi_dev_info = lenovo_yt3_spi_devs, + .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs), +- .swnode_group = lenovo_yt3_swnodes, + .modules = lenovo_yt3_modules, + .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, + .init = lenovo_yt3_init, +diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +--- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h ++++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +@@ -96,6 +96,7 @@ struct x86_dev_info { + int (*init)(struct device *dev); + void (*exit)(void); + bool use_pci; ++ bool has_crystalcove; + enum x86_gpiochip_type gpiochip_type; + }; + +@@ -107,6 +108,7 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data); + /* Software nodes representing GPIO chips used by various tablets */ + extern const struct software_node baytrail_gpiochip_nodes[]; + extern const struct software_node cherryview_gpiochip_nodes[]; ++extern const struct software_node crystalcove_gpiochip_node; + + /* + * Extern declarations of x86_dev_info structs so there can be a single diff --git a/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..f2ddd7ae2d4722fe336c7a8aa1d5dd16c4dfcaa7 GIT binary patch literal 594 zcmV-Y0Nq`(OBWQk*qjw;Lv$;eo72V4&LXN(sbEcY)J>ro!xm zAJ3X#o?)1k9xU9Wbh<$rgC@}(ZUb8eIYV1JuJ8sNHdo{yo~79ruU>m3GXo^SHt>=qpQe9ZgAcace++aosrSgFt zLo9Cq!v(^s)Q9$q%ci~XVqasZTAC!;W29|Oe2?VkN~SnN*LW%gb!bb0Ac5sDh$!$% zAiGe=T#3G+)ww*v*z5=LT$SnpWknUMS1xCPYK- g^3=lJ*#6-cJZKdXB#%tfCAb-l_J*_Dnb{RP*XDa3`2YX_ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch b/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch new file mode 100644 index 0000000..c3246f0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch @@ -0,0 +1,412 @@ +diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c +--- a/drivers/acpi/battery.c ++++ b/drivers/acpi/battery.c +@@ -736,7 +736,7 @@ static LIST_HEAD(acpi_battery_list); + static LIST_HEAD(battery_hook_list); + static DEFINE_MUTEX(hook_mutex); + +-static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) ++static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) + { + struct acpi_battery *battery; + +@@ -753,7 +753,7 @@ static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) + pr_info("hook unregistered: %s\n", hook->name); + } + +-void battery_hook_unregister(struct acpi_battery_hook *hook) ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) + { + mutex_lock(&hook_mutex); + /* +@@ -762,13 +762,13 @@ void battery_hook_unregister(struct acpi_battery_hook *hook) + * adding a new battery. + */ + if (!list_empty(&hook->list)) +- battery_hook_unregister_unlocked(hook); ++ acpi_battery_hook_unregister_unlocked(hook); + + mutex_unlock(&hook_mutex); + } +-EXPORT_SYMBOL_GPL(battery_hook_unregister); ++EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); + +-void battery_hook_register(struct acpi_battery_hook *hook) ++void acpi_battery_hook_register(struct acpi_battery_hook *hook) + { + struct acpi_battery *battery; + +@@ -789,7 +789,7 @@ void battery_hook_register(struct acpi_battery_hook *hook) + * hooks. + */ + pr_err("hook failed to load: %s", hook->name); +- battery_hook_unregister_unlocked(hook); ++ acpi_battery_hook_unregister_unlocked(hook); + goto end; + } + +@@ -799,22 +799,22 @@ void battery_hook_register(struct acpi_battery_hook *hook) + end: + mutex_unlock(&hook_mutex); + } +-EXPORT_SYMBOL_GPL(battery_hook_register); ++EXPORT_SYMBOL_GPL(acpi_battery_hook_register); + +-static void devm_battery_hook_unregister(void *data) ++static void devm_acpi_battery_hook_unregister(void *data) + { + struct acpi_battery_hook *hook = data; + +- battery_hook_unregister(hook); ++ acpi_battery_hook_unregister(hook); + } + +-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) ++int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) + { +- battery_hook_register(hook); ++ acpi_battery_hook_register(hook); + +- return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook); ++ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); + } +-EXPORT_SYMBOL_GPL(devm_battery_hook_register); ++EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); + + /* + * This function gets called right after the battery sysfs +@@ -843,7 +843,7 @@ static void battery_hook_add_battery(struct acpi_battery *battery) + */ + pr_err("error in hook, unloading: %s", + hook_node->name); +- battery_hook_unregister_unlocked(hook_node); ++ acpi_battery_hook_unregister_unlocked(hook_node); + } + } + mutex_unlock(&hook_mutex); +@@ -876,7 +876,7 @@ static void __exit battery_hook_exit(void) + * need to remove the hooks. + */ + list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +- battery_hook_unregister(hook); ++ acpi_battery_hook_unregister(hook); + } + mutex_destroy(&hook_mutex); + } +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -1681,14 +1681,14 @@ static void asus_wmi_battery_init(struct asus_wmi *asus) + asus->battery_rsoc_available = false; + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) { + asus->battery_rsoc_available = true; +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + } + } + + static void asus_wmi_battery_exit(struct asus_wmi *asus) + { + if (asus->battery_rsoc_available) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + /* LEDs ***********************************************************************/ +diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c +--- a/drivers/platform/x86/ayaneo-ec.c ++++ b/drivers/platform/x86/ayaneo-ec.c +@@ -493,7 +493,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev) + data->battery_hook.add_battery = ayaneo_add_battery; + data->battery_hook.remove_battery = ayaneo_remove_battery; + data->battery_hook.name = "Ayaneo Battery"; +- ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook); ++ ret = devm_acpi_battery_hook_register(&pdev->dev, &data->battery_hook); + if (ret) + return ret; + } +diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c +--- a/drivers/platform/x86/dell/dell-laptop.c ++++ b/drivers/platform/x86/dell/dell-laptop.c +@@ -2446,13 +2446,13 @@ static void __init dell_battery_init(struct device *dev) + battery_supported_modes = battery_get_supported_modes(); + + if (battery_supported_modes != 0) +- battery_hook_register(&dell_battery_hook); ++ acpi_battery_hook_register(&dell_battery_hook); + } + + static void dell_battery_exit(void) + { + if (battery_supported_modes != 0) +- battery_hook_unregister(&dell_battery_hook); ++ acpi_battery_hook_unregister(&dell_battery_hook); + } + + static int __init dell_init(void) +diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c +--- a/drivers/platform/x86/dell/dell-wmi-ddv.c ++++ b/drivers/platform/x86/dell/dell-wmi-ddv.c +@@ -972,7 +972,7 @@ static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) + data->eppid_attr.attr.mode = 0444; + data->eppid_attr.show = eppid_show; + +- return devm_battery_hook_register(&data->wdev->dev, &data->hook); ++ return devm_acpi_battery_hook_register(&data->wdev->dev, &data->hook); + } + + static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method method) +diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c +--- a/drivers/platform/x86/fujitsu-laptop.c ++++ b/drivers/platform/x86/fujitsu-laptop.c +@@ -267,7 +267,7 @@ static int fujitsu_battery_charge_control_add(struct device *dev) + return -ENODEV; + + priv->charge_control_supported = true; +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + } +@@ -277,7 +277,7 @@ static void fujitsu_battery_charge_control_remove(struct device *dev) + struct fujitsu_laptop *priv = dev_get_drvdata(dev); + + if (priv->charge_control_supported) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + /* Hardware access for LCD brightness control */ +diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c +--- a/drivers/platform/x86/huawei-wmi.c ++++ b/drivers/platform/x86/huawei-wmi.c +@@ -512,7 +512,7 @@ static void huawei_wmi_battery_setup(struct device *dev) + return; + } + +- battery_hook_register(&huawei_wmi_battery_hook); ++ acpi_battery_hook_register(&huawei_wmi_battery_hook); + device_create_file(dev, &dev_attr_charge_control_thresholds); + } + +@@ -521,7 +521,7 @@ static void huawei_wmi_battery_exit(struct device *dev) + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + if (huawei->battery_available) { +- battery_hook_unregister(&huawei_wmi_battery_hook); ++ acpi_battery_hook_unregister(&huawei_wmi_battery_hook); + device_remove_file(dev, &dev_attr_charge_control_thresholds); + } + } +diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c +--- a/drivers/platform/x86/lenovo/ideapad-laptop.c ++++ b/drivers/platform/x86/lenovo/ideapad-laptop.c +@@ -2197,8 +2197,8 @@ static int ideapad_check_features(struct ideapad_private *priv) + priv->battery_hook.remove_battery = ideapad_battery_remove; + priv->battery_hook.name = "Ideapad Battery Extension"; + +- err = devm_battery_hook_register(&priv->platform_device->dev, +- &priv->battery_hook); ++ err = devm_acpi_battery_hook_register(&priv->platform_device->dev, ++ &priv->battery_hook); + if (err) + return err; + } +diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c +--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c ++++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c +@@ -10021,13 +10021,13 @@ static int __init tpacpi_battery_init(struct ibm_init_struct *ibm) + battery_quirk_table, + ARRAY_SIZE(battery_quirk_table)); + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + return 0; + } + + static void tpacpi_battery_exit(void) + { +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + static struct ibm_struct battery_driver_data = { +diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c +--- a/drivers/platform/x86/lenovo/wmi-other.c ++++ b/drivers/platform/x86/lenovo/wmi-other.c +@@ -946,7 +946,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv) + priv->battery_hook.name = "Lenovo WMI Other Battery Extension"; + priv->bh_registered = true; + +- battery_hook_register(&priv->battery_hook); ++ acpi_battery_hook_register(&priv->battery_hook); + } + + /** +@@ -960,7 +960,7 @@ static void lwmi_om_psy_remove(struct lwmi_om_priv *priv) + if (!priv->bh_registered) + return; + +- battery_hook_unregister(&priv->battery_hook); ++ acpi_battery_hook_unregister(&priv->battery_hook); + priv->bh_registered = false; + } + +diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c +--- a/drivers/platform/x86/lg-laptop.c ++++ b/drivers/platform/x86/lg-laptop.c +@@ -1053,7 +1053,7 @@ static int acpi_probe(struct platform_device *pdev) + if (ret) + goto out_platform_device; + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + +@@ -1068,7 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) + { + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + platform_device_unregister(pf_device); + pf_device = NULL; + platform_driver_unregister(&pf_driver); +diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c +--- a/drivers/platform/x86/msi-ec.c ++++ b/drivers/platform/x86/msi-ec.c +@@ -1428,13 +1428,13 @@ static int __init msi_ec_init(void) + if (result < 0) + return result; + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + return 0; + } + + static void __exit msi_ec_exit(void) + { +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c +--- a/drivers/platform/x86/oxpec.c ++++ b/drivers/platform/x86/oxpec.c +@@ -956,7 +956,7 @@ static int oxp_platform_probe(struct platform_device *pdev) + return PTR_ERR(hwdev); + + if (oxp_psy_ext_supported()) { +- ret = devm_battery_hook_register(dev, &battery_hook); ++ ret = devm_acpi_battery_hook_register(dev, &battery_hook); + if (ret) + return ret; + } +diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c +--- a/drivers/platform/x86/samsung-galaxybook.c ++++ b/drivers/platform/x86/samsung-galaxybook.c +@@ -549,7 +549,8 @@ static int galaxybook_battery_threshold_init(struct samsung_galaxybook *galaxybo + galaxybook->battery_hook.remove_battery = galaxybook_battery_remove; + galaxybook->battery_hook.name = "Samsung Galaxy Book Battery Extension"; + +- return devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook); ++ return devm_acpi_battery_hook_register(&galaxybook->platform->dev, ++ &galaxybook->battery_hook); + } + + /* +diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c +--- a/drivers/platform/x86/samsung-laptop.c ++++ b/drivers/platform/x86/samsung-laptop.c +@@ -1142,8 +1142,8 @@ static int __init samsung_battery_hook_init(struct samsung_laptop *samsung) + samsung->battery_hook.add_battery = samsung_battery_add; + samsung->battery_hook.remove_battery = samsung_battery_remove; + samsung->battery_hook.name = "Samsung Battery Extension"; +- retval = devm_battery_hook_register(&samsung->platform_device->dev, +- &samsung->battery_hook); ++ retval = devm_acpi_battery_hook_register(&samsung->platform_device->dev, ++ &samsung->battery_hook); + } + + return retval; +diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c +--- a/drivers/platform/x86/system76_acpi.c ++++ b/drivers/platform/x86/system76_acpi.c +@@ -288,12 +288,12 @@ static struct acpi_battery_hook system76_battery_hook = { + + static void system76_battery_init(void) + { +- battery_hook_register(&system76_battery_hook); ++ acpi_battery_hook_register(&system76_battery_hook); + } + + static void system76_battery_exit(void) + { +- battery_hook_unregister(&system76_battery_hook); ++ acpi_battery_hook_unregister(&system76_battery_hook); + } + + // Get the airplane mode LED brightness +diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c +--- a/drivers/platform/x86/toshiba_acpi.c ++++ b/drivers/platform/x86/toshiba_acpi.c +@@ -3301,7 +3301,7 @@ static void toshiba_acpi_remove(struct platform_device *pdev) + } + + if (dev->battery_charge_mode_supported) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + + if (toshiba_acpi) + toshiba_acpi = NULL; +@@ -3566,7 +3566,7 @@ static int toshiba_acpi_probe(struct platform_device *pdev) + * set, this must be done after toshiba_acpi is assigned. + */ + if (dev->battery_charge_mode_supported) +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + +diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c +--- a/drivers/platform/x86/uniwill/uniwill-acpi.c ++++ b/drivers/platform/x86/uniwill/uniwill-acpi.c +@@ -2165,7 +2165,7 @@ static int uniwill_battery_init(struct uniwill_data *data) + data->hook.add_battery = uniwill_add_battery; + data->hook.remove_battery = uniwill_remove_battery; + +- return devm_battery_hook_register(data->dev, &data->hook); ++ return devm_acpi_battery_hook_register(data->dev, &data->hook); + } + + static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy) +diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c +--- a/drivers/power/supply/cros_charge-control.c ++++ b/drivers/power/supply/cros_charge-control.c +@@ -315,7 +315,7 @@ static int cros_chctl_probe(struct platform_device *pdev) + if (ret < 0) + return ret; + +- return devm_battery_hook_register(dev, &priv->battery_hook); ++ return devm_acpi_battery_hook_register(dev, &priv->battery_hook); + } + + static const struct platform_device_id cros_chctl_id[] = { +diff --git a/include/acpi/battery.h b/include/acpi/battery.h +--- a/include/acpi/battery.h ++++ b/include/acpi/battery.h +@@ -18,8 +18,9 @@ struct acpi_battery_hook { + struct list_head list; + }; + +-void battery_hook_register(struct acpi_battery_hook *hook); +-void battery_hook_unregister(struct acpi_battery_hook *hook); +-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook); ++void acpi_battery_hook_register(struct acpi_battery_hook *hook); ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook); ++int devm_acpi_battery_hook_register(struct device *dev, ++ struct acpi_battery_hook *hook); + + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch.sig b/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..84b720634c98ff43ec23646b6870c8be992701ed GIT binary patch literal 594 zcmV-Y0h8IF>{y<)pk>SCi>-C?$53L&THeXJLw6!H(JT54W!n#Kn%LxX-{aj z7t76WYaD(8w1J8%&=CJzmeWzkN#euqr=iEZ8O*n&#?*3(-?)n@*N*sAy}9^TgRv&! zJqltNBRvHUCn)JChZb8g=ia@I_nCr1DY)0;)(%)Q^Pe^-> zmxwf!emPrDdp2QPl`&GqJ(M5_9KSbBly|g(Qa*6Ox_2ZW?wr9?`63Qh5dd}YaGF7C z+0T)^oV9LcPp5-*&F|VNTp@uwfKlz=q&42x`Y*&LM^G(wF*|x>D|E?a"); + MODULE_DESCRIPTION("ACPI Battery Driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS"); + + static int battery_bix_broken_package; + static int battery_notification_delay_ms; +@@ -97,7 +98,7 @@ struct acpi_battery { + struct acpi_device *device; + struct device *phys_dev; + struct notifier_block pm_nb; +- struct list_head list; ++ struct acpi_battery_hooks_list_entry hooks_list_entry; + unsigned long flags; + + struct mutex property_lock; /* Protects properties below. */ +@@ -723,164 +724,6 @@ static struct attribute *acpi_battery_attrs[] = { + }; + ATTRIBUTE_GROUPS(acpi_battery); + +-/* +- * The Battery Hooking API +- * +- * This API is used inside other drivers that need to expose +- * platform-specific behaviour within the generic driver in a +- * generic way. +- * +- */ +- +-static LIST_HEAD(acpi_battery_list); +-static LIST_HEAD(battery_hook_list); +-static DEFINE_MUTEX(hook_mutex); +- +-static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) +-{ +- struct acpi_battery *battery; +- +- /* +- * In order to remove a hook, we first need to +- * de-register all the batteries that are registered. +- */ +- list_for_each_entry(battery, &acpi_battery_list, list) { +- if (!hook->remove_battery(battery->bat, hook)) +- power_supply_changed(battery->bat); +- } +- list_del_init(&hook->list); +- +- pr_info("hook unregistered: %s\n", hook->name); +-} +- +-void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) +-{ +- mutex_lock(&hook_mutex); +- /* +- * Ignore already unregistered battery hooks. This might happen +- * if a battery hook was previously unloaded due to an error when +- * adding a new battery. +- */ +- if (!list_empty(&hook->list)) +- acpi_battery_hook_unregister_unlocked(hook); +- +- mutex_unlock(&hook_mutex); +-} +-EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); +- +-void acpi_battery_hook_register(struct acpi_battery_hook *hook) +-{ +- struct acpi_battery *battery; +- +- mutex_lock(&hook_mutex); +- list_add(&hook->list, &battery_hook_list); +- /* +- * Now that the driver is registered, we need +- * to notify the hook that a battery is available +- * for each battery, so that the driver may add +- * its attributes. +- */ +- list_for_each_entry(battery, &acpi_battery_list, list) { +- if (hook->add_battery(battery->bat, hook)) { +- /* +- * If a add-battery returns non-zero, +- * the registration of the hook has failed, +- * and we will not add it to the list of loaded +- * hooks. +- */ +- pr_err("hook failed to load: %s", hook->name); +- acpi_battery_hook_unregister_unlocked(hook); +- goto end; +- } +- +- power_supply_changed(battery->bat); +- } +- pr_info("new hook: %s\n", hook->name); +-end: +- mutex_unlock(&hook_mutex); +-} +-EXPORT_SYMBOL_GPL(acpi_battery_hook_register); +- +-static void devm_acpi_battery_hook_unregister(void *data) +-{ +- struct acpi_battery_hook *hook = data; +- +- acpi_battery_hook_unregister(hook); +-} +- +-int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) +-{ +- acpi_battery_hook_register(hook); +- +- return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); +-} +-EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); +- +-/* +- * This function gets called right after the battery sysfs +- * attributes have been added, so that the drivers that +- * define custom sysfs attributes can add their own. +- */ +-static void battery_hook_add_battery(struct acpi_battery *battery) +-{ +- struct acpi_battery_hook *hook_node, *tmp; +- +- mutex_lock(&hook_mutex); +- INIT_LIST_HEAD(&battery->list); +- list_add(&battery->list, &acpi_battery_list); +- /* +- * Since we added a new battery to the list, we need to +- * iterate over the hooks and call add_battery for each +- * hook that was registered. This usually happens +- * when a battery gets hotplugged or initialized +- * during the battery module initialization. +- */ +- list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) { +- if (hook_node->add_battery(battery->bat, hook_node)) { +- /* +- * The notification of the hook has failed, to +- * prevent further errors we will unload the hook. +- */ +- pr_err("error in hook, unloading: %s", +- hook_node->name); +- acpi_battery_hook_unregister_unlocked(hook_node); +- } +- } +- mutex_unlock(&hook_mutex); +-} +- +-static void battery_hook_remove_battery(struct acpi_battery *battery) +-{ +- struct acpi_battery_hook *hook; +- +- mutex_lock(&hook_mutex); +- /* +- * Before removing the hook, we need to remove all +- * custom attributes from the battery. +- */ +- list_for_each_entry(hook, &battery_hook_list, list) { +- hook->remove_battery(battery->bat, hook); +- } +- /* Then, just remove the battery from the list */ +- list_del(&battery->list); +- mutex_unlock(&hook_mutex); +-} +- +-static void __exit battery_hook_exit(void) +-{ +- struct acpi_battery_hook *hook; +- struct acpi_battery_hook *ptr; +- /* +- * At this point, the acpi_bus_unregister_driver() +- * has called remove for all batteries. We just +- * need to remove the hooks. +- */ +- list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +- acpi_battery_hook_unregister(hook); +- } +- mutex_destroy(&hook_mutex); +-} +- + static int sysfs_add_battery(struct acpi_battery *battery) + { + struct power_supply_config psy_cfg = { +@@ -936,7 +779,7 @@ static int sysfs_add_battery(struct acpi_battery *battery) + battery->bat = NULL; + return result; + } +- battery_hook_add_battery(battery); ++ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat); + return 0; + } + +@@ -945,7 +788,7 @@ static void sysfs_remove_battery(struct acpi_battery *battery) + if (!battery->bat) + return; + +- battery_hook_remove_battery(battery); ++ acpi_battery_remove_hooks(&battery->hooks_list_entry); + power_supply_unregister(battery->bat); + battery->bat = NULL; + } +@@ -1391,7 +1234,6 @@ static int __init acpi_battery_init(void) + static void __exit acpi_battery_exit(void) + { + platform_driver_unregister(&acpi_battery_driver); +- battery_hook_exit(); + } + + module_init(acpi_battery_init); +diff --git a/drivers/acpi/battery_hooks.c b/drivers/acpi/battery_hooks.c +new file mode 100644 +--- /dev/null ++++ b/drivers/acpi/battery_hooks.c +@@ -0,0 +1,159 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ACPI Battery Hooks ++ * ++ * Provides helpers for registering and unregistering battery hooks for ++ * drivers that use platform-specific extensions to ACPI-enumerated ++ * batteries (both ACPI Control Method batteries and Smart Battery System ++ * batteries). ++ */ ++ ++#define pr_fmt(fmt) "ACPI: battery: " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static LIST_HEAD(acpi_battery_list); ++static LIST_HEAD(battery_hook_list); ++static DEFINE_MUTEX(hook_mutex); ++ ++static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) ++{ ++ struct acpi_battery_hooks_list_entry *entry; ++ ++ /* ++ * In order to remove a hook, we first need to ++ * de-register all the batteries that are registered. ++ */ ++ list_for_each_entry(entry, &acpi_battery_list, list_entry) { ++ if (!hook->remove_battery(entry->battery, hook)) ++ power_supply_changed(entry->battery); ++ } ++ list_del_init(&hook->list); ++ ++ pr_info("hook unregistered: %s\n", hook->name); ++} ++ ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) ++{ ++ guard(mutex)(&hook_mutex); ++ ++ /* ++ * Ignore already unregistered battery hooks. This might happen ++ * if a battery hook was previously unloaded due to an error when ++ * adding a new battery. ++ */ ++ if (!list_empty(&hook->list)) ++ acpi_battery_hook_unregister_unlocked(hook); ++} ++EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); ++ ++void acpi_battery_hook_register(struct acpi_battery_hook *hook) ++{ ++ struct acpi_battery_hooks_list_entry *entry; ++ ++ guard(mutex)(&hook_mutex); ++ ++ list_add(&hook->list, &battery_hook_list); ++ /* ++ * Now that the driver is registered, we need ++ * to notify the hook that a battery is available ++ * for each battery, so that the driver may add ++ * its attributes. ++ */ ++ list_for_each_entry(entry, &acpi_battery_list, list_entry) { ++ if (hook->add_battery(entry->battery, hook)) { ++ /* ++ * If a add-battery returns non-zero, ++ * the registration of the hook has failed, ++ * and we will not add it to the list of loaded ++ * hooks. ++ */ ++ pr_err("hook failed to load: %s\n", hook->name); ++ acpi_battery_hook_unregister_unlocked(hook); ++ return; ++ } ++ ++ power_supply_changed(entry->battery); ++ } ++ pr_info("new hook: %s\n", hook->name); ++} ++EXPORT_SYMBOL_GPL(acpi_battery_hook_register); ++ ++static void devm_acpi_battery_hook_unregister(void *data) ++{ ++ struct acpi_battery_hook *hook = data; ++ ++ acpi_battery_hook_unregister(hook); ++} ++ ++int devm_acpi_battery_hook_register(struct device *dev, ++ struct acpi_battery_hook *hook) ++{ ++ acpi_battery_hook_register(hook); ++ ++ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); ++} ++EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); ++ ++/* ++ * This function gets called right after the battery sysfs ++ * attributes have been added, so that the drivers that ++ * define custom sysfs attributes can add their own. ++ */ ++void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry, ++ struct power_supply *battery) ++{ ++ struct acpi_battery_hook *hook_node, *tmp; ++ ++ entry->battery = battery; ++ ++ guard(mutex)(&hook_mutex); ++ ++ list_add(&entry->list_entry, &acpi_battery_list); ++ /* ++ * Since we added a new battery to the list, we need to ++ * iterate over the hooks and call add_battery for each ++ * hook that was registered. This usually happens ++ * when a battery gets hotplugged or initialized ++ * during the battery module initialization. ++ */ ++ list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) { ++ if (hook_node->add_battery(entry->battery, hook_node)) { ++ /* ++ * The notification of the hook has failed, to ++ * prevent further errors we will unload the hook. ++ */ ++ pr_err("error in hook, unloading: %s\n", hook_node->name); ++ acpi_battery_hook_unregister_unlocked(hook_node); ++ } ++ } ++} ++EXPORT_SYMBOL_NS_GPL(acpi_battery_add_hooks, "ACPI_BATTERY_HOOKS"); ++ ++void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry) ++{ ++ struct acpi_battery_hook *hook; ++ ++ guard(mutex)(&hook_mutex); ++ /* ++ * Before removing the hook, we need to remove all ++ * custom attributes from the battery. ++ */ ++ list_for_each_entry(hook, &battery_hook_list, list) ++ hook->remove_battery(entry->battery, hook); ++ ++ /* Then, just remove the battery from the list */ ++ list_del(&entry->list_entry); ++ entry->battery = NULL; ++} ++EXPORT_SYMBOL_NS_GPL(acpi_battery_remove_hooks, "ACPI_BATTERY_HOOKS"); ++ ++MODULE_DESCRIPTION("ACPI battery hooks"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c +--- a/drivers/acpi/sbs.c ++++ b/drivers/acpi/sbs.c +@@ -36,6 +36,7 @@ + MODULE_AUTHOR("Alexey Starikovskiy "); + MODULE_DESCRIPTION("Smart Battery System ACPI interface driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS"); + + static unsigned int cache_time = 1000; + module_param(cache_time, uint, 0644); +@@ -54,6 +55,7 @@ struct acpi_battery { + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct acpi_sbs *sbs; ++ struct acpi_battery_hooks_list_entry hooks_list_entry; + unsigned long update_time; + char name[8]; + char manufacturer_name[ACPI_SBS_BLOCK_MAX]; +@@ -555,6 +557,8 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id) + goto end; + } + ++ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat); ++ + end: + pr_info("%s [%s]: Battery Slot [%s] (battery %s)\n", + ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), +@@ -566,8 +570,10 @@ static void acpi_battery_remove(struct acpi_sbs *sbs, int id) + { + struct acpi_battery *battery = &sbs->battery[id]; + +- if (battery->bat) ++ if (battery->bat) { ++ acpi_battery_remove_hooks(&battery->hooks_list_entry); + power_supply_unregister(battery->bat); ++ } + } + + static int acpi_charger_add(struct acpi_sbs *sbs) +diff --git a/include/acpi/battery.h b/include/acpi/battery.h +--- a/include/acpi/battery.h ++++ b/include/acpi/battery.h +@@ -18,9 +18,18 @@ struct acpi_battery_hook { + struct list_head list; + }; + ++struct acpi_battery_hooks_list_entry { ++ struct list_head list_entry; ++ struct power_supply *battery; ++}; ++ + void acpi_battery_hook_register(struct acpi_battery_hook *hook); + void acpi_battery_hook_unregister(struct acpi_battery_hook *hook); + int devm_acpi_battery_hook_register(struct device *dev, + struct acpi_battery_hook *hook); + ++void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry, ++ struct power_supply *battery); ++void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry); ++ + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig b/pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..3d8754f702ce44b6dad887b4c56a875f0527bae8 GIT binary patch literal 594 zcmV-Y0IvkZ?*LH6sl!89p=C=(wd?-%!RUnJEB9fTa@S#9|FGz!Y<�nK=>!ci z!=;69#lOj1g6QsB9g;3Cl(8+6YiJqNh8q*HhG}Nh*40|wcJ>*#YifL^gK`W@l%fjs zL1ov?gxrO~GKs~kGM?Ihig-5dFrckh@GW67w^Vi4UWff_c4qn2L@HvhT3de^TzirI z*HO5R^kc(oOf1ZJq$)6*>#=xb$kSEmtb8Y)6dR2#T&JJUS9mb=TR@OU;`+6>gkY0Sd0KbcEhKz4)s~650+Y3I<{LJ_W%F@ literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch b/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch new file mode 100644 index 0000000..9b45c66 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch @@ -0,0 +1,209 @@ +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -380,6 +380,7 @@ config SENSORS_FAM15H_POWER + config SENSORS_APPLESMC + tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" + depends on INPUT && X86 ++ depends on POWER_SUPPLY || POWER_SUPPLY=n + select NEW_LEDS + select LEDS_CLASS + help +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,8 @@ + #include + #include + #include ++#include ++#include + #include + + /* data port used by Apple SMC */ +@@ -76,6 +78,20 @@ + + #define TEMP_SENSOR_TYPE "sp78" + ++/* ++ * BCLM caps charging at a percentage. BFCL only sets when the charging LED ++ * switches from orange to green. ++ */ ++#define BATTERY_CHARGE_LIMIT_KEY "BCLM" /* r/w ui8 */ ++#define BATTERY_CHARGE_LIMIT_LED_KEY "BFCL" /* r/w ui8 */ ++ ++/* ++ * Points kept between BCLM and BFCL so the LED turns green before charging ++ * stops. Measured on a MacBookAir6,2, where a margin of 1 only just ties; ++ * 3 leaves headroom for other models. ++ */ ++#define APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN 3 ++ + /* List of keys used to read/write fan speeds */ + static const char *const fan_speed_fmt[] = { + "F%dAc", /* actual speed */ +@@ -131,6 +147,8 @@ static struct applesmc_registers { + int num_light_sensors; /* number of light sensors */ + bool has_accelerometer; /* has motion sensor */ + bool has_key_backlight; /* has keyboard backlight */ ++ bool has_battery_charge_limit; /* has BCLM battery charge limit */ ++ bool has_battery_charge_limit_led; /* has BFCL MagSafe LED charge limit */ + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ +@@ -633,6 +651,12 @@ static int applesmc_init_smcreg_try(void) + if (ret) + return ret; + ret = applesmc_has_key(BACKLIGHT_KEY, &s->has_key_backlight); ++ if (ret) ++ return ret; ++ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_KEY, &s->has_battery_charge_limit); ++ if (ret) ++ return ret; ++ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_LED_KEY, &s->has_battery_charge_limit_led); + if (ret) + return ret; + +@@ -724,6 +748,120 @@ static struct platform_driver applesmc_driver = { + }, + }; + ++static DEFINE_MUTEX(applesmc_bat_lock); ++ ++static int applesmc_bat_get_property(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ u8 limit; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ scoped_guard(mutex, &applesmc_bat_lock) ++ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1); ++ if (ret) ++ return ret; ++ val->intval = limit; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int applesmc_bat_set_property(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp, ++ const union power_supply_propval *val) ++{ ++ u8 limit, led_limit, readback; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ /* 20 is known to work and 10 is not accepted; the floor is unknown. */ ++ if (val->intval < 20 || val->intval > 100) ++ return -EINVAL; ++ ++ limit = val->intval; ++ /* At 100% charging is never cut short, so no margin is needed. */ ++ led_limit = limit < 100 ? ++ limit - APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN : limit; ++ ++ scoped_guard(mutex, &applesmc_bat_lock) { ++ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1); ++ if (ret) ++ return ret; ++ ++ /* ++ * The SMC silently ignores values it does not accept and ++ * still reports success, so read the limit back. ++ */ ++ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &readback, 1); ++ if (ret) ++ return ret; ++ if (readback != limit) ++ return -EINVAL; ++ ++ if (smcreg.has_battery_charge_limit_led) { ++ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_LED_KEY, ++ &led_limit, 1); ++ /* BCLM is already applied, so this is not an error. */ ++ if (ret) ++ pr_warn("failed to update MagSafe LED threshold (BFCL): %d\n", ++ ret); ++ } ++ } ++ ++ power_supply_changed(psy); ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int applesmc_bat_property_is_writeable(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp) ++{ ++ return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; ++} ++ ++static const enum power_supply_property applesmc_bat_props[] = { ++ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, ++}; ++ ++static const struct power_supply_ext applesmc_bat_ext = { ++ .name = "applesmc-charge-control", ++ .properties = applesmc_bat_props, ++ .num_properties = ARRAY_SIZE(applesmc_bat_props), ++ .get_property = applesmc_bat_get_property, ++ .set_property = applesmc_bat_set_property, ++ .property_is_writeable = applesmc_bat_property_is_writeable, ++}; ++ ++static int applesmc_bat_add(struct power_supply *battery, struct acpi_battery_hook *hook) ++{ ++ return power_supply_register_extension(battery, &applesmc_bat_ext, &pdev->dev, NULL); ++} ++ ++static int applesmc_bat_remove(struct power_supply *battery, struct acpi_battery_hook *hook) ++{ ++ power_supply_unregister_extension(battery, &applesmc_bat_ext); ++ return 0; ++} ++ ++static struct acpi_battery_hook applesmc_bat_hook = { ++ .name = "Apple SMC Battery Charge Control", ++ .add_battery = applesmc_bat_add, ++ .remove_battery = applesmc_bat_remove, ++}; ++ + /* + * applesmc_calibrate - Set our "resting" values. Callers must + * hold applesmc_lock. +@@ -1563,6 +1701,10 @@ static int __init applesmc_init(void) + goto out_light_ledclass; + } + ++ if (smcreg.has_battery_charge_limit && ++ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS)) ++ acpi_battery_hook_register(&applesmc_bat_hook); ++ + return 0; + + out_light_ledclass: +@@ -1589,6 +1731,10 @@ static int __init applesmc_init(void) + + static void __exit applesmc_exit(void) + { ++ if (smcreg.has_battery_charge_limit && ++ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS)) ++ acpi_battery_hook_unregister(&applesmc_bat_hook); ++ + hwmon_device_unregister(hwmon_dev); + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); diff --git a/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig b/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..992c843531e3a8a893e1f063ebbbd058f237f5ec GIT binary patch literal 594 zcmV-Y0MV2_$ zk2j4$DNExLv853zgN-G=S39A>gYp>$IPYMX<3v9&o?A@4{U@TgpX7uG0|zoOu_1=v z(|WKan&KcN7EK^9)`iFnVm<2Ea_31=kC4ck3AtSiN>BYsk4MapB-MjApC(X6!!M*a zZ1Cs$JN$slZ+>|75gY~9+jWmeZDI-_Im6Rm*6r26j#kxegw*CpA5{st1GPqtvn5@W z5N4L+(`Rl81VnCTx0w*p5xo!&LL$76m7OA#4@Szh7@vPgVayKEae3sQec|%A>b~^% zDdWN%5@v=hp8quFjDei%aY)l-7$Qz_k+Bluhd-|a!zK1)l}otr;yhX>c|0P`>u!wA zKB*NQ#4jMe^7=&FtOKm@a7h|EXm{d#*7-sEB$sv^-he g+#fDvViKiu+MwQ?l7($CAS~;{H;IBmOV literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch b/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch new file mode 100644 index 0000000..d4ab07f --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch @@ -0,0 +1,142 @@ +diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h +--- a/include/uapi/linux/futex.h ++++ b/include/uapi/linux/futex.h +@@ -22,6 +22,7 @@ + #define FUTEX_WAIT_REQUEUE_PI 11 + #define FUTEX_CMP_REQUEUE_PI 12 + #define FUTEX_LOCK_PI2 13 ++#define FUTEX_WAIT_MULTIPLE 31 + + #define FUTEX_PRIVATE_FLAG 128 + #define FUTEX_CLOCK_REALTIME 256 +@@ -126,6 +127,18 @@ struct futex_waitv { + __u32 __reserved; + }; + ++/** ++ * struct futex_wait_block - Block of futexes to be waited for ++ * @uaddr: User address of the futex ++ * @val: Futex value expected by userspace ++ * @bitset: Bitset for the optional bitmasked wakeup ++ */ ++struct futex_wait_block { ++ __u32 __user *uaddr; ++ __u32 val; ++ __u32 bitset; ++}; ++ + /* + * Support for robust futexes: the kernel cleans up held futexes at + * thread exit time. +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -169,6 +169,7 @@ static __always_inline bool futex_cmd_has_timeout(u32 cmd) + case FUTEX_LOCK_PI2: + case FUTEX_WAIT_BITSET: + case FUTEX_WAIT_REQUEUE_PI: ++ case FUTEX_WAIT_MULTIPLE: + return true; + } + return false; +@@ -181,13 +182,79 @@ futex_init_timeout(u32 cmd, u32 op, struct timespec64 *ts, ktime_t *t) + return -EINVAL; + + *t = timespec64_to_ktime(*ts); +- if (cmd == FUTEX_WAIT) ++ if (cmd == FUTEX_WAIT || cmd == FUTEX_WAIT_MULTIPLE) + *t = ktime_add_safe(ktime_get(), *t); + else if (cmd != FUTEX_LOCK_PI && !(op & FUTEX_CLOCK_REALTIME)) + *t = timens_ktime_to_host(CLOCK_MONOTONIC, *t); + return 0; + } + ++/** ++ * futex_read_wait_block - Read an array of futex_wait_block from userspace ++ * @uaddr: Userspace address of the block ++ * @count: Number of blocks to be read ++ * ++ * This function creates and allocate an array of futex_q (we zero it to ++ * initialize the fields) and then, for each futex_wait_block element from ++ * userspace, fill a futex_q element with proper values. ++ */ ++inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++{ ++ unsigned int i; ++ struct futex_vector *futexv; ++ struct futex_wait_block fwb; ++ struct futex_wait_block __user *entry = ++ (struct futex_wait_block __user *)uaddr; ++ ++ if (!count || count > FUTEX_WAITV_MAX) ++ return ERR_PTR(-EINVAL); ++ ++ futexv = kcalloc(count, sizeof(*futexv), GFP_KERNEL); ++ if (!futexv) ++ return ERR_PTR(-ENOMEM); ++ ++ for (i = 0; i < count; i++) { ++ if (copy_from_user(&fwb, &entry[i], sizeof(fwb))) { ++ kfree(futexv); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ futexv[i].w.flags = FUTEX_32; ++ futexv[i].w.val = fwb.val; ++ futexv[i].w.uaddr = (uintptr_t) (fwb.uaddr); ++ futexv[i].q = futex_q_init; ++ } ++ ++ return futexv; ++} ++ ++int futex_wait_multiple(struct futex_vector *vs, unsigned int count, ++ struct hrtimer_sleeper *to); ++ ++int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++{ ++ int ret; ++ struct futex_vector *vs; ++ struct hrtimer_sleeper *to = NULL, timeout; ++ ++ to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ ++ vs = futex_read_wait_block(uaddr, count); ++ ++ if (IS_ERR(vs)) ++ return PTR_ERR(vs); ++ ++ ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); ++ kfree(vs); ++ ++ if (to) { ++ hrtimer_cancel(&to->timer); ++ destroy_hrtimer_on_stack(&to->timer); ++ } ++ ++ return ret; ++} ++ + SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, + const struct __kernel_timespec __user *, utime, + u32 __user *, uaddr2, u32, val3) +@@ -207,6 +274,9 @@ SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, + tp = &t; + } + ++ if (cmd == FUTEX_WAIT_MULTIPLE) ++ return futex_opcode_31(tp, uaddr, val); ++ + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } + +@@ -521,6 +591,9 @@ SYSCALL_DEFINE6(futex_time32, u32 __user *, uaddr, int, op, u32, val, + tp = &t; + } + ++ if (cmd == FUTEX_WAIT_MULTIPLE) ++ return futex_opcode_31(tp, uaddr, val); ++ + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } + #endif /* CONFIG_COMPAT_32BIT_TIME */ diff --git a/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch.sig b/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..c9e1f4ed1b8a6e4b269c96b432ba932588f2af0e GIT binary patch literal 594 zcmV-Y0d3g)id+HbK;;DY2|+jzAr!^^7W<2F(u4&ycD3Ajb6Wg{EvV{k0_OSTMFYv zV`C!EcMv}4`eZ^c$U0ya|N1m&-M_+bB@9FF4?Y2$iU!xPP)9Z!B$l@4amn$+*$&`| zOLigSP4;k&TT%BfLs}Zn)+uCaGocKQ$h`~}W z@);rL7yzx+6oh?-&GawsWI46%FwS7ND`X6308csXF@A_bGS!KMvC%R}xA-X0*82et%? zA8|qA?7$*eGt2V3m$+U;(ylbMlMdm6%^mT#^@+qK}cKIbn@wN1sB8px`H93$y z5giJ6qbRqF|nXoD2<3*?ARqrdHd!>p{iVzrz geHs?N6#J0XU;$G#A$|U{_X_@h-Y6WhyC8KUWvQAW1ONa4 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch new file mode 100644 index 0000000..671934a --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch @@ -0,0 +1,35 @@ +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -198,7 +198,7 @@ + * initialize the fields) and then, for each futex_wait_block element from + * userspace, fill a futex_q element with proper values. + */ +-inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) + { + unsigned int i; + struct futex_vector *futexv; +@@ -231,19 +231,18 @@ + int futex_wait_multiple(struct futex_vector *vs, unsigned int count, + struct hrtimer_sleeper *to); + +-int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) + { + int ret; + struct futex_vector *vs; + struct hrtimer_sleeper *to = NULL, timeout; + +- to = futex_setup_timer(abs_time, &timeout, 0, 0); +- + vs = futex_read_wait_block(uaddr, count); +- + if (IS_ERR(vs)) + return PTR_ERR(vs); + ++ to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ + ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); + kfree(vs); + diff --git a/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..e3fb191030a9b3c9f0267dedbcfaa0876da62870 GIT binary patch literal 594 zcmV-Y05ZTgYoUc%#m*PMyk zK7xT78HA2pfQ+SJYXN5GFg~q_IedA^>}v+fWMB2gCOmsCtXm(-{O=T+@Y*S83F>Q> z&b~ZTnx>%@lQs2!oKVt#RiiC?$$KA;&FKyL;Usr&J0178fO^Klo7Y^9t7rcGG%XPTaS|M+2haOyYi#*n zwT}s1`iT*gwvZi>@bebgvR_`c4#N!D5rW9&G!boevh8W&Civhfu_XjFvCK4afewnQ zfX{W~4C+sKS!UkYARKFr+5;M9E)~SHo+IA@awGY7P^8|{ZQo=)1;-L1-~a;MhW1vY zi8sC}fc1UnG(x-^Xqv|=^!-Fh8bynFV5p~Z;y~(T+I;ji@vI@M9>NK%v-dFnSjrhs z==AMVC#wBX%pdZd#M-(K^w&gIUNj$rm1r{1v@d zzeTfSPR%9xcCq76I+ho5$Hn?PwAEih^utpET3 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch new file mode 100644 index 0000000..b0d6ba3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch @@ -0,0 +1,88 @@ +diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h +--- a/include/uapi/linux/futex.h ++++ b/include/uapi/linux/futex.h +@@ -134,7 +134,7 @@ + * @bitset: Bitset for the optional bitmasked wakeup + */ + struct futex_wait_block { +- __u32 __user *uaddr; ++ __aligned_u64 uaddr; + __u32 val; + __u32 bitset; + }; +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -198,7 +198,8 @@ + * initialize the fields) and then, for each futex_wait_block element from + * userspace, fill a futex_q element with proper values. + */ +-static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count, ++ unsigned int flags) + { + unsigned int i; + struct futex_vector *futexv; +@@ -219,10 +220,17 @@ + return ERR_PTR(-EFAULT); + } + +- futexv[i].w.flags = FUTEX_32; ++ if (!fwb.bitset) { ++ kfree(futexv); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ futexv[i].w.flags = flags; + futexv[i].w.val = fwb.val; +- futexv[i].w.uaddr = (uintptr_t) (fwb.uaddr); ++ futexv[i].w.uaddr = fwb.uaddr; + futexv[i].q = futex_q_init; ++ futexv[i].q.bitset = fwb.bitset; ++ futexv[i].q.wake = futex_wake_mark; + } + + return futexv; +@@ -231,17 +239,21 @@ + int futex_wait_multiple(struct futex_vector *vs, unsigned int count, + struct hrtimer_sleeper *to); + +-static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count, int op) + { + int ret; + struct futex_vector *vs; + struct hrtimer_sleeper *to = NULL, timeout; ++ unsigned int flags = futex_to_flags(op); ++ ++ if (flags & FLAGS_CLOCKRT) ++ return -ENOSYS; + +- vs = futex_read_wait_block(uaddr, count); ++ vs = futex_read_wait_block(uaddr, count, flags); + if (IS_ERR(vs)) + return PTR_ERR(vs); + +- to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ to = futex_setup_timer(abs_time, &timeout, flags, current->timer_slack_ns); + + ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); + kfree(vs); +@@ -274,7 +286,7 @@ + } + + if (cmd == FUTEX_WAIT_MULTIPLE) +- return futex_opcode_31(tp, uaddr, val); ++ return futex_opcode_31(tp, uaddr, val, op); + + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } +@@ -591,7 +603,7 @@ + } + + if (cmd == FUTEX_WAIT_MULTIPLE) +- return futex_opcode_31(tp, uaddr, val); ++ return futex_opcode_31(tp, uaddr, val, op); + + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } diff --git a/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..d3db2d880064ffae4554c728580169d8baf652aa GIT binary patch literal 594 zcmV-Y0*NE7BEw7kFN3nubr%kHC{2r?hNc+%YR}XYJ|0K^3^B*YJ@et2OIDkcW`KLt6=s;v ze#t2&c3xsq!$IiSUbUA}r1M`J5p|l;e}|jBw+Ewng{{o+swcA-!$-cO0a6f7N(G9* zC}e#YhUl5~mia{iGDSUKU%iMh(KQ^3e@R%uHOjNokYH7jH>ZdGZvz%@zRs;3w!+-_ zc=0{L;&V*L2ff%-)r}K^NJfsAQZ%KCcJRW)v7NImQ~b%|zsOQN+%dSp;Bz`WtZjv!q+{uGK(NZq+XJ?vrB4 zGD%`L1Zq=^H$9B6ujr+;OPN=2n=#fIfRI6~iSJM0CZ>^*^l1do!n8d9P#FI>l*D%R zZ0NRPgwv^CFt4XPoEpWD@h|UY5cdJbm&JnW>~TSuoQ}#ul?+Y_=s_VFumAu6 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch b/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch new file mode 100644 index 0000000..ebd6e0d --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch @@ -0,0 +1,95 @@ +diff --git a/include/linux/module.h b/include/linux/module.h +--- a/include/linux/module.h ++++ b/include/linux/module.h +@@ -975,6 +975,10 @@ unsigned long module_kallsyms_lookup_name(const char *name); + + unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name); + ++void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data); ++ + #else /* CONFIG_MODULES && CONFIG_KALLSYMS */ + + static inline int module_kallsyms_on_each_symbol(const char *modname, +@@ -1018,6 +1022,12 @@ static inline unsigned long find_kallsyms_symbol_value(struct module *mod, + return 0; + } + ++static inline void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data) ++{ ++} ++ + #endif /* CONFIG_MODULES && CONFIG_KALLSYMS */ + + /* Define __free(module_put) macro for struct module *. */ +diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c +--- a/kernel/module/kallsyms.c ++++ b/kernel/module/kallsyms.c +@@ -246,6 +246,18 @@ static const char *kallsyms_symbol_name(struct mod_kallsyms *kallsyms, unsigned + return kallsyms->strtab + kallsyms->symtab[symnum].st_name; + } + ++/* ++ * Whether find_kallsyms_symbol() may resolve an address to symbol @symnum. ++ * Unnamed symbols are ignored: they're uninformative and inserted at a whim. ++ */ ++static bool is_lookup_symbol(struct mod_kallsyms *kallsyms, unsigned int symnum) ++{ ++ const char *name = kallsyms_symbol_name(kallsyms, symnum); ++ ++ return kallsyms->symtab[symnum].st_shndx != SHN_UNDEF && ++ *name != '\0' && !is_mapping_symbol(name); ++} ++ + /* + * Given a module and address, find the corresponding symbol and return its name + * while providing its size and offset if needed. +@@ -286,15 +298,7 @@ static const char *find_kallsyms_symbol(struct module *mod, + const Elf_Sym *sym = &kallsyms->symtab[i]; + unsigned long thisval = kallsyms_symbol_value(sym); + +- if (sym->st_shndx == SHN_UNDEF) +- continue; +- +- /* +- * We ignore unnamed symbols: they're uninformative +- * and inserted at a whim. +- */ +- if (*kallsyms_symbol_name(kallsyms, i) == '\0' || +- is_mapping_symbol(kallsyms_symbol_name(kallsyms, i))) ++ if (!is_lookup_symbol(kallsyms, i)) + continue; + + if (thisval <= addr && thisval > bestval) { +@@ -458,6 +462,28 @@ unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name) + return __find_kallsyms_symbol_value(mod, name); + } + ++/* ++ * Call @fn with the address of each symbol of @mod that find_kallsyms_symbol() ++ * may resolve an address to. Unlike module_kallsyms_on_each_symbol(), this ++ * also works while @mod is still being loaded. ++ */ ++void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data) ++{ ++ struct mod_kallsyms *kallsyms; ++ unsigned int i; ++ ++ guard(rcu)(); ++ kallsyms = rcu_dereference(mod->kallsyms); ++ ++ /* ELF starts real symbols at 1. */ ++ for (i = 1; i < kallsyms->num_symtab; i++) { ++ if (is_lookup_symbol(kallsyms, i)) ++ fn(data, kallsyms_symbol_value(&kallsyms->symtab[i])); ++ } ++} ++ + int module_kallsyms_on_each_symbol(const char *modname, + int (*fn)(void *, const char *, unsigned long), + void *data) diff --git a/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch.sig b/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..bf22caf0665aee91d2dfb81d552265341aaeb8f4 GIT binary patch literal 594 zcmV-Y0Q00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=jW6U|9Dbf|H!0J8>NrNJ8;DQ zh+scL@3M$D&}s6IfO>~85%*rUC*)*b<<(9fk8!3+4cZrazh=*B4%Z#Y79^?jb=?I{ zCbt&>)G^9OmmqF-3Vf&XZ=Ceb6p>qdj*6-rMY78~(Q8i66QKv<8cQrVG!l1t)F+NE zHbkW3xn_&uS#MNpCXpqASZyhw+Z3N!DKz$Wsen~#T7O?U)HkHSQhElV*O6@PvH5zL z5I_pd0iGwDa3*$9ur#IOM%xq1jStD0OTxZPZb;zb!tJzdeI%?^a5O>)`ZK`;T(L6iISs?Jgui?P{Ndm(6ms zFZ02igM4GVUD8P(C^DnZs^w4u@FDa@TfO>Iek|?g0_a58L|z8a>6MS0ZxVASKYUAl zEL29<#kK+QX@If09{=IBdq}~4GCE-;jI>xr)hSKTu^Ufdv^K+W!=+ko3msc*mUpn% z)%eNEwqSAk{kVs}gw~Qqo%$1Z=Z+)MKtt6{sV)2qXr#7*Azr8;t0Gu<6?}~i+$_Q@ gSK_;($c%D2IkhE%HSZ6nD}(H;Zx+oS?%9t*4o9aOm;e9( literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch b/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch new file mode 100644 index 0000000..82b00bb --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch @@ -0,0 +1,113 @@ +diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c +--- a/kernel/trace/ftrace.c ++++ b/kernel/trace/ftrace.c +@@ -4445,11 +4445,6 @@ static int print_rec(struct seq_file *m, unsigned long ip) + return ret == NULL ? -1 : 0; + } + #else +-static inline int test_for_valid_rec(struct dyn_ftrace *rec) +-{ +- return 1; +-} +- + static inline int print_rec(struct seq_file *m, unsigned long ip) + { + seq_printf(m, "%ps", (void *)ip); +@@ -7611,6 +7606,73 @@ static void test_is_sorted(unsigned long *start, unsigned long count) + } + #endif + ++#ifdef FTRACE_MCOUNT_MAX_OFFSET ++struct ftrace_mod_locs { ++ unsigned long *start; ++ unsigned long count; ++ unsigned long *valid; ++}; ++ ++/* Mark the locations that lie at most FTRACE_MCOUNT_MAX_OFFSET after @addr. */ ++static void ftrace_mark_valid_locs(void *data, unsigned long addr) ++{ ++ struct ftrace_mod_locs *locs = data; ++ unsigned long lo = 0, hi = locs->count, mid, ip; ++ ++ /* ++ * ftrace_call_adjust() moves a location forward by at most ++ * FTRACE_MCOUNT_MAX_OFFSET, so start looking that far before @addr. ++ */ ++ while (lo < hi) { ++ mid = lo + (hi - lo) / 2; ++ if (locs->start[mid] + FTRACE_MCOUNT_MAX_OFFSET < addr) ++ lo = mid + 1; ++ else ++ hi = mid; ++ } ++ ++ for (; lo < locs->count; lo++) { ++ if (locs->start[lo] > addr + FTRACE_MCOUNT_MAX_OFFSET) ++ break; ++ ip = ftrace_call_adjust(locs->start[lo]); ++ if (ip >= addr && ip - addr <= FTRACE_MCOUNT_MAX_OFFSET) ++ __set_bit(lo, locs->valid); ++ } ++} ++ ++/* ++ * A weak function overridden within its module keeps its mcount location but ++ * has no symbol. Zero such locations before they become records, as sorttable ++ * does for vmlinux: keep only those with a symbol at most ++ * FTRACE_MCOUNT_MAX_OFFSET before them. ++ */ ++static int ftrace_zero_weak_locs(struct module *mod, unsigned long *start, ++ unsigned long count) ++{ ++ struct ftrace_mod_locs locs = { .start = start, .count = count }; ++ unsigned long i; ++ ++ locs.valid = bitmap_zalloc(count, GFP_KERNEL); ++ if (!locs.valid) ++ return -ENOMEM; ++ ++ module_kallsyms_on_each_addr(mod, ftrace_mark_valid_locs, &locs); ++ ++ for_each_clear_bit(i, locs.valid, count) ++ start[i] = 0; ++ ++ bitmap_free(locs.valid); ++ return 0; ++} ++#else ++static inline int ftrace_zero_weak_locs(struct module *mod, ++ unsigned long *start, ++ unsigned long count) ++{ ++ return 0; ++} ++#endif ++ + static int ftrace_process_locs(struct module *mod, + unsigned long *start, + unsigned long *end) +@@ -7644,6 +7706,9 @@ static int ftrace_process_locs(struct module *mod, + test_is_sorted(start, count); + } + ++ if (mod && ftrace_zero_weak_locs(mod, start, count)) ++ return -ENOMEM; ++ + start_pg = ftrace_allocate_pages(count, &pages); + if (!start_pg) + return -ENOMEM; +@@ -8049,13 +8114,6 @@ void ftrace_module_enable(struct module *mod) + + cond_resched(); + +- /* Weak functions should still be ignored */ +- if (!test_for_valid_rec(rec)) { +- /* Clear all other flags. Should not be enabled anyway */ +- rec->flags = FTRACE_FL_DISABLED; +- continue; +- } +- + cnt = 0; + + /* diff --git a/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig b/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..ba105f0588d227df06e22f5c32af478f9d1d5f16 GIT binary patch literal 594 zcmV-Y0Q00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=g3YF0IQ%VX1Qp2Hew{cRB`f& zoC;hz>&-LFZ`nmceG}5P-YlYf>^_KoMl>`B0F!t^K+6c^Ey5hDbHb7XxDlWbCaF z)fn(;!&K8_LLD&AmK-*cFrupD5|iX|PpFouizV)Q#X?@$mOGBcwY zS;YzUyAgh@JdC%6%PdiMMEHBltkHf|$1AF(lq{O&5e^AT3WJ(`Uy)3|Auyq06Ecb} zs5-CJoMEH7P9f#5x;dYW{BFgocR)v(SdIXGlhzYGhwNpp68s{R)R`&0I%P?GYY7^G zxDw0ar`Od%PY9F?F_ULw;c6Xyh#qV4!ZM<6tMT1bw87K~&m83?OBE;-`1g{H=UI8) zzb_sPB$2_$;=~P`9cSPrF8#|rRnv8~(bZ`;O{9!npr^eL2acsNu!F(g2X+fmk3f7dK2C?65c)F`ex37x5Kf2K{9-p@(l?Y#ymW^~L zaJ5QB$};s5@wF!lGzzhXkMLBJYU6W2jn}e`Ld-I`W-{K)6ggX{r1BU#2lTWRjx91z gTjD%%wCsWd<^u5gO_I_AuygDY-CYq3#hjBNB{RnjCIA2c literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch b/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch new file mode 100644 index 0000000..45590a2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch @@ -0,0 +1,381 @@ +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1468,6 +1468,39 @@ struct memory_failure_stats { + }; + #endif + ++/* ++ * Per-pgdat state machine for the kswapd "opportunistic compaction" hint. ++ * ++ * wakeup_kswapd() collapses the gfp flags of all wakers that arrive between ++ * two kswapd runs into a single tri-state, which kswapd then forwards to the ++ * shrinkers via shrink_control::opportunistic_compaction: ++ * ++ * KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION ++ * Initial state after kswapd consumes the previous value. No waker has ++ * been observed yet for the upcoming run. ++ * ++ * KSWAPD_NO_OPPORTUNISTIC_COMPACTION ++ * At least one waker is an order-0 allocation, or a high-order ++ * allocation that cannot tolerate failure (i.e., not eligible for ++ * opportunistic behaviour). Shrinkers must do their normal best-effort ++ * work; the hint is cleared. ++ * ++ * KSWAPD_OPPORTUNISTIC_COMPACTION ++ * All wakers seen so far are high-order allocations that may fail ++ * (__GFP_NORETRY or __GFP_RETRY_MAYFAIL, without __GFP_NOFAIL). Shrinkers ++ * may skip work that is unlikely to produce a contiguous high-order ++ * block (e.g., evicting working-set pages). ++ * ++ * The state is sticky in the "NO" direction within a single kswapd run: once ++ * any non-eligible waker is observed, subsequent eligible wakers cannot ++ * upgrade it back to KSWAPD_OPPORTUNISTIC_COMPACTION. ++ */ ++enum kswapd_opportunistic_compaction_type { ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION = 0, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION, ++ KSWAPD_OPPORTUNISTIC_COMPACTION, ++}; ++ + /* + * On NUMA machines, each NUMA node would have a pg_data_t to describe + * it's memory layout. On UMA machines there is a single pglist_data which +@@ -1532,6 +1565,13 @@ typedef struct pglist_data { + #endif + struct task_struct *kswapd; /* Protected by kswapd_lock */ + int kswapd_order; ++ /* ++ * Aggregated opportunistic-compaction hint for the next kswapd run. ++ * Updated by wakeup_kswapd() based on the gfp flags / order of each ++ * waker, and consumed (and reset) by kswapd before balance_pgdat(). ++ * See enum kswapd_opportunistic_compaction_type for the state machine. ++ */ ++ atomic_t kswapd_opportunistic_compaction; + enum zone_type kswapd_highest_zoneidx; + + atomic_t kswapd_failures; /* Number of 'reclaimed == 0' runs */ +diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h +--- a/include/linux/shrinker.h ++++ b/include/linux/shrinker.h +@@ -37,6 +37,26 @@ struct shrink_control { + /* current node being shrunk (for NUMA aware shrinkers) */ + int nid; + ++ /* ++ * Opportunistic compaction hint. ++ * ++ * Set by the reclaim path to tell shrinkers that this pass is ++ * driven by an order > 0 allocation that the caller is willing to ++ * have fail (e.g., __GFP_NORETRY / __GFP_RETRY_MAYFAIL without ++ * __GFP_NOFAIL). Such allocations only really benefit from ++ * shrinking when doing so frees up a contiguous, high-order block; ++ * thrashing working sets in the hope of producing one is typically ++ * counter-productive. ++ * ++ * Shrinkers that can produce naturally-aligned high-order folios ++ * (see shrink_control::order) should treat this as a hint to skip ++ * costly work that is unlikely to help compaction (for example, ++ * evicting hot/working-set pages just to free single pages). ++ * ++ * Only meaningful when @order > 0; ignored otherwise. ++ */ ++ bool opportunistic_compaction; ++ + /* + * How many objects scan_objects should scan and try to reclaim. + * This is reset before every call, so it is safe for callees +diff --git a/mm/internal.h b/mm/internal.h +--- a/mm/internal.h ++++ b/mm/internal.h +@@ -1767,7 +1767,7 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid); + + /* shrinker related functions */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +- int priority); ++ int priority, bool opportunistic_compaction); + + int shmem_add_to_page_cache(struct folio *folio, + struct address_space *mapping, +diff --git a/mm/shrinker.c b/mm/shrinker.c +--- a/mm/shrinker.c ++++ b/mm/shrinker.c +@@ -476,7 +476,7 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl, + + #ifdef CONFIG_MEMCG + static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +- struct mem_cgroup *memcg, int priority) ++ struct mem_cgroup *memcg, int priority, bool opportunistic_compaction) + { + struct shrinker_info *info; + unsigned long ret, freed = 0; +@@ -538,6 +538,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + .gfp_mask = gfp_mask, + .nid = nid, + .memcg = memcg, ++ .opportunistic_compaction = opportunistic_compaction, + }; + struct shrinker *shrinker; + int shrinker_id = calc_shrinker_id(index, offset); +@@ -597,7 +598,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + } + #else /* !CONFIG_MEMCG */ + static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +- struct mem_cgroup *memcg, int priority) ++ struct mem_cgroup *memcg, int priority, ++ bool opportunistic_compaction) + { + return 0; + } +@@ -609,6 +611,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + * @nid: node whose slab caches to target + * @memcg: memory cgroup whose slab caches to target + * @priority: the reclaim priority ++ * @opportunistic_compaction: do compaction opportunistically (e.g., do not swap working sets) + * + * Call the shrink functions to age shrinkable caches. + * +@@ -624,7 +627,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + * Returns the number of reclaimed slab objects. + */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +- int priority) ++ int priority, bool opportunistic_compaction) + { + unsigned long ret, freed = 0; + struct shrinker *shrinker; +@@ -637,7 +640,8 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + * oom. + */ + if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg)) +- return shrink_slab_memcg(gfp_mask, nid, memcg, priority); ++ return shrink_slab_memcg(gfp_mask, nid, memcg, priority, ++ opportunistic_compaction); + + /* + * lockless algorithm of global shrink. +@@ -666,6 +670,7 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + .gfp_mask = gfp_mask, + .nid = nid, + .memcg = memcg, ++ .opportunistic_compaction = opportunistic_compaction, + }; + + if (!shrinker_try_get(shrinker)) +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -96,6 +96,14 @@ struct scan_control { + /* Swappiness value for proactive reclaim. Always use sc_swappiness()! */ + int *proactive_swappiness; + ++ /* ++ * Opportunistic compaction hint snapshotted from the pgdat at the ++ * start of this reclaim pass. Forwarded to shrinkers through ++ * shrink_control::opportunistic_compaction so they can skip ++ * non-productive work for failable high-order allocations. ++ */ ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; ++ + /* Can active folios be deactivated as part of reclaim? */ + #define DEACTIVATE_ANON 1 + #define DEACTIVATE_FILE 2 +@@ -198,6 +206,29 @@ struct scan_control { + */ + int vm_swappiness = 60; + ++/* ++ * Is @gfp_flags a high-order allocation that is eligible for the ++ * "opportunistic compaction" treatment in kswapd / shrinkers? ++ * ++ * The caller must be willing to tolerate failure (__GFP_NORETRY or ++ * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such ++ * allocations there is little value in burning working-set pages just to ++ * scrape together a single high-order block: if compaction can't easily ++ * succeed, the caller would rather see the allocation fail. ++ */ ++static bool gfp_opportunistic_compaction(gfp_t gfp_flags) ++{ ++ return (gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)) && ++ !(gfp_flags & __GFP_NOFAIL); ++} ++ ++static bool sc_opportunistic_compaction(struct scan_control *sc) ++{ ++ return sc->order && (sc->kswapd_opportunistic_compaction == ++ KSWAPD_OPPORTUNISTIC_COMPACTION || (!current_is_kswapd() && ++ gfp_opportunistic_compaction(sc->gfp_mask))); ++} ++ + static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) + { + if (sc->proactive && sc->proactive_swappiness) +@@ -411,7 +442,7 @@ static unsigned long drop_slab_node(int nid) + + memcg = mem_cgroup_iter(NULL, NULL, NULL); + do { +- freed += shrink_slab(GFP_KERNEL, nid, memcg, 0); ++ freed += shrink_slab(GFP_KERNEL, nid, memcg, 0, false); + } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); + + return freed; +@@ -5081,6 +5112,7 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) + unsigned long reclaimed = sc->nr_reclaimed; + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct pglist_data *pgdat = lruvec_pgdat(lruvec); ++ bool opportunistic_compaction = sc_opportunistic_compaction(sc); + + /* lru_gen_age_node() called mem_cgroup_calculate_protection() */ + if (mem_cgroup_below_min(NULL, memcg)) +@@ -5096,7 +5128,8 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) + + need_rotate = try_to_shrink_lruvec(lruvec, sc); + +- shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority); ++ shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority, ++ opportunistic_compaction); + + if (!sc->proactive) + vmpressure(sc->gfp_mask, sc->order, memcg, false, +@@ -6163,6 +6196,7 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc) + struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat); + unsigned long reclaimed; + unsigned long scanned; ++ bool opportunistic_compaction = sc_opportunistic_compaction(sc); + + /* + * This loop can become CPU-bound when target memcgs +@@ -6200,7 +6234,7 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc) + shrink_lruvec(lruvec, sc); + + shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, +- sc->priority); ++ sc->priority, opportunistic_compaction); + + /* Record the group's reclaim efficiency */ + if (!sc->proactive) +@@ -7133,8 +7167,14 @@ clear_reclaim_active(pg_data_t *pgdat, int highest_zoneidx) + * found to have free_pages <= high_wmark_pages(zone), any page in that zone + * or lower is eligible for reclaim until at least one usable zone is + * balanced. ++ * ++ * @kswapd_opportunistic_compaction is the aggregated hint produced by ++ * wakeup_kswapd() for this run; it is propagated into scan_control so that ++ * shrinkers can skip costly work that is unlikely to help compaction when ++ * all wakers are failable high-order allocations. + */ +-static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) ++static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx, ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction) + { + int i; + unsigned long nr_soft_reclaimed; +@@ -7148,6 +7188,7 @@ static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) + .gfp_mask = GFP_KERNEL, + .order = order, + .may_unmap = 1, ++ .kswapd_opportunistic_compaction = kswapd_opportunistic_compaction, + }; + + trace_mm_vmscan_balance_pgdat_begin(pgdat->node_id, order, +@@ -7372,8 +7413,10 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, + return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; + } + +-static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, +- unsigned int highest_zoneidx) ++static void ++kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, ++ unsigned int highest_zoneidx, ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction) + { + long remaining = 0; + DEFINE_WAIT(wait); +@@ -7419,6 +7462,11 @@ static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_o + + if (READ_ONCE(pgdat->kswapd_order) < reclaim_order) + WRITE_ONCE(pgdat->kswapd_order, reclaim_order); ++ ++ if (kswapd_opportunistic_compaction == ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION) ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION); + } + + finish_wait(&pgdat->kswapd_wait, &wait); +@@ -7475,6 +7523,7 @@ static int kswapd(void *p) + unsigned int highest_zoneidx = MAX_NR_ZONES - 1; + pg_data_t *pgdat = (pg_data_t *)p; + struct task_struct *tsk = current; ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; + + /* + * Tell the memory management that we're a "memory allocator", +@@ -7492,6 +7541,8 @@ static int kswapd(void *p) + set_freezable(); + + WRITE_ONCE(pgdat->kswapd_order, 0); ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + atomic_set(&pgdat->nr_writeback_throttled, 0); + for ( ; ; ) { +@@ -7500,13 +7551,18 @@ static int kswapd(void *p) + alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); ++ kswapd_opportunistic_compaction = ++ atomic_read(&pgdat->kswapd_opportunistic_compaction); + + kswapd_try_sleep: + kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, +- highest_zoneidx); ++ highest_zoneidx, kswapd_opportunistic_compaction); + + /* Read the new order and highest_zoneidx */ + alloc_order = READ_ONCE(pgdat->kswapd_order); ++ kswapd_opportunistic_compaction = ++ atomic_xchg(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); + WRITE_ONCE(pgdat->kswapd_order, 0); +@@ -7533,7 +7589,8 @@ static int kswapd(void *p) + trace_mm_vmscan_kswapd_wake(pgdat->node_id, highest_zoneidx, + alloc_order); + reclaim_order = balance_pgdat(pgdat, alloc_order, +- highest_zoneidx); ++ highest_zoneidx, ++ kswapd_opportunistic_compaction); + if (reclaim_order < alloc_order) + goto kswapd_try_sleep; + } +@@ -7571,6 +7628,28 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + if (READ_ONCE(pgdat->kswapd_order) < order) + WRITE_ONCE(pgdat->kswapd_order, order); + ++ /* ++ * Fold this waker into the per-pgdat opportunistic-compaction hint ++ * that kswapd will pick up at the start of its next run. ++ * ++ * The state is sticky in the "NO" direction: once any waker in this ++ * batch is order-0 or a non-failable high-order allocation, the hint ++ * stays cleared until kswapd consumes it. Only when every waker so ++ * far is a failable high-order allocation do we set ++ * KSWAPD_OPPORTUNISTIC_COMPACTION, asking shrinkers to skip work ++ * that won't realistically help compaction. ++ */ ++ if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { ++ if (!order || !gfp_opportunistic_compaction(gfp_flags)) ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION); ++ else if (order && gfp_opportunistic_compaction(gfp_flags)) ++ atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, ++ KSWAPD_OPPORTUNISTIC_COMPACTION); ++ } ++ + if (!waitqueue_active(&pgdat->kswapd_wait)) + return; + diff --git a/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch.sig b/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..46b6d946cde932540b4e1e688eecddf15dc2e11e GIT binary patch literal 594 zcmV-Y0t#5gk$JX%`C!hTnokm zhXsgi=C)`X<<`Ks0t@H?LCgjX4>!}*{wV)ZC(|IH8_DiR$=?O@djLBqfCGX~Yf_xd z>n<|hN!af*f1r`rs-4vX0f*NuwjaSPk6H?|pf^YP`&9G g25cx!*DjJ=&^Hxa`(=O|@T-anode_zones + z, 0, +- folio_order(folio), ZONE_MOVABLE); ++ folio_order(folio), false, ZONE_MOVABLE); + return -EAGAIN; + } + +diff --git a/mm/page_alloc.c b/mm/page_alloc.c +--- a/mm/page_alloc.c ++++ b/mm/page_alloc.c +@@ -3423,7 +3423,7 @@ struct page *rmqueue(struct zone *preferred_zone, + if ((alloc_flags & ALLOC_KSWAPD) && + unlikely(test_bit(ZONE_BOOSTED_WATERMARK, &zone->flags))) { + clear_bit(ZONE_BOOSTED_WATERMARK, &zone->flags); +- wakeup_kswapd(zone, 0, 0, zone_idx(zone)); ++ wakeup_kswapd(zone, 0, 0, false, zone_idx(zone)); + } + + VM_BUG_ON_PAGE(page && bad_range(zone, page), page); +@@ -4442,6 +4442,7 @@ static unsigned int check_retry_zonelist(unsigned int seq) + /* Perform direct synchronous page reclaim */ + static unsigned long + __perform_reclaim(gfp_t gfp_mask, unsigned int order, ++ unsigned int alloc_order, + const struct alloc_context *ac) + { + unsigned int noreclaim_flag; +@@ -4454,7 +4455,7 @@ __perform_reclaim(gfp_t gfp_mask, unsigned int order, + fs_reclaim_acquire(gfp_mask); + noreclaim_flag = memalloc_noreclaim_save(); + +- progress = try_to_free_pages(ac->zonelist, order, gfp_mask, ++ progress = try_to_free_pages(ac->zonelist, order, alloc_order, gfp_mask, + ac->nodemask); + + memalloc_noreclaim_restore(noreclaim_flag); +@@ -4481,7 +4482,7 @@ __alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order, + reclaim_order = max(order, pageblock_order); + + psi_memstall_enter(&pflags); +- *did_some_progress = __perform_reclaim(gfp_mask, reclaim_order, ac); ++ *did_some_progress = __perform_reclaim(gfp_mask, reclaim_order, order, ac); + if (unlikely(!(*did_some_progress))) + goto out; + +@@ -4513,19 +4514,24 @@ static void wake_all_kswapds(unsigned int order, gfp_t gfp_mask, + pg_data_t *last_pgdat = NULL; + enum zone_type highest_zoneidx = ac->highest_zoneidx; + unsigned int reclaim_order; ++ bool opportunistic; + + if (defrag_mode) + reclaim_order = max(order, pageblock_order); + else + reclaim_order = order; + ++ /* Classify what the caller asked for, not what reclaim was raised to. */ ++ opportunistic = gfp_opportunistic_compaction(gfp_mask, order); ++ + for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, highest_zoneidx, + ac->nodemask) { + if (!managed_zone(zone)) + continue; + if (last_pgdat == zone->zone_pgdat) + continue; +- wakeup_kswapd(zone, gfp_mask, reclaim_order, highest_zoneidx); ++ wakeup_kswapd(zone, gfp_mask, reclaim_order, opportunistic, ++ highest_zoneidx); + last_pgdat = zone->zone_pgdat; + } + } +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -160,6 +160,12 @@ struct scan_control { + /* Allocation order */ + s8 order; + ++ /* ++ * The order the allocation asked for. @order above may have been ++ * raised to pageblock_order by defrag_mode. ++ */ ++ s8 alloc_order; ++ + /* Scan (total_size >> priority) pages at once */ + s8 priority; + +@@ -206,27 +212,19 @@ struct scan_control { + */ + int vm_swappiness = 60; + +-/* +- * Is @gfp_flags a high-order allocation that is eligible for the +- * "opportunistic compaction" treatment in kswapd / shrinkers? +- * +- * The caller must be willing to tolerate failure (__GFP_NORETRY or +- * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such +- * allocations there is little value in burning working-set pages just to +- * scrape together a single high-order block: if compaction can't easily +- * succeed, the caller would rather see the allocation fail. +- */ +-static bool gfp_opportunistic_compaction(gfp_t gfp_flags) +-{ +- return (gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)) && +- !(gfp_flags & __GFP_NOFAIL); +-} +- + static bool sc_opportunistic_compaction(struct scan_control *sc) + { +- return sc->order && (sc->kswapd_opportunistic_compaction == +- KSWAPD_OPPORTUNISTIC_COMPACTION || (!current_is_kswapd() && +- gfp_opportunistic_compaction(sc->gfp_mask))); ++ if (!sc->order) ++ return false; ++ ++ if (sc->kswapd_opportunistic_compaction == ++ KSWAPD_OPPORTUNISTIC_COMPACTION) ++ return true; ++ ++ if (current_is_kswapd()) ++ return false; ++ ++ return gfp_opportunistic_compaction(sc->gfp_mask, sc->alloc_order); + } + + static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) +@@ -6781,7 +6779,8 @@ static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist, + } + + unsigned long try_to_free_pages(struct zonelist *zonelist, int order, +- gfp_t gfp_mask, nodemask_t *nodemask) ++ int alloc_order, gfp_t gfp_mask, ++ nodemask_t *nodemask) + { + unsigned long nr_reclaimed; + struct scan_control sc = { +@@ -6789,6 +6788,7 @@ unsigned long try_to_free_pages(struct zonelist *zonelist, int order, + .gfp_mask = current_gfp_context(gfp_mask), + .reclaim_idx = gfp_zone(gfp_mask), + .order = order, ++ .alloc_order = alloc_order, + .nodemask = nodemask, + .priority = DEF_PRIORITY, + .may_writepage = 1, +@@ -7608,7 +7608,7 @@ static int kswapd(void *p) + * needed. + */ + void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, +- enum zone_type highest_zoneidx) ++ bool opportunistic, enum zone_type highest_zoneidx) + { + pg_data_t *pgdat; + enum zone_type curr_idx; +@@ -7641,10 +7641,10 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + */ + if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != + KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { +- if (!order || !gfp_opportunistic_compaction(gfp_flags)) ++ if (!opportunistic) + atomic_set(&pgdat->kswapd_opportunistic_compaction, + KSWAPD_NO_OPPORTUNISTIC_COMPACTION); +- else if (order && gfp_opportunistic_compaction(gfp_flags)) ++ else + atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, + KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, + KSWAPD_OPPORTUNISTIC_COMPACTION); +@@ -7942,6 +7942,7 @@ int node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned int order) + .nr_to_reclaim = max(nr_pages, SWAP_CLUSTER_MAX), + .gfp_mask = current_gfp_context(gfp_mask), + .order = order, ++ .alloc_order = order, + .priority = NODE_RECLAIM_PRIORITY, + .may_writepage = !!(node_reclaim_mode & RECLAIM_WRITE), + .may_unmap = !!(node_reclaim_mode & RECLAIM_UNMAP), diff --git a/pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch.sig b/pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..b1703dd39525546423d47f3f38d8142c429db263 GIT binary patch literal 594 zcmV-Y0|k9OMEz9I|BWOY@XN6XQb)8kvKN>g?aFQ*SQ-iyl2jKq`?iW*MGY(C=u;4Lo89CC zVl9_f4>iNO=gBjfR9-thFO9V2Tl&Meih4B%cwdQ={u>D;petm5bVBI1+grJP%+T>8 zj6>!cN~^V!(GPVJ({0it{L22U75YO{&wuX55l5U2>Nn1x@pcmBV zj<0cVb&_WfXF3JXjdh=s4;o|J7YAe8QOk+@Z%I0XuIc*<%>kK$uRCdC1qe9U4j5+? z;M9_pefGKVJMTZ!kB@zbJyX80rBJYO^9vTbk`9$VGe0hKx*I_^YCvY4)u27)m~@B~ ztkHcv8x%R&+_vpvG@O(V!)ciq|6pmRvdo+H7fdrxI6l=8x(D0uzpZr#<-jmGw|BzU zL996uwThbP&78z*8482>!yhderHy(N?B4rnu*q52^lAoAYD$_*z(zQ;v7mTQ6XqQ? zaAMFlEe585L9Zn}@>)t2Haws}fHatq5Xm7Ux4AR$7sBvi#}F@5fiOQ0B*FF_QG0RR91 literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch b/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch new file mode 100644 index 0000000..2a82df3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch @@ -0,0 +1,232 @@ +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1501,6 +1501,32 @@ enum kswapd_opportunistic_compaction_type { + KSWAPD_OPPORTUNISTIC_COMPACTION, + }; + ++/* ++ * pgdat->kswapd_request holds the order and the hint above in one word. A ++ * waker folds both in with a single cmpxchg and kswapd takes both with a ++ * single exchange, so a run cannot serve one waker's order under another ++ * waker's hint. ++ */ ++#define KSWAPD_REQUEST_HINT_MASK 3 ++#define KSWAPD_REQUEST_ORDER_SHIFT 2 ++ ++static inline int kswapd_request(int order, ++ enum kswapd_opportunistic_compaction_type hint) ++{ ++ return (order << KSWAPD_REQUEST_ORDER_SHIFT) | hint; ++} ++ ++static inline int kswapd_request_order(int request) ++{ ++ return request >> KSWAPD_REQUEST_ORDER_SHIFT; ++} ++ ++static inline enum kswapd_opportunistic_compaction_type ++kswapd_request_hint(int request) ++{ ++ return request & KSWAPD_REQUEST_HINT_MASK; ++} ++ + /* + * On NUMA machines, each NUMA node would have a pg_data_t to describe + * it's memory layout. On UMA machines there is a single pglist_data which +@@ -1564,14 +1590,15 @@ typedef struct pglist_data { + struct mutex kswapd_lock; + #endif + struct task_struct *kswapd; /* Protected by kswapd_lock */ +- int kswapd_order; + /* +- * Aggregated opportunistic-compaction hint for the next kswapd run. +- * Updated by wakeup_kswapd() based on the gfp flags / order of each +- * waker, and consumed (and reset) by kswapd before balance_pgdat(). +- * See enum kswapd_opportunistic_compaction_type for the state machine. ++ * The next kswapd run's request: the highest order asked for since ++ * kswapd last consumed one, and the aggregated opportunistic- ++ * compaction hint, in one word. Updated by wakeup_kswapd() from the ++ * gfp flags and order of each waker, and consumed by kswapd before ++ * balance_pgdat(). See kswapd_request() and ++ * enum kswapd_opportunistic_compaction_type. + */ +- atomic_t kswapd_opportunistic_compaction; ++ atomic_t kswapd_request; + enum zone_type kswapd_highest_zoneidx; + + atomic_t kswapd_failures; /* Number of 'reclaimed == 0' runs */ +diff --git a/mm/mm_init.c b/mm/mm_init.c +--- a/mm/mm_init.c ++++ b/mm/mm_init.c +@@ -1558,7 +1558,8 @@ int __ref free_area_init_core_hotplug(struct pglist_data *pgdat) + * when it starts in the near future. + */ + pgdat->nr_zones = 0; +- pgdat->kswapd_order = 0; ++ atomic_set(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); + pgdat->kswapd_highest_zoneidx = 0; + pgdat->node_start_pfn = 0; + pgdat->node_present_pages = 0; +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -7413,6 +7413,49 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, + return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; + } + ++/* ++ * How a caller votes on the opportunistic-compaction hint when it folds a ++ * request into pgdat->kswapd_request. ++ */ ++enum kswapd_vote { ++ KSWAPD_VOTE_NONE, ++ KSWAPD_VOTE_NO_OPPORTUNISTIC, ++ KSWAPD_VOTE_OPPORTUNISTIC, ++}; ++ ++/* ++ * Raise the requested order to @order and fold in @vote, in one step. ++ * ++ * The hint is sticky in the "NO" direction: once any waker votes against ++ * opportunistic treatment, the hint stays cleared until kswapd consumes the ++ * request. KSWAPD_VOTE_NONE raises the order and leaves the hint alone. ++ * ++ * Order and hint live in the same word, so a waker whose order kswapd ++ * observes always contributed its vote to the hint kswapd observes with it. ++ */ ++static void kswapd_request_fold(pg_data_t *pgdat, int order, ++ enum kswapd_vote vote) ++{ ++ int old = atomic_read(&pgdat->kswapd_request); ++ int new; ++ ++ do { ++ enum kswapd_opportunistic_compaction_type hint = ++ kswapd_request_hint(old); ++ int req_order = max(kswapd_request_order(old), order); ++ ++ if (vote == KSWAPD_VOTE_NO_OPPORTUNISTIC) ++ hint = KSWAPD_NO_OPPORTUNISTIC_COMPACTION; ++ else if (vote == KSWAPD_VOTE_OPPORTUNISTIC && ++ hint == KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION) ++ hint = KSWAPD_OPPORTUNISTIC_COMPACTION; ++ ++ new = kswapd_request(req_order, hint); ++ if (new == old) ++ return; ++ } while (!atomic_try_cmpxchg(&pgdat->kswapd_request, &old, new)); ++} ++ + static void + kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, + unsigned int highest_zoneidx, +@@ -7460,13 +7503,11 @@ kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, + kswapd_highest_zoneidx(pgdat, + highest_zoneidx)); + +- if (READ_ONCE(pgdat->kswapd_order) < reclaim_order) +- WRITE_ONCE(pgdat->kswapd_order, reclaim_order); +- +- if (kswapd_opportunistic_compaction == +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION) +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION); ++ kswapd_request_fold(pgdat, reclaim_order, ++ kswapd_opportunistic_compaction == ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION ? ++ KSWAPD_VOTE_NO_OPPORTUNISTIC : ++ KSWAPD_VOTE_NONE); + } + + finish_wait(&pgdat->kswapd_wait, &wait); +@@ -7524,6 +7565,7 @@ static int kswapd(void *p) + pg_data_t *pgdat = (pg_data_t *)p; + struct task_struct *tsk = current; + enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; ++ int request; + + /* + * Tell the memory management that we're a "memory allocator", +@@ -7540,32 +7582,30 @@ static int kswapd(void *p) + tsk->flags |= PF_MEMALLOC | PF_KSWAPD; + set_freezable(); + +- WRITE_ONCE(pgdat->kswapd_order, 0); +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); ++ atomic_set(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + atomic_set(&pgdat->nr_writeback_throttled, 0); + for ( ; ; ) { + bool was_frozen; + +- alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order); ++ request = atomic_read(&pgdat->kswapd_request); ++ alloc_order = reclaim_order = kswapd_request_order(request); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); +- kswapd_opportunistic_compaction = +- atomic_read(&pgdat->kswapd_opportunistic_compaction); ++ kswapd_opportunistic_compaction = kswapd_request_hint(request); + + kswapd_try_sleep: + kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, + highest_zoneidx, kswapd_opportunistic_compaction); + +- /* Read the new order and highest_zoneidx */ +- alloc_order = READ_ONCE(pgdat->kswapd_order); +- kswapd_opportunistic_compaction = +- atomic_xchg(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); ++ /* Take the new request and highest_zoneidx */ ++ request = atomic_xchg(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); ++ alloc_order = kswapd_request_order(request); ++ kswapd_opportunistic_compaction = kswapd_request_hint(request); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); +- WRITE_ONCE(pgdat->kswapd_order, 0); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + + if (kthread_freezable_should_stop(&was_frozen)) +@@ -7625,30 +7665,15 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + if (curr_idx == MAX_NR_ZONES || curr_idx < highest_zoneidx) + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, highest_zoneidx); + +- if (READ_ONCE(pgdat->kswapd_order) < order) +- WRITE_ONCE(pgdat->kswapd_order, order); +- + /* +- * Fold this waker into the per-pgdat opportunistic-compaction hint +- * that kswapd will pick up at the start of its next run. +- * +- * The state is sticky in the "NO" direction: once any waker in this +- * batch is order-0 or a non-failable high-order allocation, the hint +- * stays cleared until kswapd consumes it. Only when every waker so +- * far is a failable high-order allocation do we set +- * KSWAPD_OPPORTUNISTIC_COMPACTION, asking shrinkers to skip work +- * that won't realistically help compaction. ++ * Fold this waker's order and its vote on the opportunistic-compaction ++ * hint into the request kswapd will pick up at the start of its next ++ * run. Both change together, so the run that serves this order also ++ * carries this waker's vote. + */ +- if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { +- if (!opportunistic) +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION); +- else +- atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, +- KSWAPD_OPPORTUNISTIC_COMPACTION); +- } ++ kswapd_request_fold(pgdat, order, ++ opportunistic ? KSWAPD_VOTE_OPPORTUNISTIC : ++ KSWAPD_VOTE_NO_OPPORTUNISTIC); + + if (!waitqueue_active(&pgdat->kswapd_wait)) + return; diff --git a/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch.sig b/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..6f15263c8cceca3f214e2c2d08314d10a58f79b2 GIT binary patch literal 594 zcmV-Y0hvS^vaJ7j#asCBAz%%pk?2*9faHMSeY2p*3Z5&(N6bbDb5Cz7o0TOE3O4* z{=A-vcNUW@+;1tBLrQUhil32Xqfq#+5X5Tt8G#Uu{WO&g`6A4DiYhdHJC=(Co6MJh zE3Wd)zl>ypW(_+~w$f%h&vVlaW*)hh<$FiFas5ulFQWGaCCd#ibSv*1}5h4hn;V)Vs=*S1Vf3=@`@`gY2N z-UdqeD5WAj7gQ5?0BfDc76plmw>DlV|T$MEpizG6m=t|p0Z^+4O&if727Eod>q g#y?2vaSAWrgiWC_?UmV~DfTr@wn21!Dq`_6kFSIo8UO$Q literal 0 HcmV?d00001 diff --git a/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch b/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch new file mode 100644 index 0000000..bfa7d1e --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch @@ -0,0 +1,28 @@ +diff --git c/mm/internal.h i/mm/internal.h +--- c/mm/internal.h ++++ i/mm/internal.h +@@ -1778,12 +1778,24 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid); + * @order is the order the caller asked for, not the order reclaim was + * promoted to. defrag_mode raises the reclaim order to pageblock_order, + * which says nothing about what the caller can tolerate. ++ * ++ * A transparent huge page allocation sets neither flag, yet a failed ++ * collapse or fault falls back to small pages. Recognise it by its mask. ++ * In this tree the complete GFP_TRANSHUGE_LIGHT set is carried by the ++ * huge page fault path, khugepaged and the large folio migration ++ * targets. The huge zero folio clears __GFP_MOVABLE and so keeps normal ++ * shrinker behaviour. The three other sites that combine __GFP_COMP ++ * with __GFP_NOMEMALLOC do not carry the movable, highmem and ++ * filesystem bits, so they do not match. + */ + static inline bool gfp_opportunistic_compaction(gfp_t gfp_flags, int order) + { + if (!order || (gfp_flags & __GFP_NOFAIL)) + return false; + ++ if ((gfp_flags & GFP_TRANSHUGE_LIGHT) == GFP_TRANSHUGE_LIGHT) ++ return true; ++ + return !!(gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch.sig b/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..e60df1b952fe0d2075e47fabd9e135a68c97cce1 GIT binary patch literal 594 zcmV-Y0j00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=k5Cs{yp+!1n?BoGP0#eq5~iz z%d3&DbLpzI*JPT{eylTrDHvIPYa)T0Smr-O2Kj>Wt zE)K79IqR4(S`a1Bp*Tg-whGr&z`~C?2?l0%kn5@Y(thrOW3PF^izb<0o4_Pjdk$~h zOrCO|qcLWG5Ftpo*f*r3 zXR_SPpq#Q8iVG{ZzW`l2t0HGy_u;As&~K~c8D8j99zHLy%EgFcMIU%wt-MaMK-t2oiHU&qM>*6`im<_;uheT0CWY~jUIz?H4;BN*F8 zv+K3QN^bdA)%Wzt6QU8<>9TKn^Vo%r>T^sv;NEeadts_HWEl&s;^TDyW)>3fET{zD zMn^EtT`$9C^EFU5mf>Eu!IeHVcI&t)?+zu^bWs_TpPj(DJO-@TC)>{$B-mo0RxdRb g(C=SuyUxjTqL0E{fgGTg`iCCg1)rvgfp_mask & __GFP_FS); + +- num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; ++ /* ++ * Skip accounting backup-able pages when this is an opportunistic ++ * high-order pass: TTM backup work shrinks at native page granularity ++ * and is unlikely to produce the contiguous block the caller wants, ++ * so don't advertise it as reclaimable for this hint. ++ */ ++ if (!sc->opportunistic_compaction) ++ num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; + read_lock(&shrinker->lock); + + if (can_backup) +@@ -249,7 +256,14 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; +- if (nr_scanned >= nr_to_scan || !can_backup) ++ /* ++ * Stop after the purge pass for opportunistic high-order reclaim: ++ * the subsequent backup/writeback pass works at native page order ++ * and is unlikely to free a contiguous high-order block, so doing ++ * it here would just churn working sets for no compaction benefit. ++ */ ++ if (nr_scanned >= nr_to_scan || !can_backup || ++ sc->opportunistic_compaction) + goto out; + + /* If we didn't wake before, try to do it now if needed. */ diff --git a/pkgbuilds/linux-omarchy-muqss/8209-xe-shrinker-use-opportunistic-hint.patch.sig b/pkgbuilds/linux-omarchy-muqss/8209-xe-shrinker-use-opportunistic-hint.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..7928b4ee0dcbc148b07fd44f0fb52cd369749037 GIT binary patch literal 594 zcmV-Y0j00000 z1ON_gVQzIYEHW-ND={uHGAuAGG5`t*5PW2IjXmQu=WbdL|90C93g)f}u_3C;r%Z8+ zDhY4(*ndyrP!QY*S#g9smhKC`ktrz{45qh8GqUsS#^yhu{!^VXX`rXtCd>bU4zJjo z6uR4Y(!5DX^)BM3jSp$uL|CEQsXSR$_1K0jQu*y`tt~IQ;I}9XXn*`ynDEKwb2Vq& z4~ddxIDi?-W!;BgEr*QEY!8Yt#iV1M+np-IDT^@ih#JAUb&v@}N`HGqZH?%ePT(v-t(Sg*J^>>Y(<-F&fgfp_mask & __GFP_FS) && !sc->opportunistic_compaction; ++} ++ + static unsigned long + xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); + unsigned long num_pages = 0; +- bool can_backup = !!(sc->gfp_mask & __GFP_FS); + + /* +- * Skip accounting backup-able pages when this is an opportunistic +- * high-order pass: TTM backup work shrinks at native page granularity +- * and is unlikely to produce the contiguous block the caller wants, +- * so don't advertise it as reclaimable for this hint. ++ * Only advertise backup-able pages when the backup pass can run. TTM ++ * backup work shrinks at native page granularity and is unlikely to ++ * produce the contiguous block an opportunistic high-order caller ++ * wants, and it needs __GFP_FS. + */ +- if (!sc->opportunistic_compaction) +- num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT; + read_lock(&shrinker->lock); +- +- if (can_backup) +- num_pages = min_t(unsigned long, num_pages, shrinker->shrinkable_pages); +- else +- num_pages = 0; ++ if (xe_shrinker_can_backup(sc)) ++ num_pages = min_t(unsigned long, ++ ttm_backup_bytes_avail() >> PAGE_SHIFT, ++ shrinker->shrinkable_pages); + + num_pages += shrinker->purgeable_pages; + read_unlock(&shrinker->lock); +@@ -240,7 +241,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + }; + bool runtime_pm; + bool purgeable; +- bool can_backup = !!(sc->gfp_mask & __GFP_FS); ++ bool can_backup = xe_shrinker_can_backup(sc); + + nr_to_scan = sc->nr_to_scan; + +@@ -257,13 +258,15 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + sc->nr_scanned = nr_scanned; + /* +- * Stop after the purge pass for opportunistic high-order reclaim: +- * the subsequent backup/writeback pass works at native page order +- * and is unlikely to free a contiguous high-order block, so doing +- * it here would just churn working sets for no compaction benefit. ++ * Stop after the purge pass when the backup pass may not run. The ++ * backup/writeback pass works at native page order and is unlikely to ++ * free a contiguous high-order block for an opportunistic caller, so ++ * doing it there would churn working sets for no compaction benefit. ++ * ++ * The per-walk runtime PM reference in __xe_shrinker_walk() is not ++ * affected. Only the outer backup-driven reference is. + */ +- if (nr_scanned >= nr_to_scan || !can_backup || +- sc->opportunistic_compaction) ++ if (nr_scanned >= nr_to_scan || !can_backup) + goto out; + + /* If we didn't wake before, try to do it now if needed. */ diff --git a/pkgbuilds/linux-omarchy-muqss/8210-xe-shrinker-single-backup-decision.patch.sig b/pkgbuilds/linux-omarchy-muqss/8210-xe-shrinker-single-backup-decision.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..a823434bcc05c16ba0826edbab0594a800549ec4 GIT binary patch literal 594 zcmV-Y0S3A`y=u`6lx*)@aXGn1W-;25~yy4Z76*Q z?|~zy{=-|2)S)fD@fdael+t`SwYJ_kO9oz=!U)xa-8?1CJ8mx-3kf|H6qMGKR z=Y|Dqva2q|)beU|Q6Aga-Dwd`P{F}CH!BOR_RJJ`r8Zdl^t>dF=cm*P<+$a`8$zalW>DqykH?!H>)F7;(cj>hdpveMka1 zoHe@Su*V{XiWpZkGq51;xLFb^2`y_HB<-XRc*1R_{@cN08+8LK`lH71H{%)BPK34X zZ0?3d(jAttm.base) { ++ struct xe_vm *vm = gpuvm_to_vm(vm_bo->vm); ++ u64 last = READ_ONCE(vm->last_exec_ns); ++ ++ if (!xe_vm_in_fault_mode(vm) && last && now - last < window) ++ return true; ++ } ++ ++ return false; ++} ++ + /** + * xe_bo_shrink() - Try to shrink an xe bo. + * @ctx: The struct ttm_operation_ctx used for shrinking. +@@ -1295,6 +1316,10 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo, + goto out_unref; + } + ++ if (xe_bo_bound_to_active_vm(xe_bo) && ++ try_wait_for_completion(&xe->pm_block)) ++ goto out_unref; ++ + /* System CCS needs gpu copy when moving PL_TT -> PL_SYSTEM */ + needs_rpm = (!IS_DGFX(xe) && bo->resource->mem_type != XE_PL_SYSTEM && + xe_bo_needs_ccs_pages(xe_bo)); +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -22,5 +22,6 @@ + #define XE_DEFAULT_WEDGED_MODE XE_WEDGED_MODE_UPON_CRITICAL_ERROR + #define XE_DEFAULT_WEDGED_MODE_STR "upon-critical-error" + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 ++#define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 + + #endif +diff --git a/drivers/gpu/drm/xe/xe_exec.c b/drivers/gpu/drm/xe/xe_exec.c +--- a/drivers/gpu/drm/xe/xe_exec.c ++++ b/drivers/gpu/drm/xe/xe_exec.c +@@ -344,6 +344,7 @@ int xe_exec_ioctl(struct drm_device *dev, void *data, struct drm_file *file) + if (!xe_vm_in_lr_mode(vm)) + xe_exec_queue_last_fence_set(q, vm, &job->drm.s_fence->finished); + xe_sched_job_push(job); ++ WRITE_ONCE(vm->last_exec_ns, ktime_get_ns()); + xe_vm_reactivate_rebind(vm); + + if (!err && !xe_vm_in_lr_mode(vm)) { +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -30,6 +30,7 @@ struct xe_modparam xe_modparam = { + #endif + .wedged_mode = XE_DEFAULT_WEDGED_MODE, + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, ++ .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, + /* the rest are 0 by default */ + }; + +@@ -37,6 +38,11 @@ module_param_named(svm_notifier_size, xe_modparam.svm_notifier_size, uint, 0600) + MODULE_PARM_DESC(svm_notifier_size, "Set the svm notifier size in MiB, must be power of 2 " + "[default=" __stringify(XE_DEFAULT_SVM_NOTIFIER_SIZE) "]"); + ++module_param_named(shrink_active_ms, xe_modparam.shrink_active_ms, uint, 0600); ++MODULE_PARM_DESC(shrink_active_ms, "Do not back up a buffer object bound to a VM that submitted work " ++ "within this many ms, 0=disabled " ++ "[default=" __stringify(XE_DEFAULT_SHRINK_ACTIVE_MS) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -25,6 +25,7 @@ struct xe_modparam { + #endif + unsigned int wedged_mode; + u32 svm_notifier_size; ++ unsigned int shrink_active_ms; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_vm_types.h b/drivers/gpu/drm/xe/xe_vm_types.h +--- a/drivers/gpu/drm/xe/xe_vm_types.h ++++ b/drivers/gpu/drm/xe/xe_vm_types.h +@@ -245,6 +245,8 @@ struct xe_vm { + /** @lru_bulk_move: Bulk LRU move list for this VM's BOs */ + struct ttm_lru_bulk_move lru_bulk_move; + ++ u64 last_exec_ns; ++ + u64 size; + + struct xe_pt *pt_root[XE_MAX_TILES_PER_DEVICE]; diff --git a/pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch.sig b/pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..7467cb6666107a7d52bb16a6936cb2562e31d35d GIT binary patch literal 594 zcmV-Y0K=Bo%8awy6gl7F;jiY|iWNk@&J1bVkJ_{|L)~(a2Kp3;CQQ z2oX-+R20LcFp|8^_v!FU?1P!s!bE%Dyzo`AfPrh)%MTWio{U`wA<|rsdNut46H<~L z(qOUJ51!Ff-G5PP5cvTfje(WEC&9@qHEjlHpCc^+Ib2z$16u8{&17pR!MEhQU7sc+ z5%1XyGk%9d6L1caX)q%X3+~O%MGyVhif=@m!TJ%1KNy425xRrh z#(ZBfS*-u9n#tu)kN{0YxW=WrNjK4CFCGX^kQtE^kGV;Dv4|Vy0sCy* zebh%rT*|gBU`v+tMOxsl9?KubnJ!WJcAI~kP;lW?>EryypnX8%GM6r#6QAx`1A8ne z-GZ(HdJ6lKL00xR;EuOEYlg`ZTM$1_?1LV^U?ne)%xH$z!o2O!l`|*}bFFOFirI4+ z1Kg3gRUI>%nk-Q7Qy`|DgnOB$z+S*PSqOFb&&RvN!44? ++#include + + #include + #include +@@ -11,6 +12,7 @@ + #include + + #include "xe_bo.h" ++#include "xe_module.h" + #include "xe_pm.h" + #include "xe_shrinker.h" + +@@ -170,9 +172,12 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + +-static inline bool xe_shrinker_can_backup(const struct shrink_control *sc) ++static inline bool xe_shrinker_can_backup(struct xe_shrinker *shrinker, ++ const struct shrink_control *sc) + { +- return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction; ++ return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction && ++ (current_is_kswapd() || READ_ONCE(xe_modparam.shrink_direct_backup) || ++ !try_wait_for_completion(&shrinker->xe->pm_block)); + } + + static unsigned long +@@ -188,7 +193,7 @@ xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + * wants, and it needs __GFP_FS. + */ + read_lock(&shrinker->lock); +- if (xe_shrinker_can_backup(sc)) ++ if (xe_shrinker_can_backup(shrinker, sc)) + num_pages = min_t(unsigned long, + ttm_backup_bytes_avail() >> PAGE_SHIFT, + shrinker->shrinkable_pages); +@@ -241,7 +246,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + }; + bool runtime_pm; + bool purgeable; +- bool can_backup = xe_shrinker_can_backup(sc); ++ bool can_backup = xe_shrinker_can_backup(shrinker, sc); + + nr_to_scan = sc->nr_to_scan; + diff --git a/pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch.sig b/pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..ae4ebedebb86bf34dd552b383d5ef39149c06ac3 GIT binary patch literal 594 zcmV-Y0!yj(PBQha9 z7RKnKSp+Rg*U(3cZ!Pr}f8f^yaM09G;*z}=5{;`~TdUk3D7}~v2uitrtvtfD0#>N+ zTn}JWPz}1o+_a*;Rp>ImHV$e&_+l%=X<~DZBDFou6m&Chr43=Asx~Piy*HU`z5K4i zpXTRy+f%&RJ!+YZg41G!kk?<$Ok$5aE6S^=1Dexm!f7+z|G7X?h0@HwIafh6Fhj*z8l_#g2B?J2Db;NyiY#GuW z;drm, "Notifier evict user failed (%d)\n", err); + ++ if (action == PM_HIBERNATION_PREPARE) ++ xe_shrinker_backup_for_hibernation(xe->mem.shrinker); ++ + err = xe_bo_notifier_prepare_all_pinned(xe); + if (err) + drm_dbg(&xe->drm, "Notifier prepare pin failed (%d)\n", err); +diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c +--- a/drivers/gpu/drm/xe/xe_shrinker.c ++++ b/drivers/gpu/drm/xe/xe_shrinker.c +@@ -172,6 +172,34 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + ++void xe_shrinker_backup_for_hibernation(struct xe_shrinker *shrinker) ++{ ++ struct ttm_operation_ctx ctx = { ++ .interruptible = false, ++ .no_wait_gpu = false, ++ }; ++ struct xe_bo_shrink_flags flags = { ++ .purge = false, ++ .writeback = true, ++ }; ++ unsigned long to_scan, scanned = 0, freed = 0; ++ ktime_t start = ktime_get(); ++ int ret; ++ ++ if (!READ_ONCE(xe_modparam.hibernate_writeback) || IS_DGFX(shrinker->xe) || ++ !ttm_backup_bytes_avail()) ++ return; ++ ++ read_lock(&shrinker->lock); ++ to_scan = shrinker->shrinkable_pages; ++ read_unlock(&shrinker->lock); ++ ++ ret = __xe_shrinker_walk(shrinker, &ctx, flags, to_scan, &scanned, &freed); ++ drm_info(&shrinker->xe->drm, ++ "Hibernation writeback: %lu of %lu pages scanned, %lu freed in %lld ms (%d)\n", ++ scanned, to_scan, freed, ktime_ms_delta(ktime_get(), start), ret); ++} ++ + static inline bool xe_shrinker_can_backup(struct xe_shrinker *shrinker, + const struct shrink_control *sc) + { +diff --git a/drivers/gpu/drm/xe/xe_shrinker.h b/drivers/gpu/drm/xe/xe_shrinker.h +--- a/drivers/gpu/drm/xe/xe_shrinker.h ++++ b/drivers/gpu/drm/xe/xe_shrinker.h +@@ -13,4 +13,6 @@ void xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long p + + int xe_shrinker_create(struct xe_device *xe); + ++void xe_shrinker_backup_for_hibernation(struct xe_shrinker *shrinker); ++ + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch.sig b/pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch.sig new file mode 100644 index 0000000000000000000000000000000000000000..eb7552a45c26631d9667046aa9ccf8ce34c4c8f2 GIT binary patch literal 594 zcmV-Y0c~{&Np1D7bq%nAwDu3^PXL0x{ zG(9<~3rrRfwk#D7UlEI`u~HDlNFilGR~0dV;-quJ5E*1eFF5ScFFOSP6nSbnfI|uY zQoOk*;W}mHSn%n localversion.10-pkgrel + echo "${pkgbase#linux}" > localversion.20-pkgname + + if [[ -n ${_rcpatch:-} ]]; then + echo "Applying patch $_rcpatch..." + patch -Np1 --fuzz=0 < "../$_rcpatch" + fi + + local src + for src in "${source[@]}"; do + src="${src%%::*}" + src="${src##*/}" + src="${src%.zst}" + [[ $src = *.patch ]] || continue + echo "Applying patch $src..." + patch -Np1 --fuzz=0 < "../$src" + done + + echo "Setting config..." + cp ../config.$CARCH .config + make olddefconfig + diff -u ../config.$CARCH .config || : + + make -s kernelrelease > version + echo "Prepared $pkgbase version $(