diff --git a/.github/workflows/sync-aur.yml b/.github/workflows/sync-aur.yml deleted file mode 100644 index b5d3fd3..0000000 --- a/.github/workflows/sync-aur.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Sync AUR Packages - -on: - schedule: - # Every 6 hours - - cron: '0 */6 * * *' - workflow_dispatch: - inputs: - packages: - description: 'Specific packages to sync (space-separated, leave empty for all)' - required: false - default: '' - -jobs: - sync: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Sync AUR packages - run: | - docker run --rm \ - -e PACKAGES="$PACKAGES" \ - -e HOST_UID="$(id -u)" \ - -e HOST_GID="$(id -g)" \ - -v "$PWD/bin:/workspace/bin:ro" \ - -v "$PWD/helpers:/workspace/helpers:ro" \ - -v "$PWD/pkgbuilds:/workspace/pkgbuilds" \ - -w /workspace \ - archlinux:base-devel bash -lc ' - set -euo pipefail - - pacman -Syu --noconfirm git jq - - groupadd -g "$HOST_GID" runner - useradd -m -u "$HOST_UID" -g "$HOST_GID" runner - chown -R runner:runner /workspace/pkgbuilds - - if [[ -n "${PACKAGES:-}" ]]; then - read -r -a package_args <<< "$PACKAGES" - runuser -u runner -- ./bin/sync-aur "${package_args[@]}" - else - runuser -u runner -- ./bin/sync-aur - fi - ' - env: - PACKAGES: ${{ github.event.inputs.packages }} - - - name: Check for changes - id: changes - run: | - if [ -z "$(git status --porcelain)" ]; then - echo "has_changes=false" >> "$GITHUB_OUTPUT" - else - echo "has_changes=true" >> "$GITHUB_OUTPUT" - fi - - - name: Create Pull Request - if: steps.changes.outputs.has_changes == 'true' - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: 'chore: sync AUR packages' - title: 'chore: sync AUR packages' - body: | - Automated AUR package sync. - - Package sync behavior is controlled by `.omarchy/package.json`. - branch: auto/sync-aur - delete-branch: true - labels: automated - reviewers: ryanrhughes - - - name: Notify Basecamp on failure - if: failure() && env.BASECAMP_CHATBOT_URL != '' - env: - BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }} - run: | - curl -s -o /dev/null \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg content \ - "πŸ”΄ AUR sync failed
View run" \ - '{content: $content}')" \ - "$BASECAMP_CHATBOT_URL" diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index c19c9ae..2e69136 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -27,9 +27,11 @@ jobs: # Runs in an Arch container for vercmp: whether a release is an upgrade has # to be decided by the same comparator pacman will use on users' machines. - name: Update packages from upstream release feeds + id: sync run: | docker run --rm \ -e PACKAGES="$PACKAGES" \ + -e UPSTREAM_GITHUB_TOKEN="$UPSTREAM_GITHUB_TOKEN" \ -e HOST_UID="$(id -u)" \ -e HOST_GID="$(id -g)" \ -v "$PWD/bin:/workspace/bin:ro" \ @@ -39,7 +41,7 @@ jobs: archlinux:base-devel bash -lc ' set -euo pipefail - pacman -Syu --noconfirm git jq + pacman -Syu --noconfirm git jq python libarchive groupadd -g "$HOST_GID" runner useradd -m -u "$HOST_UID" -g "$HOST_GID" runner @@ -54,8 +56,12 @@ jobs: ' env: PACKAGES: ${{ github.event.inputs.packages }} + UPSTREAM_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Failed feeds leave their recipes untouched; completed updates still + # reach review. The failed sync step keeps the workflow red and notifies. - name: Check for changes + if: ${{ !cancelled() && steps.sync.outcome != 'skipped' }} id: changes run: | if [ -z "$(git status --porcelain)" ]; then @@ -65,7 +71,7 @@ jobs: fi - name: Create Pull Request - if: steps.changes.outputs.has_changes == 'true' + if: ${{ !cancelled() && steps.changes.outputs.has_changes == 'true' }} uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -75,8 +81,9 @@ jobs: Automated update of packages that track an upstream vendor release feed rather than the AUR. - Each package reports its newest release through - `.omarchy/upstream.sh`. + Release watches and providers are declared in `.omarchy/package.json`; + exceptional feeds use `.omarchy/upstream.sh`. Failed package updates + are left untouched; check the workflow result for outstanding failures. branch: auto/sync-upstream delete-branch: true labels: automated diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3d0a68e..6e48346 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,9 +39,12 @@ jobs: -w /workspace \ archlinux:base-devel bash -lc ' set -euo pipefail - pacman -Syu --noconfirm git jq + pacman -Syu --noconfirm git jq python libarchive + python tests/upstream-watch.py ./bin/sync-upstream self-test ./bin/sync-rebuilds --self-test ./bin/omarchy-pkgs self-test ./bin/omarchy-release self-test + ./tests/partial-release.sh + ./tests/published-build-plan.sh ' diff --git a/.gitignore b/.gitignore index 7873a6d..7b1f63b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ pkgbuilds/yay/yay/ .srcdest/ .repo-host .worktrees/ + +# Python helpers and offline tests +__pycache__/ diff --git a/README.md b/README.md index 64a5d58..5ff0edc 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ The filesystem no longer encodes release policy. Instead: (`OMARCHY_RC_PINS=1`, which `omarchy-release rc` sets) may build it for rc β€” master's shipped pins can never overwrite an in-flight RC. The dev pair (`omarchy-dev`, `omarchy-settings-dev`) is pinned to `edge` -- AUR sync behavior is controlled by `source`, `sync`, `aur`, patches, and hooks in `.omarchy/` +- Omarchy owns every checked-in recipe; upstream watches update release metadata without replacing packaging or architecture support - packages can opt out of unscoped builds with `skip_build`; explicit `--package` builds remain available -- packages that follow a vendor release feed instead of the AUR carry an `.omarchy/upstream.sh` hook +- packages follow direct upstream watches/providers in `.omarchy/package.json`, or a custom `.omarchy/upstream.sh` hook ## Prerequisites ### aarch64 Builds (Optional) @@ -125,6 +125,14 @@ bin/repo advance --from edge --to rc The release command is smart and **incremental** - it only builds packages that have changed or are missing. You generally don't need to specify a package manually unless you are debugging a specific failure. +When a package fails, a completed build run still signs and publishes the packages +that succeeded. Failed packages and their blocked dependents remain queued with +failure backoff; retries compare against the updated repository and skip the +published versions. Only artifacts recorded by fully completed package builds +are eligible for a partial release. An interrupted build, a failed publication +step, or an incomplete pair using deferred runtime dependencies still stops the +release. Reports distinguish partial publication from complete success. + ```bash # Build changed/new packages, sign, promote, clean, update, and sync bin/repo release @@ -321,14 +329,16 @@ push uploaded, so `push` stops when it finds packages already staged there β€” usually leftovers from a failed run. Remove them on the host, or pass `--include-staged` to publish them too. -### Sync AUR PKGBUILDs +### Import an initial AUR recipe ```bash -bin/sync-aur # Sync all AUR packages with sync enabled -bin/sync-aur yay v4l2-relayd # Sync specific packages +bin/add-package package-name --source aur ``` -AUR sync is metadata-driven. It preserves `.omarchy/`, replaces the package root with AUR contents, applies `.omarchy/patches/*.patch`, runs `.omarchy/post-sync.sh` when present, applies pkgrel metadata, removes AUR-only `.SRCINFO` and `.gitignore` files, and records `upstream_commit`. +AUR is an optional source for an initial recipe. Imported packages become +Omarchy-owned immediately; subsequent updates use direct upstream releases. +There is no scheduled AUR sync. Edit the checked-in PKGBUILD to maintain +architecture support and packaging behavior. ### Sync Upstream Releases @@ -542,8 +552,8 @@ bin/omarchy-release # Release front door (start / pick / rc / s bin/repo list # List package metadata bin/repo deploy # Build locally, then publish from the host bin/repo push # Upload local builds to the host and publish -bin/add-package # Add an AUR/local package with metadata -bin/package-worktree # Create upstream/patched/current scratch workspace +bin/add-package # Add an Omarchy-owned package with metadata +bin/package-worktree # Inspect historical AUR provenance in a scratch workspace bin/repo remove # Remove package bin/sync-upstream # Update packages that track a vendor release feed bin/sync-rebuilds # Bump pkgrel for packages whose dependencies moved @@ -562,7 +572,7 @@ bin/repo list # Table view of source package metadata bin/repo list --json # Agent/script-friendly JSON bin/repo list --repo --mirror stable # List packages in a published repo database -bin/package-worktree v4l2-relayd # Create upstream/patched/current scratch workspace +bin/package-worktree yay # Compare with the original imported AUR recipe ``` ## Cutting an Omarchy Release @@ -676,9 +686,7 @@ omarchy-pkgs/ β”‚ β”œβ”€β”€ PKGBUILD β”‚ └── .omarchy/ β”‚ β”œβ”€β”€ package.json # Source/sync/release metadata -β”‚ β”œβ”€β”€ patches/ # Omarchy patches reapplied after AUR sync -β”‚ β”œβ”€β”€ post-sync.sh # Optional dynamic post-sync customization hook -β”‚ └── upstream.sh # Optional vendor release feed hook (non-AUR packages) +β”‚ └── upstream.sh # Optional custom vendor release feed hook β”œβ”€β”€ build/ β”œβ”€β”€ build-output/ # Unsigned packages (temporary) β”‚ β”œβ”€β”€ edge/ # (rc/ and stable/ alongside, each x86_64 + aarch64) @@ -698,45 +706,28 @@ Each source package has Omarchy metadata at `pkgbuilds//.omarchy/packag Minimal examples: -```json -{ "source": "aur" } -``` - -```json -{ "source": "aur", "sync": false } -``` - -```json -{ "source": "aur", "release_ring": "fast" } -``` - ```json { "source": "local" } -``` - -```json +{ "source": "local", "release_ring": "fast" } { "source": "local", "skip_build": true } -``` - -```json -{ "source": "aur", "pkgrel": { "suffix": 1 } } +{ "source": "local", "upstream": { "watch": { "github": "abenz1267/walker", "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" } } } ``` Fields: -- `source`: `aur` or `local`. A `local` package can still follow an upstream release, either declaratively via `upstream` or with an `.omarchy/upstream.sh` hook. -- `upstream`: optional for `local` packages following GitHub releases, git tags, npm dist-tags, or a Debian `Packages` index. GitHub architecture assets may be a string or an ordered array, and can be combined with disjoint versioned `sources` β€” see [Sync Upstream Releases](#sync-upstream-releases). Mutually exclusive with `.omarchy/upstream.sh`. +- `source`: `local` for maintained packages. The legacy `aur` value is used only during an initial import. A local recipe can follow an upstream watch, provider, or `.omarchy/upstream.sh` hook. +- `upstream`: optional direct release watch (see [Upstream watches](docs/upstream-sources.md)), or an existing GitHub, git-tag, npm, or Debian provider. GitHub architecture assets may be a string or an ordered array, and can be combined with disjoint versioned `sources` β€” see [Sync Upstream Releases](#sync-upstream-releases). Mutually exclusive with `.omarchy/upstream.sh`. - `min_release_age`: optional quarantine for upstream releases (`"24h"`, `"2d"`, or bare seconds). The newest release older than the window ships; anything younger waits, and a release whose age cannot be proven fails the sync. Bypass deliberately with `BYPASS_MIN_RELEASE_AGE=1 bin/sync-upstream `. -- `sync`: optional for AUR packages; defaults to `true`. Set `false` for AUR-origin packages that Omarchy maintains manually. -- `aur`: optional AUR package name when it differs from the local package directory, usually for split packages. +- `sync`: `false` records an existing manual maintenance hold. Held packages have no upstream watch/provider/hook and are excluded from automatic updates. +- `origin`: optional historical import provenance, with `aur` (package name) and `commit`. It does not control updates. - `release_ring`: optional. `fast` means the package is built directly for stable as well as edge, with the artifacts replicated into rc for parity. Packages without a ring build in edge and reach stable through the pipeline (`bin/repo advance`). - `channels`: optional array bounding where the package may be built (`edge`, `rc`, `stable`). Without the key a package is a member of every channel and follows the default build rules above; `bin/repo advance` refuses to carry a package anywhere it isn't a member. - `pinned`: optional boolean. A pinned package's version is set per release by `omarchy-release` on the `rc` branch, so it is never built for stable (promotion only) and is built for rc only from that branch's worktree (`OMARCHY_RC_PINS=1`). Used by `omarchy` and `omarchy-settings`. - `skip_build`: optional boolean; defaults to `false`. Set `true` to exclude a package from scheduled version checks and unscoped builds. The package can still be built explicitly with `bin/repo release --package `. -- `pkgrel`: optional Omarchy pkgrel suffix for a version-pinned rebuild bump. This emits `.` instead of replacing AUR's pkgrel. `offset` can be used only when preserving monotonic upgrades from old absolute pkgrel bumps. The metadata is removed automatically when AUR sync changes `pkgver`; the current package version is read from the checked-in PKGBUILD, so the version is not duplicated in JSON. +- `pkgrel`: legacy import customization metadata. Maintained recipes keep their complete package release directly in PKGBUILD; rebuilds increment it there. - `rebuild_on`: optional array of package names this package links against closely enough that it must be rebuilt when they change, independent of its own source. Read by `bin/sync-rebuilds`. - `rebuilt_against`: written by `bin/sync-rebuilds`. Maps each published architecture to the versions of its `rebuild_on` packages that the current pkgrel was bumped for. -- `upstream_commit`: set by `bin/sync-aur` for AUR packages. Used by `bin/package-worktree` to recreate the exact raw AUR package that Omarchy last synced. +- `upstream_commit`: legacy AUR metadata, superseded by `origin.commit`. `bin/package-worktree` can use historical provenance to inspect the original recipe. ### Build Matrix @@ -748,78 +739,26 @@ Fields: ## Adding Packages -### From AUR +### Start from an existing recipe ```bash -bin/add-package package-name +bin/add-package package-name --source aur --fast +# Review the imported files, own any architecture/packaging changes directly, +# and declare an upstream watch/provider or hook in .omarchy/. +bin/sync-upstream package-name bin/repo release --package package-name ``` -### From AUR, fast release ring +The import records historical provenance in `origin`. It does not opt a package +into future AUR imports. Upstream watches update only release scalars and source +checksums; downstream build behavior stays in the recipe. Ordinary source-code +patches still belong beside PKGBUILD and are applied by `prepare()` as needed. + +### Custom package ```bash -bin/add-package package-name --fast -bin/repo release --package package-name -bin/repo release --mirror stable --package package-name -``` - -### AUR-origin, manually maintained by Omarchy - -```bash -bin/add-package package-name --no-sync -``` - -### Local Customizations for AUR Packages - -For static changes, create `pkgbuilds/package-name/.omarchy/patches/*.patch` to maintain modifications across AUR syncs. - -The recommended workflow is to use a scratch workspace: - -```bash -bin/package-worktree package-name --dir /tmp/package-name-worktree -``` - -This creates: - -```text -upstream/ # raw AUR package at upstream_commit -patched/ # AUR + existing Omarchy .omarchy customizations -current/ # current checked-in package directory -``` - -Patch-authoring flow: - -```bash -# 1. Make the intended change in pkgbuilds/package-name/ - -# 2. Recreate the scratch workspace -bin/package-worktree package-name --dir /tmp/package-name-worktree - -# 3. Inspect drift from patched -> current -# For multi-file changes, inspect this and split into focused patches. -diff -ruN /tmp/package-name-worktree/patched /tmp/package-name-worktree/current - -# For a single PKGBUILD change, write a patch like this: -mkdir -p pkgbuilds/package-name/.omarchy/patches -( - cd /tmp/package-name-worktree/patched - diff -u --label a/PKGBUILD --label b/PKGBUILD \ - PKGBUILD /tmp/package-name-worktree/current/PKGBUILD || true -) > pkgbuilds/package-name/.omarchy/patches/my-fix.patch - -# 4. Verify the package is reproducible from AUR + .omarchy -bin/sync-aur package-name -bin/package-worktree package-name --dir /tmp/package-name-check -diff -ruN /tmp/package-name-check/patched /tmp/package-name-check/current -``` - -For dynamic changes that depend on the current upstream version, add `pkgbuilds/package-name/.omarchy/post-sync.sh`. The hook runs after the AUR package is copied into a temporary worktree and before the Omarchy pkgrel suffix is applied. After patches/hooks/metadata pkgrel overrides, `bin/sync-aur` removes AUR-only `.SRCINFO` and `.gitignore` files before writing the package back. - -### Custom Package - -```bash -bin/add-package my-package --local --scaffold -# Fill in PKGBUILD and package files +bin/add-package my-package --scaffold +# Fill in PKGBUILD, package files, and upstream metadata bin/repo release --package my-package ``` @@ -902,9 +841,8 @@ The repository includes GitHub workflows and systemd services for automated rele #### GitHub Workflows -1. **sync-aur.yml** (Every 6 hours): Syncs AUR packages according to `.omarchy/package.json` and opens a PR when changes are found. -2. **sync-upstream.yml** (Every 6 hours): Runs `.omarchy/upstream.sh` for packages that track a vendor release feed and opens a PR when a newer version is out. -3. **sync-rebuilds.yml** (Every 6 hours): Bumps pkgrel for packages whose `rebuild_on` dependencies have moved in the official repositories and opens a PR. +1. **sync-upstream.yml** (Every 6 hours): Watches direct upstream feeds and updates owned recipes. Successful package updates reach a PR even if another feed fails; failed recipes stay untouched and the workflow remains red. +2. **sync-rebuilds.yml** (Every 6 hours): Bumps pkgrel for packages whose `rebuild_on` dependencies have moved in the official repositories and opens a PR. #### Systemd Services diff --git a/bin/add-package b/bin/add-package index ee18f80..b347b8d 100755 --- a/bin/add-package +++ b/bin/add-package @@ -6,7 +6,7 @@ source "$BUILD_ROOT/helpers/message-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" PACKAGE="" -SOURCE="aur" +SOURCE="local" SYNC="true" RELEASE_RING="" AUR_PACKAGE="" @@ -17,14 +17,14 @@ usage() { cat < [OPTIONS] -Create pkgbuilds/ with Omarchy metadata. AUR packages are synced -immediately after metadata is written. +Create an Omarchy-owned package. --source aur imports a starting recipe once; +future releases must use an upstream watch, provider, or hook. Options: - --source Package source (default: aur) + --source Initial recipe source (default: local) --local Shortcut for --source local --aur AUR package name when different from local directory - --no-sync For AUR packages, mark sync disabled after initial setup + --no-sync Keep the imported recipe manually maintained --fast Put package in the fast release ring --release-ring Release ring (currently: fast) --scaffold For local packages, create a starter PKGBUILD @@ -32,9 +32,9 @@ Options: -h, --help Show this help message Examples: - $0 yay - $0 spotify --fast - $0 signal-desktop --no-sync + $0 yay --source aur + $0 spotify --source aur --fast + $0 signal-desktop --source aur --no-sync $0 omarchy-zsh --local --scaffold EOF } @@ -97,6 +97,10 @@ if [[ -z "$PACKAGE" ]]; then usage exit 1 fi +if [[ ! $PACKAGE =~ ^[a-zA-Z0-9@_+][a-zA-Z0-9@._+-]*$ ]]; then + print_error "Invalid package name: $PACKAGE" + exit 1 +fi case "$SOURCE" in aur|local) ;; @@ -112,6 +116,11 @@ PACKAGE_DIR="$PKGBUILDS_DIR/$PACKAGE" OMARCHY_DIR="$PACKAGE_DIR/.omarchy" METADATA_FILE="$OMARCHY_DIR/package.json" +if [[ "$SOURCE" == aur && -f "$PACKAGE_DIR/PKGBUILD" ]]; then + print_error "Refusing to replace the maintained recipe for $PACKAGE" + exit 1 +fi + if [[ -f "$METADATA_FILE" && "$FORCE" != true ]]; then print_error "Package metadata already exists: $METADATA_FILE" print_info "Use --force to overwrite it" @@ -146,18 +155,8 @@ jq -n "${jq_args[@]}" "$jq_filter" > "$METADATA_FILE" print_success "Wrote $METADATA_FILE" if [[ "$SOURCE" == "aur" ]]; then - if [[ "$SYNC" == "false" ]]; then - # Temporarily sync once, then restore sync=false so future automated syncs skip it. - tmpfile=$(mktemp) - jq 'del(.sync)' "$METADATA_FILE" > "$tmpfile" - mv "$tmpfile" "$METADATA_FILE" - "$BUILD_ROOT/bin/sync-aur" "$PACKAGE" - jq '. + {sync: false}' "$METADATA_FILE" > "$tmpfile" - mv "$tmpfile" "$METADATA_FILE" - print_info "AUR sync disabled for future runs" - else - "$BUILD_ROOT/bin/sync-aur" "$PACKAGE" - fi + "$BUILD_ROOT/bin/import-aur" "$PACKAGE" + else if [[ "$SCAFFOLD" == true && ! -f "$PACKAGE_DIR/PKGBUILD" ]]; then cat > "$PACKAGE_DIR/PKGBUILD" < "$OMARCHY_BUILD_RESULT_DIR/artifacts" + for package in "${SUCCESSFUL_PACKAGES[@]}"; do + cat "$PLAN_DIR/artifacts/$package" >> "$OMARCHY_BUILD_RESULT_DIR/artifacts" + done + printf '%s\n' "${FAILED_PACKAGES[@]}" | sed '/^$/d' > "$OMARCHY_BUILD_RESULT_DIR/failed" + printf '%s\n' "${BLOCKED_PACKAGES[@]}" | sed '/^$/d' > "$OMARCHY_BUILD_RESULT_DIR/blocked" + touch "$OMARCHY_BUILD_RESULT_DIR/complete" +fi + if (( ${#FAILED_PACKAGES[@]} + ${#BLOCKED_PACKAGES[@]} )); then if (( ${#FAILED_PACKAGES[@]} )); then echo "Failed packages:" @@ -330,7 +347,9 @@ if (( ${#FAILED_PACKAGES[@]} + ${#BLOCKED_PACKAGES[@]} )); then printf ' - %s\n' "${BLOCKED_PACKAGES[@]}" fi print_warning "Some packages failed (see details above)" - exit 1 + # Reserved for a completed run with unsuccessful packages. Other failures + # must not let release publish arbitrary files left in the workspace. + exit 2 fi print_success "Build completed successfully!" diff --git a/bin/check-versions b/bin/check-versions index 925570f..6f0558f 100755 --- a/bin/check-versions +++ b/bin/check-versions @@ -172,8 +172,8 @@ check_package() { # it because that exact filename is already published with different bytes. # If the artifact for this version already exists in the channel, there is # nothing to build regardless of which direction the versions differ. - if compgen -G "$REPO_ROOT/$mirror/$ARCH/${pkg}-${pkgbuild_version}-*.pkg.tar."[!s]* >/dev/null 2>&1; then - print_warning "$pkg $pkgbuild_version is already published β€” this checkout is behind the channel; not queueing" + if package_version_is_published "$REPO_ROOT/$mirror/$ARCH" "$pkg" "$pkgbuild_version" "$ARCH"; then + print_warning "$pkg $pkgbuild_version is already published; not queueing" return 1 fi diff --git a/bin/import-aur b/bin/import-aur new file mode 100755 index 0000000..cbc5ec5 --- /dev/null +++ b/bin/import-aur @@ -0,0 +1,62 @@ +#!/bin/bash +# Internal initial-recipe import used by add-package. Maintained recipes are +# never replaced; subsequent releases go through sync-upstream. +set -euo pipefail + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/paths.sh" + +if [[ ${1:-} == --help || ${1:-} == -h ]]; then + echo "Usage: bin/add-package --source aur [--aur ]" + exit 0 +fi +if [[ $# != 1 || ! $1 =~ ^[a-zA-Z0-9@_+][a-zA-Z0-9@._+-]*$ ]]; then + print_error "Use bin/add-package --source aur for an initial import" + exit 1 +fi +package=$1 +package_dir="$PKGBUILDS_DIR/$package" +metadata="$package_dir/.omarchy/package.json" +if [[ -f "$package_dir/PKGBUILD" ]]; then + print_error "Refusing to replace the maintained recipe for $package" + exit 1 +fi +if [[ ! -f "$metadata" ]] || [[ $(jq -r .source "$metadata") != aur ]]; then + print_error "Initial import requires metadata created by bin/add-package --source aur" + exit 1 +fi +aur_package=$(jq -r --arg name "$package" '.aur // $name' "$metadata") +if [[ ! $aur_package =~ ^[a-zA-Z0-9@_+][a-zA-Z0-9@._+-]*$ ]]; then + print_error "Invalid AUR package name" + exit 1 +fi +# Refuse unrelated package files: an import only starts from .omarchy metadata. +shopt -s dotglob nullglob +for item in "$package_dir"/*; do + if [[ ${item##*/} != .omarchy ]]; then + print_error "Initial import needs an empty package directory: $package_dir" + exit 1 + fi +done + +work=$(mktemp -d "$PKGBUILDS_DIR/.import-aur.XXXXXX") +trap 'rm -rf "$work"' EXIT +print_info "Importing $package from AUR package $aur_package..." +git clone --quiet "https://aur.archlinux.org/${aur_package}.git" "$work/recipe" +[[ -f "$work/recipe/PKGBUILD" ]] || { print_error "AUR package has no PKGBUILD"; exit 1; } +commit=$(git -C "$work/recipe" rev-parse HEAD) +rm -rf "$work/recipe/.git" "$work/recipe/.omarchy" +rm -f "$work/recipe/.SRCINFO" "$work/recipe/.gitignore" +cp -a "$package_dir/.omarchy" "$work/recipe/.omarchy" +jq --arg name "$aur_package" --arg commit "$commit" ' + .source = "local" | .origin = {aur: $name, commit: $commit} + | del(.aur, .upstream_commit) +' "$metadata" > "$work/recipe/.omarchy/package.json" +# Stage on the same filesystem, restoring the metadata directory on failure. +mv "$package_dir" "$work/original" +if ! mv "$work/recipe" "$package_dir"; then + mv "$work/original" "$package_dir" + exit 1 +fi +print_success "Imported $package; review the recipe and configure its direct upstream watch" diff --git a/bin/package-worktree b/bin/package-worktree index 8deca92..37ca4ed 100755 --- a/bin/package-worktree +++ b/bin/package-worktree @@ -17,13 +17,13 @@ Usage: $0 [OPTIONS] Create a scratch workspace for inspecting an AUR-backed package. The workspace contains: - upstream/ Raw AUR package at .omarchy/package.json upstream_commit, or HEAD + upstream/ Raw AUR package at the recorded origin.commit, or HEAD patched/ Raw AUR package with Omarchy .omarchy patches/hooks/pkgrel applied current/ Current checked-in package directory Options: --dir Workspace directory (default: mktemp under /tmp) - --commit Use a specific AUR commit instead of upstream_commit + --commit Use a specific AUR commit instead of origin.commit -h, --help Show this help message Examples: @@ -75,13 +75,13 @@ if [[ ! -f "$METADATA" ]]; then exit 1 fi -if [[ "$(jq -r '.source // ""' "$METADATA")" != "aur" ]]; then +if [[ "$(jq -r '.origin.aur // .aur // (if .source == "aur" then "legacy" else "" end)' "$METADATA")" == "" ]]; then print_error "package-worktree only supports AUR-backed packages" exit 1 fi -AUR_PACKAGE=$(jq -r --arg package "$PACKAGE" '.aur // $package' "$METADATA") -UPSTREAM_COMMIT=${COMMIT_OVERRIDE:-$(jq -r '.upstream_commit // ""' "$METADATA")} +AUR_PACKAGE=$(jq -r --arg package "$PACKAGE" '.origin.aur // .aur // $package' "$METADATA") +UPSTREAM_COMMIT=${COMMIT_OVERRIDE:-$(jq -r '.origin.commit // .upstream_commit // ""' "$METADATA")} if [[ -z "$DEST_DIR" ]]; then DEST_DIR=$(mktemp -d "${TMPDIR:-/tmp}/omarchy-${PACKAGE}.XXXXXX") @@ -224,7 +224,7 @@ print_info "AUR package: $AUR_PACKAGE" if [[ -n "$UPSTREAM_COMMIT" ]]; then print_info "Upstream commit: $UPSTREAM_COMMIT" else - print_warning "No upstream_commit recorded; using AUR HEAD" + print_warning "No origin.commit recorded; using AUR HEAD" fi rm -rf "$UPSTREAM_DIR" "$PATCHED_DIR" "$CURRENT_DIR" diff --git a/bin/release b/bin/release index 193bfb5..4a1d704 100755 --- a/bin/release +++ b/bin/release @@ -138,11 +138,43 @@ if [[ "$DRY_RUN" == true ]]; then else print_info "Step 1/6: Building packages..." fi -"$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || { +BUILD_RESULT_DIR=$(mktemp -d) +trap 'rm -rf "$BUILD_RESULT_DIR"' EXIT +build_status=0 +OMARCHY_BUILD_RESULT_DIR="$BUILD_RESULT_DIR" "$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || build_status=$? +partial=false +if [[ "$build_status" == 2 && -f "$BUILD_RESULT_DIR/complete" && "$DRY_RUN" != true ]]; then + if [[ "${OMARCHY_DEFER_RUNTIME_DEPS:-false}" == true ]]; then + print_error "The deferred release pair must succeed together; nothing will be published" + notify_error "Release failed: Incomplete release pair" "$RELEASE_CONTEXT" + exit 1 + fi + partial=true + while IFS= read -r file; do + [[ -f "$BUILD_OUTPUT_DIR/$file" ]] || { + print_error "Completed build artifact is missing: $file" + notify_error "Release failed: Missing completed artifact" "$RELEASE_CONTEXT" + exit 1 + } + done < "$BUILD_RESULT_DIR/artifacts" + # Exclude partial split outputs or leftovers from failed builds. Moving + # them out of the flat publication directory keeps them available for + # diagnosis without handing them to sign/promote. + unpublished="" + for path in "$BUILD_OUTPUT_DIR"/*.pkg.tar.*; do + [[ -f "$path" ]] || continue + file=${path##*/} + if ! grep -Fxq -- "${file%.sig}" "$BUILD_RESULT_DIR/artifacts"; then + [[ -n "$unpublished" ]] || unpublished=$(mktemp -d "$BUILD_OUTPUT_DIR/.unpublished.XXXXXX") + mv -- "$path" "$unpublished/" + fi + done + print_warning "Some packages failed; publishing the completed packages before retrying the failures" +elif [[ "$build_status" != 0 ]]; then print_error "Build failed" notify_error "Release failed: Build step failed" "$RELEASE_CONTEXT" exit 1 -} +fi if [[ "$DRY_RUN" == true ]]; then echo "" @@ -155,6 +187,12 @@ BUILT_COUNT=$(grep -c '' <<<"$BUILT_FILES") [[ -z "$BUILT_FILES" ]] && BUILT_COUNT=0 print_info "Built $BUILT_COUNT package(s) this run" +if [[ "$partial" == true && "$BUILT_COUNT" == 0 ]]; then + print_error "No completed packages to publish" + notify_error "Release failed: No completed packages" "$RELEASE_CONTEXT" + exit 1 +fi + # Step 2: Sign echo "" print_info "Step 2/6: Signing packages..." @@ -213,7 +251,16 @@ if ((BUILT_COUNT > 0)); then summary+="

$BUILT_COUNT package(s) published:" summary+="$(format_package_list_html "$BUILT_FILES")" summary+="

Live at https://pkgs.omarchy.org/$MIRROR/$ARCH/" - notify_success "Release published: $MIRROR" "$summary" + if [[ "$partial" == true ]]; then + failed=$(tr '\n' ' ' < "$BUILD_RESULT_DIR/failed" | basecamp_html_escape) + blocked=$(tr '\n' ' ' < "$BUILD_RESULT_DIR/blocked" | basecamp_html_escape) + [[ -z "$failed" ]] || summary+="

Failed: $failed" + [[ -z "$blocked" ]] || summary+="
Blocked by failed dependencies: $blocked" + summary+="
Published packages will be skipped on retry; failed packages remain queued." + notify_info "Partial release published: $MIRROR" "$summary" + else + notify_success "Release published: $MIRROR" "$summary" + fi else # Rare by construction: a release only runs when the version check queued # work, so publishing nothing means the check and the builder disagreed @@ -228,4 +275,10 @@ else fi echo "" +if [[ "$partial" == true ]]; then + print_warning "Completed packages published; unsuccessful packages remain for retry" + # Preserve the scheduled queue and failure backoff. The next version + # check/build compares against the updated repository and skips successes. + exit 1 +fi print_success "Release workflow completed successfully!" diff --git a/bin/sync-aur b/bin/sync-aur deleted file mode 100755 index 59af6ca..0000000 --- a/bin/sync-aur +++ /dev/null @@ -1,434 +0,0 @@ -#!/bin/bash -set -euo pipefail - -BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") -source "$BUILD_ROOT/helpers/message-helpers.sh" -source "$BUILD_ROOT/helpers/paths.sh" -source "$BUILD_ROOT/helpers/package-metadata.sh" - -TEMP_DIR=$(mktemp -d) -trap 'rm -rf "$TEMP_DIR"' EXIT - -SPECIFIC_PACKAGES=() - -usage() { - cat </. - -Package selection is driven by pkgbuilds//.omarchy/package.json: - { "source": "aur" } # synced from matching AUR package name - { "source": "aur", "aur": "yaru" } # synced from different AUR package name - { "source": "aur", "sync": false } # AUR-origin, but not auto-synced - -Arguments: - PACKAGE One or more package names to sync (optional) - -Examples: - $0 # Sync all AUR packages with sync enabled - $0 yay cursor-bin # Sync specific packages -EOF -} - -while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) - usage - exit 0 - ;; - --tier) - print_error "--tier is no longer supported; package metadata controls sync behavior" - exit 1 - ;; - --*) - print_error "Unknown option: $1" - exit 1 - ;; - *) - SPECIFIC_PACKAGES+=("$1") - shift - ;; - esac -done - -print_header "AUR Package Sync" -mkdir -p "$PKGBUILDS_DIR" - -SYNCED=0 -SKIPPED=0 -FAILED=0 -SYNCED_PACKAGES=() -SPECIFIC_MODE=false - -get_pkgbuild_field() { - local package_dir="$1" - local field="$2" - local value="" - - if [[ -f "$package_dir/PKGBUILD" ]]; then - value=$(grep -m1 "^${field}=" "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'") || true - if [[ -n "$value" ]]; then - echo "$value" - return - fi - fi - - if [[ -f "$package_dir/.SRCINFO" ]]; then - awk -F' = ' -v field="$field" '$1 ~ "^[[:space:]]*" field "$" { print $2; exit }' "$package_dir/.SRCINFO" - fi -} - -set_pkgrel() { - local package_dir="$1" - local pkgrel="$2" - local pkgbuild="$package_dir/PKGBUILD" - - if [[ -f "$pkgbuild" ]]; then - sed -i "s/^pkgrel=.*/pkgrel=$pkgrel/" "$pkgbuild" - fi -} - -display_package_name() { - local package_dir="$1" - local name - name=$(basename "$package_dir") - echo "${name%.work}" -} - -remove_aur_only_files() { - local package_dir="$1" - - rm -f "$package_dir/.SRCINFO" "$package_dir/.gitignore" -} - -copy_aur_contents() { - local aur_dir="$1" - local target_dir="$2" - - mkdir -p "$target_dir" - - shopt -s dotglob nullglob - local item base - for item in "$aur_dir"/*; do - base=$(basename "$item") - [[ "$base" == ".git" ]] && continue - cp -a "$item" "$target_dir/" - done - shopt -u dotglob nullglob -} - -commit_synced_worktree() { - local work_dir="$1" - local target_dir="$2" - local parent base staged_dir backup_root backup_dir - - parent=$(dirname "$target_dir") - base=$(basename "$target_dir") - staged_dir=$(mktemp -d "$parent/.${base}.staged.XXXXXX") - backup_root=$(mktemp -d "$parent/.${base}.backup.XXXXXX") - backup_dir="$backup_root/$base" - - # Copy to the package filesystem before swapping. This keeps the original - # package directory intact if copying from /tmp fails or is interrupted. - if ! cp -a "$work_dir/." "$staged_dir/"; then - print_error "Failed to stage synced package for $base" - rm -rf "$staged_dir" "$backup_root" - return 1 - fi - - if [[ -e "$target_dir" ]]; then - if ! mv "$target_dir" "$backup_dir"; then - print_error "Failed to back up existing package directory: $target_dir" - rm -rf "$staged_dir" "$backup_root" - return 1 - fi - fi - - if ! mv "$staged_dir" "$target_dir"; then - print_error "Failed to install synced package directory: $target_dir" - if [[ -e "$backup_dir" ]]; then - mv "$backup_dir" "$target_dir" || true - fi - rm -rf "$staged_dir" "$backup_root" - return 1 - fi - - rm -rf "$backup_root" "$work_dir" -} - -set_upstream_commit() { - local package_dir="$1" - local commit="$2" - local metadata="$package_dir/.omarchy/package.json" - local tmpfile - - tmpfile=$(mktemp) - jq --arg commit "$commit" '.upstream_commit = $commit' "$metadata" > "$tmpfile" - mv "$tmpfile" "$metadata" -} - -apply_omarchy_patches() { - local package_dir="$1" - local patches_dir="$package_dir/.omarchy/patches" - local applied=false - - [[ -d "$patches_dir" ]] || return 1 - - shopt -s nullglob - local patch_files=("$patches_dir"/*.patch) - local patch_dir_files=("$patches_dir"/*) - shopt -u nullglob - - if [[ ${#patch_files[@]} -eq 0 ]]; then - if [[ ${#patch_dir_files[@]} -gt 0 ]]; then - print_warning "No .patch files found in $patches_dir" - fi - return 1 - fi - - print_info "Applying Omarchy patches for $(display_package_name "$package_dir")..." - local patch_file - for patch_file in "${patch_files[@]}"; do - print_info " $(basename "$patch_file")" - if ! (cd "$package_dir" && patch -p1 --forward --batch --no-backup-if-mismatch < "$patch_file"); then - print_error "Failed to apply patch: $patch_file" - return 2 - fi - applied=true - done - - [[ "$applied" == true ]] -} - -run_omarchy_post_sync_hook() { - local package_dir="$1" - local package="$2" - local aur_package="$3" - local aur_pkgrel="$4" - local hook="$package_dir/.omarchy/post-sync.sh" - - [[ -f "$hook" ]] || return 1 - - print_info "Running Omarchy post-sync hook for $(display_package_name "$package_dir")..." - if ! ( - cd "$package_dir" - PACKAGE_NAME="$package" \ - AUR_PACKAGE_NAME="$aur_package" \ - AUR_PKGREL="$aur_pkgrel" \ - bash ".omarchy/post-sync.sh" - ); then - print_error "Failed to run post-sync hook: $hook" - return 2 - fi - - return 0 -} - -apply_pkgrel_suffix_if_customized() { - local package_dir="$1" - local aur_pkgrel="$2" - - [[ -n "$aur_pkgrel" ]] || return 0 - - print_info "Applying Omarchy pkgrel suffix for $(display_package_name "$package_dir"): pkgrel=$aur_pkgrel.1" - set_pkgrel "$package_dir" "$aur_pkgrel.1" -} - -apply_pkgrel_override() { - local package_dir="$1" - local aur_pkgrel="$2" - local previous_pkgver="$3" - local metadata="$package_dir/.omarchy/package.json" - local pkgbuild="$package_dir/PKGBUILD" - - [[ -f "$metadata" ]] || return 1 - jq -e 'has("pkgrel")' "$metadata" >/dev/null || return 1 - - local current_pkgver suffix offset base rel tmpfile - # Read through the same accessor that produced previous_pkgver. Parsing it a - # second time here let a quoted pkgver= compare unequal to itself, which threw - # away the pkgrel metadata of an unchanged package on every sync. - current_pkgver=$(get_pkgbuild_field "$package_dir" pkgver) - - if [[ -n "$previous_pkgver" && "$current_pkgver" != "$previous_pkgver" ]]; then - print_info "Removing stale pkgrel metadata for $(display_package_name "$package_dir") (pkgver changed: $previous_pkgver -> $current_pkgver)" - tmpfile=$(mktemp) - jq 'del(.pkgrel)' "$metadata" > "$tmpfile" - mv "$tmpfile" "$metadata" - return 1 - fi - - suffix=$(jq -r '.pkgrel.suffix // 1' "$metadata") - offset=$(jq -r '.pkgrel.offset // 0' "$metadata") - - if [[ ! "$offset" =~ ^[0-9]+$ || ! "$aur_pkgrel" =~ ^[0-9]+$ ]]; then - print_error "pkgrel offset requires numeric AUR pkgrel for $(display_package_name "$package_dir")" - return 2 - fi - - base=$((aur_pkgrel + offset)) - rel="$base.$suffix" - - print_info "Applying pkgrel suffix for $(display_package_name "$package_dir"): AUR pkgrel=$aur_pkgrel, offset=$offset, suffix=$suffix -> pkgrel=$rel" - set_pkgrel "$package_dir" "$rel" - return 0 -} - -clone_aur_package() { - local aur_package="$1" - local dest="$2" - local clone_log="$TEMP_DIR/clone.log" - local attempt - - for attempt in 1 2 3; do - rm -rf "$dest" - if git clone "https://aur.archlinux.org/${aur_package}.git" "$dest" >"$clone_log" 2>&1; then - return 0 - fi - if [[ $attempt -lt 3 ]]; then - print_warning "Clone of $aur_package failed (attempt $attempt/3), retrying in 10s..." - sleep 10 - fi - done - - print_warning "Failed to clone $aur_package after 3 attempts: $(tail -n 1 "$clone_log")" - return 1 -} - -sync_package() { - local package="$1" - local package_dir="$PKGBUILDS_DIR/$package" - local metadata="$package_dir/.omarchy/package.json" - - if [[ ! -f "$metadata" ]]; then - if [[ "$SPECIFIC_MODE" == true ]]; then - print_error "Package $package is missing .omarchy/package.json" - ((++FAILED)) - else - print_warning "Skipping $package: missing .omarchy/package.json" - ((++SKIPPED)) - fi - return 0 - fi - - if [[ "$(jq -r '.source // ""' "$metadata")" != "aur" ]]; then - print_info "Skipping $package: source is not AUR" - ((++SKIPPED)) - return 0 - fi - - if [[ "$(jq -r 'if has("sync") then .sync else true end' "$metadata")" == "false" ]]; then - print_info "Skipping $package: AUR sync disabled" - ((++SKIPPED)) - return 0 - fi - - local aur_package - aur_package=$(jq -r --arg package "$package" '.aur // $package' "$metadata") - - if [[ "$aur_package" == "$package" ]]; then - print_info "Syncing $package from AUR..." - else - print_info "Syncing $package from AUR package $aur_package..." - fi - - cd "$TEMP_DIR" - - if ! clone_aur_package "$aur_package" "$TEMP_DIR/$aur_package"; then - ((++FAILED)) - return 0 - fi - - if [[ ! -f "$TEMP_DIR/$aur_package/PKGBUILD" ]]; then - print_warning "AUR repository for $aur_package is empty (package does not exist in AUR)" - ((++FAILED)) - return 0 - fi - - local aur_dir="$TEMP_DIR/$aur_package" - local work_dir="$TEMP_DIR/${package}.work" - local aur_pkgrel previous_pkgver upstream_commit - aur_pkgrel=$(get_pkgbuild_field "$aur_dir" pkgrel) - previous_pkgver=$(get_pkgbuild_field "$package_dir" pkgver || true) - upstream_commit=$(git -C "$aur_dir" rev-parse HEAD) - - rm -rf "$work_dir" - copy_aur_contents "$aur_dir" "$work_dir" - rm -rf "$work_dir/.omarchy" - cp -a "$package_dir/.omarchy" "$work_dir/.omarchy" - - local customized=false - - if apply_omarchy_patches "$work_dir"; then - customized=true - else - local patch_status=$? - if [[ $patch_status -eq 2 ]]; then - ((++FAILED)) - return 0 - fi - fi - - if run_omarchy_post_sync_hook "$work_dir" "$package" "$aur_package" "$aur_pkgrel"; then - customized=true - else - local hook_status=$? - if [[ $hook_status -eq 2 ]]; then - ((++FAILED)) - return 0 - fi - fi - - local pkgrel_overridden=false - set +e - apply_pkgrel_override "$work_dir" "$aur_pkgrel" "$previous_pkgver" - local pkgrel_status=$? - set -e - case "$pkgrel_status" in - 0) pkgrel_overridden=true ;; - 1) ;; - *) ((++FAILED)); return 0 ;; - esac - - if [[ "$customized" == true && "$pkgrel_overridden" == false ]]; then - apply_pkgrel_suffix_if_customized "$work_dir" "$aur_pkgrel" - fi - - remove_aur_only_files "$work_dir" - - set_upstream_commit "$work_dir" "$upstream_commit" - if ! commit_synced_worktree "$work_dir" "$package_dir"; then - ((++FAILED)) - return 0 - fi - - SYNCED_PACKAGES+=("$package") - ((++SYNCED)) -} - -if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 ]]; then - SPECIFIC_MODE=true - for package in "${SPECIFIC_PACKAGES[@]}"; do - sync_package "$package" - done -else - while IFS= read -r package; do - sync_package "$package" - done < <(packages_for_aur_sync) -fi - -echo "" -if [[ $FAILED -gt 0 ]]; then - print_error "Sync completed with failures" -else - print_success "Sync complete!" -fi -echo " Target: $PKGBUILDS_DIR" -echo " Synced: $SYNCED" -echo " Skipped: $SKIPPED" -echo " Failed: $FAILED" - -if [[ $FAILED -gt 0 ]]; then - exit 1 -fi diff --git a/bin/sync-upstream b/bin/sync-upstream index 148cd13..2f84506 100755 --- a/bin/sync-upstream +++ b/bin/sync-upstream @@ -9,6 +9,7 @@ source "$BUILD_ROOT/helpers/upstream-github.sh" TEMP_DIR=$(mktemp -d) trap 'rm -rf "$TEMP_DIR"' EXIT +export UPSTREAM_CACHE_DIR="$TEMP_DIR/watch-cache" SPECIFIC_PACKAGES=() @@ -332,6 +333,12 @@ sync_package() { return 0 fi + if jq -e '.sync == false' "$package_dir/.omarchy/package.json" >/dev/null 2>&1; then + print_info "Skipping $package: upstream updates held by sync=false" + ((++SKIPPED)) + return 0 + fi + local provider has_upstream=false provider=$(package_upstream_provider "$package_dir") if package_has_upstream_provider "$package_dir"; then @@ -372,6 +379,21 @@ sync_package() { print_info "Checking $package for upstream releases..." + if [[ "$provider" == watch ]]; then + local result + if ! result=$(python3 "$BUILD_ROOT/helpers/upstream-watch.py" sync "$package_dir" --min-age "$min_age"); then + print_error "Upstream watch failed for $package" + ((++FAILED)) + elif [[ $(jq -r .status <<<"$result") == updated ]]; then + print_success " $(jq -r '.before + " -> " + .after' <<<"$result")" + ((++UPDATED)) + else + print_info " $(jq -r '.reason' <<<"$result")" + ((++SKIPPED)) + fi + return 0 + fi + local release release_status=0 if [[ -n "$provider" ]]; then case "$provider" in @@ -757,6 +779,9 @@ EOF mkdir -p "$one_root" cp -a "$BUILD_ROOT/pkgbuilds/1password" "$one_root/1password" one_dir="$one_root/1password" + # Keep the real recipe shape, but make the fixture's starting version stable. + # Otherwise a routine package update can outrun the mocked release below. + sed -i -e 's/^pkgver=.*/pkgver=8.12.34/' -e 's/^pkgrel=.*/pkgrel=2/' "$one_dir/PKGBUILD" debian_fetch_packages() { printf 'Package: 1password\nVersion: %s\n' "$one_version" } diff --git a/build/build.sh b/build/build.sh index bcf02a4..4c39147 100755 --- a/build/build.sh +++ b/build/build.sh @@ -411,6 +411,11 @@ build_package() { ln -sf omarchy-build.db.tar.zst omarchy-build.db || return 1 fi + # A release may publish successful builds even when a peer fails. Record + # outputs only after this package's entire split build has completed. + mkdir -p "$BUILD_PLAN_DIR/artifacts" || return 1 + printf '%s\n' "${new_pkgs[@]}" > "$BUILD_PLAN_DIR/artifacts/$pkg" || return 1 + echo " Successfully built $pkg" return 0 else @@ -526,9 +531,17 @@ check_needs_build() { if [[ "$local_version" == "$pkgbuild_version" ]]; then return 1 # Already up to date - else - return 0 # Needs building fi + + # Match check-versions: a retained archive is already published even when + # the DB now indexes a newer release (for example, 4.0.4rc1 vs 4.0.3). + # Rebuilding it would produce different bytes under an immutable filename. + if package_version_is_published "$FINAL_OUTPUT_DIR" "$pkg" "$pkgbuild_version" "$ARCH"; then + echo " + $pkg $pkgbuild_version - archive already published; skipping rebuild" + return 1 + fi + + return 0 # Needs building } # Collect packages that should be built for the selected mirror diff --git a/docs/upstream-sources.md b/docs/upstream-sources.md new file mode 100644 index 0000000..9276bd2 --- /dev/null +++ b/docs/upstream-sources.md @@ -0,0 +1,178 @@ +# Direct upstream watches + +Omarchy owns the recipes in `pkgbuilds/`. `bin/sync-upstream` discovers new +releases directly from project/vendor feeds and updates versions, declared +release variables, and the source checksums already used by the recipe. It never +imports upstream PKGBUILDs or runs downloaded build scripts. Architecture support, +root install hooks, dependencies, and build functions remain ours to maintain. + +`upstream.watch` complements the existing declarative providers and custom hooks: + +```json +{ + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/walker", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + } +} +``` + +## Watch fields + +Choose exactly one provider: `github` (owner/repository), `git_tags` (repository +HTTPS URL), `git_branch` (repository URL plus explicit `branch`), `npm` or `pypi` +(package name), `debian` (Packages index plus exact `package`), `json` (URL plus +version `path`), `regex` (text URL), `redirect` (final HTTPS download URL), or +`archive` (inspect archive metadata without extracting/executing code). + +Tag, text, redirect and archive watches use an explicit `pattern` with a named +`version` capture. Tag patterns match the entire tag. `version` optionally formats +those captures into an Arch pkgver; e.g. Sublime uses `4.{version}`. JSON feeds can +expose additional capture values through `fields`, a name-to-JSON-path map. + +`variables` maps recipe scalars such as `_commit` or `_build` to capture templates. +Only explicitly declared underscore-prefixed variables can change. GitHub +`{commit}` resolves the selected tag, not a moving target_commitish branch. +`submodules` can map a recipe variable to a gitlink in the selected GitHub tag; +RustDesk uses this for hbb_common. Downloaded repository code is never evaluated. + +For upstreams that rebuild a release, declare a numeric `revision` template and +its `revision_variable`. With unchanged pkgver, only an increasing revision can +advance that variable, and the downstream pkgrel increments instead of resetting. +Cursor CLI uses `sequence` to preserve its date/counter/hash version convention +when the vendor publishes a second hash on the same day. A new pkgver resets +pkgrel to 1, but the complete epoch:pkgver-pkgrel must still increase. + +GitHub releases exclude drafts and prereleases unless `allow_prerelease` is true. +Existing `min_release_age` policies apply: a feed without a verifiable publication +time cannot bypass a configured hold. Git branch watches derive a commit count +and date from the actual branch history and write an immutable source pin. + +Checksums retain their algorithms (SHA256, SHA512, BLAKE2, etc.) and source order. +Changed git sources are hashed with makepkg's git-archive convention. Unchanged +sources retain their hashes; `mutable_sources` explicitly names entries such as +`source:0` that must be fetched again for a new version despite a stable URL. +Existing `SKIP` entries remain unchanged (including signed metadata verified by +the recipe); new skips are never introduced. Changed URLs are still fetched. +A matching GitHub release asset SHA256 digest avoids downloading large assets. +Missing architecture artifacts or malformed metadata fail the package atomically. +Every declared architecture must read back the same release and checksum values. + +Archive watches use `member` to select a text member, or `filenames: true` to read +versions from archive member names. Debian archives are read through their control +metadata. `unescape_json` handles JSON strings embedded in a vendor's HTML page. + +## Maintenance and validation + +Running watches locally requires Python 3.11+, Bash, curl, git, jq, Arch's +`vercmp`, and `bsdtar`. CI installs these in its Arch container. + +- Edit packaging and architecture changes directly in PKGBUILD. The old AUR + overlays have been folded into these recipes and removed. +- Keep source-code patches and install hooks checked in as ordinary package files. +- Bump pkgrel when changing a recipe at the same version. Removing a dotted AUR + suffix must never lower the complete version. +- Add a watch with each new package. `bin/add-package --source aur` is a one-time + import; it records historical `origin` metadata and leaves an owned recipe. +- `python helpers/upstream-watch.py check pkgbuilds/NAME` checks release discovery + without rewriting the recipe. `bin/sync-upstream NAME` performs the update. +- `python tests/upstream-watch.py` tests update atomicity, architecture coverage, + version ordering, source hashes and hostile metadata using offline fixtures. + +The scheduled workflow continues reviewing completed updates if another package +fails. The failing recipe stays unchanged and the run still reports failure. + +## Migrated package watches + +68 active AUR packages now use direct watches. The nine previously disabled +packages retain manual maintenance holds. Historical AUR provenance is recorded +in `origin` and has no effect on release selection. + +| Package | Provider | Upstream | +|---|---|---| +| `1password-beta` | debian | [https://downloads.1password.com/linux/debian/amd64/dists/beta/main/binary-amd64/Packages](https://downloads.1password.com/linux/debian/amd64/dists/beta/main/binary-amd64/Packages) | +| `1password-cli` | json | [https://app-updates.agilebits.com/check/1/0/CLI2/en/0](https://app-updates.agilebits.com/check/1/0/CLI2/en/0) | +| `aether` | github | [omacom/aether](https://github.com/omacom/aether) | +| `asusctl` | git_tags | [https://github.com/OpenGamingCollective/asusctl.git](https://github.com/OpenGamingCollective/asusctl.git) | +| `basecamp-cli` | github | [basecamp/basecamp-cli](https://github.com/basecamp/basecamp-cli) | +| `bun-bin` | github | [oven-sh/bun](https://github.com/oven-sh/bun) | +| `claude-code` | regex | [https://downloads.claude.ai/claude-code-releases/latest](https://downloads.claude.ai/claude-code-releases/latest) | +| `cliamp` | github | [bjarneo/cliamp](https://github.com/bjarneo/cliamp) | +| `crush-bin` | github | [charmbracelet/crush](https://github.com/charmbracelet/crush) | +| `cursor-bin` | json | [https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable](https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable) | +| `cursor-cli` | regex | [https://cursor.com/install](https://cursor.com/install) | +| `dbxcli-bin` | github | [dropbox/dbxcli](https://github.com/dropbox/dbxcli) | +| `dropbox` | redirect | [https://www.dropbox.com/download?plat=lnx.x86_64](https://www.dropbox.com/download?plat=lnx.x86_64) | +| `dropbox-cli` | regex | [https://linux.dropbox.com/packages/](https://linux.dropbox.com/packages/) | +| `elephant` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-all` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-archlinuxpkgs` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-bluetooth` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-calc` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-clipboard` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-desktopapplications` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-files` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-menus` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-providerlist` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-runner` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-symbols` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-todo` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-unicode` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `elephant-websearch` | github | [abenz1267/elephant](https://github.com/abenz1267/elephant) | +| `heroic-games-launcher-bin` | github | [Heroic-Games-Launcher/HeroicGamesLauncher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) | +| `hyprshade` | pypi | [hyprshade](https://pypi.org/project/hyprshade/) | +| `lib32-nvidia-580xx-utils` | regex | [https://download.nvidia.com/XFree86/Linux-x86_64/](https://download.nvidia.com/XFree86/Linux-x86_64/) | +| `limine-mkinitcpio-hook` | git_tags | [https://gitlab.com/Zesko/limine-entry-tool.git](https://gitlab.com/Zesko/limine-entry-tool.git) | +| `limine-snapper-sync` | git_tags | [https://gitlab.com/Zesko/limine-snapper-sync.git](https://gitlab.com/Zesko/limine-snapper-sync.git) | +| `lmstudio-bin` | regex | [https://lmstudio.ai/download](https://lmstudio.ai/download) | +| `localsend` | github | [localsend/localsend](https://github.com/localsend/localsend) | +| `localsend-bin` | github | [localsend/localsend](https://github.com/localsend/localsend) | +| `macbook12-spi-driver-dkms` | git_branch | [https://github.com/marc-git/macbook12-spi-driver.git](https://github.com/marc-git/macbook12-spi-driver.git) | +| `makima-bin` | github | [cyber-sushi/makima](https://github.com/cyber-sushi/makima) | +| `minecraft-launcher` | archive | [https://launcher.mojang.com/download/Minecraft.deb](https://launcher.mojang.com/download/Minecraft.deb) | +| `nautilus-dropbox` | github | [dropbox/nautilus-dropbox](https://github.com/dropbox/nautilus-dropbox) | +| `nautilus-open-any-terminal` | git_tags | [https://github.com/Stunkymonkey/nautilus-open-any-terminal.git](https://github.com/Stunkymonkey/nautilus-open-any-terminal.git) | +| `nordvpn-bin` | debian | [https://repo.nordvpn.com/deb/nordvpn/debian/dists/stable/main/binary-amd64/Packages](https://repo.nordvpn.com/deb/nordvpn/debian/dists/stable/main/binary-amd64/Packages) | +| `nvidia-580xx-utils` | regex | [https://download.nvidia.com/XFree86/Linux-x86_64/](https://download.nvidia.com/XFree86/Linux-x86_64/) | +| `omarchy-chromium-bin` | github | [omacom/omarchy-chromium](https://github.com/omacom/omarchy-chromium) | +| `omarchy-emacs` | git_tags | [https://github.com/scottjones/omarchy-emacs.git](https://github.com/scottjones/omarchy-emacs.git) | +| `omazed` | git_tags | [https://github.com/aps6/omazed.git](https://github.com/aps6/omazed.git) | +| `once-bin` | github | [basecamp/once](https://github.com/basecamp/once) | +| `openai-codex-bin` | github | [openai/codex](https://github.com/openai/codex) | +| `python-mediapipe` | github | [google-ai-edge/mediapipe](https://github.com/google-ai-edge/mediapipe) | +| `python-sounddevice` | pypi | [sounddevice](https://pypi.org/project/sounddevice/) | +| `python-terminaltexteffects` | pypi | [terminaltexteffects](https://pypi.org/project/terminaltexteffects/) | +| `rustdesk` | github | [rustdesk/rustdesk](https://github.com/rustdesk/rustdesk) | +| `spotify` | debian | [https://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages](https://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages) | +| `sublime-text-4` | json | [https://www.sublimetext.com/updates/4/stable_update_check](https://www.sublimetext.com/updates/4/stable_update_check) | +| `sunshine` | github | [LizardByte/Sunshine](https://github.com/LizardByte/Sunshine) | +| `ttf-ia-writer` | git_branch | [https://github.com/iaolo/iA-Fonts.git](https://github.com/iaolo/iA-Fonts.git) | +| `tuxedo-drivers-nocompatcheck-dkms` | git_tags | [https://gitlab.com/kronerm/tuxedo-drivers-nocompatcheck.git](https://gitlab.com/kronerm/tuxedo-drivers-nocompatcheck.git) | +| `typora` | debian | [https://downloads.typora.io/linux/Packages](https://downloads.typora.io/linux/Packages) | +| `ufw-docker` | git_tags | [https://github.com/chaifeng/ufw-docker.git](https://github.com/chaifeng/ufw-docker.git) | +| `vi` | regex | [https://sources.archlinux.org/other/vi/](https://sources.archlinux.org/other/vi/) | +| `visual-studio-code-bin` | json | [https://update.code.visualstudio.com/api/update/linux-deb-x64/stable/latest](https://update.code.visualstudio.com/api/update/linux-deb-x64/stable/latest) | +| `walker` | github | [abenz1267/walker](https://github.com/abenz1267/walker) | +| `xdg-terminal-exec` | git_tags | [https://gitlab.freedesktop.org/Vladimir-csp/xdg-terminal-exec.git](https://gitlab.freedesktop.org/Vladimir-csp/xdg-terminal-exec.git) | +| `xpadneo-dkms` | github | [atar-axis/xpadneo](https://github.com/atar-axis/xpadneo) | +| `yaru-icon-theme` | git_tags | [https://github.com/ubuntu/yaru.git](https://github.com/ubuntu/yaru.git) | +| `yay` | github | [Jguer/yay](https://github.com/Jguer/yay) | +| `yt6801-dkms` | archive | [https://www.motor-comm.com/Cn/Skippower/downloadFile.html?id=1817](https://www.motor-comm.com/Cn/Skippower/downloadFile.html?id=1817) | + +## Existing manual holds + +`grok-bot`, `libfprint-git`, `libretro-cap32-git`, `libretro-database-git`, `libretro-fbneo-git`, `libretro-uae-git`, `libretro-vice-git`, `quickshell-git`, `supergfxctl`. + +These packages were already excluded from automatic AUR updates. The migration preserves that policy. + +## Package-specific boundaries + +- NVIDIA watches remain on the 580 driver branch. +- Hardware-specific packages keep their declared architectures; this migration does not invent ARM binaries for x86-only upstreams. +- iA Duospace was deleted upstream. Its four legacy font files retain their original immutable pin while the other families track the current repository. +- RustDesk reads hbb_common from the release gitlink; its existing build-time dependency/toolchain checks remain in force. +- Spotify uses HTTPS and retains its signed Release/Packages verification. +- Source and build compatibility still need review when upstream code changes. Direct watches remove AUR recipe churn, not the need to maintain packaging. diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh index c66aef1..ce46e92 100644 --- a/helpers/package-metadata.sh +++ b/helpers/package-metadata.sh @@ -3,15 +3,13 @@ # Expects package directories in $PKGBUILDS_DIR, each with: # .omarchy/package.json # -# Minimal schema: -# { "source": "aur" } -# { "source": "aur", "sync": false } -# { "source": "aur", "aur": "different-aur-name" } -# { "source": "aur", "release_ring": "fast" } -# { "source": "aur", "skip_build": true } -# { "source": "aur", "pkgrel": { "suffix": 1, "offset": 1 } } -# { "source": "aur", "rebuild_on": ["qt6-base"] } +# Minimal schema (legacy source:aur remains readable for initial imports): # { "source": "local" } +# { "source": "local", "sync": false } +# { "source": "local", "release_ring": "fast" } +# { "source": "local", "skip_build": true } +# { "source": "local", "rebuild_on": ["qt6-base"] } +# { "source": "local", "upstream": { "watch": { "github": "owner/repo", "pattern": "v(?P[0-9.]+)" } } } # { "source": "local", "channels": ["edge"] } # { "source": "local", "channels": ["edge", "rc", "stable"] } # { "source": "local", "min_release_age": "24h" } @@ -21,7 +19,7 @@ # { "source": "local", "upstream": { "npm": "@scope/package", "sources": { "any": ["{npm_tarball}"] } } } # { "source": "local", "upstream": { "debian": "https://example/debian/dists/stable/main/binary-amd64/Packages", "package": "example", "sources": { "any": ["https://example/releases/{pkgver}.tar.gz"] } } } # -# bin/sync-aur also writes upstream_commit for AUR-backed packages, and +# bin/import-aur records historical origin.aur and origin.commit; # bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on. if [[ -z "${PKGBUILDS_DIR:-}" ]]; then @@ -193,6 +191,18 @@ package_supports_arch() { esac } +# The channel DB indexes only its newest version, but older published archives +# remain immutable. Both the scheduler and build planner must skip an existing +# filename even when the checkout differs from the version currently indexed. +package_version_is_published() { + local repo_dir="$1" package="$2" version="$3" target="$4" path + for path in "$repo_dir/$package-$version-$target.pkg.tar."* \ + "$repo_dir/$package-$version-any.pkg.tar."*; do + [[ -f "$path" && "$path" != *.sig ]] && return 0 + done + return 1 +} + # Channel membership: where a package may be published. Packages without a # `channels` key are members of every channel (they flow edge -> rc -> stable). package_has_channels() { @@ -523,8 +533,9 @@ validate_package_metadata() { if has("upstream") | not then true elif (.upstream | type) != "object" then false else .upstream | - ([has("github"), has("git_tags"), has("npm"), has("debian")] | map(select(.)) | length) == 1 - and if has("github") then + ([has("github"), has("git_tags"), has("npm"), has("debian"), has("watch")] | map(select(.)) | length) == 1 + and if has("watch") then (.watch | type == "object") + elif has("github") then (.github | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z")) and (if has("checksums") then (.checksums | type == "string" and length > 0) else true end) and (if has("digests") then (.digests | type == "boolean") else true end) @@ -550,10 +561,14 @@ validate_package_metadata() { end end ' "$metadata" >/dev/null; then - echo "invalid upstream for $(basename "$pkgdir"): configure exactly one valid github, git_tags, npm, or debian provider" + echo "invalid upstream for $(basename "$pkgdir"): configure exactly one valid github, git_tags, npm, debian, or watch provider" return 1 fi + if jq -e '.upstream? | objects | has("watch")' "$metadata" >/dev/null; then + python3 "${BASH_SOURCE[0]%/*}/upstream-watch.py" validate "$pkgdir" || return 1 + fi + pkgrel_type=$(jq -r 'if has("pkgrel") then .pkgrel | type else "missing" end' "$metadata") case "$pkgrel_type" in object|missing) ;; diff --git a/helpers/upstream-github.sh b/helpers/upstream-github.sh index 6c7e1ba..f19da78 100644 --- a/helpers/upstream-github.sh +++ b/helpers/upstream-github.sh @@ -31,7 +31,7 @@ package_upstream_provider() { metadata=$(metadata_file_for_dir "$pkgdir") jq -r ' (.upstream? | objects) as $u - | [$u | keys[] | select(. == "github" or . == "git_tags" or . == "npm" or . == "debian")] + | [$u | keys[] | select(. == "github" or . == "git_tags" or . == "npm" or . == "debian" or . == "watch")] | if length == 1 then .[0] else "" end ' "$metadata" } diff --git a/helpers/upstream-watch.py b/helpers/upstream-watch.py new file mode 100644 index 0000000..6e17b97 --- /dev/null +++ b/helpers/upstream-watch.py @@ -0,0 +1,566 @@ +#!/usr/bin/env python3 +"""Discover releases and update our own recipes; never import upstream build code. + +The watch selects release metadata. Sources, supported architectures, integrity +algorithms and packaging behavior stay in the checked-in PKGBUILD. Only release +scalars and checksum arrays are replaced, atomically, after every source passes. +""" +import argparse +import datetime as dt +import gzip +import hashlib +import io +import json +import os +from pathlib import Path +import re +import subprocess +import sys +import tarfile +import tempfile +from urllib.parse import quote, urlsplit +import zipfile + +PROVIDERS = {"github", "git_tags", "git_branch", "npm", "pypi", "debian", "json", "regex", "archive", "redirect"} +VERSION = re.compile(r"[A-Za-z0-9][A-Za-z0-9._+]*\Z") +SCALAR = re.compile(r"[A-Za-z0-9._+/-]+\Z") +SUM = re.compile(r"(md5|sha1|sha224|sha256|sha384|sha512|b2)sums(_[a-z0-9_]+)?\Z") +HASHES = {"b2": "blake2b"} + + +def run(args, **kwargs): + return subprocess.check_output(args, **kwargs) + + +def vercmp(a, b): + return int(run(["vercmp", a, b], text=True).strip()) + + +def https(url): + parts = urlsplit(url) + if parts.scheme != "https" or not parts.hostname or parts.username or parts.password or re.search(r"[\s\x00-\x1f]", url): + raise ValueError(f"expected an HTTPS upstream URL: {url!r}") + return url + + +class Fetcher: + def __init__(self, cache): + self.cache = Path(cache) + self.cache.mkdir(parents=True, exist_ok=True) + + def file(self, url): + https(url) + dest = self.cache / hashlib.sha256(url.encode()).hexdigest() + if not dest.exists(): + scratch = dest.with_suffix(f".{os.getpid()}.tmp") + command = ["curl", "--proto", "=https", "--proto-redir", "=https", "-fsSL", + "--connect-timeout", "20", "--max-time", "300", "--retry", "2", "-o", str(scratch), url] + # Credentials only go to GitHub's API, never to release assets or vendors. + token = os.environ.get("UPSTREAM_GITHUB_TOKEN") + if token and urlsplit(url).hostname == "api.github.com": + command[1:1] = ["--config", "-"] + subprocess.run(command, input=f'header = "Authorization: Bearer {token}"\n', text=True, check=True) + else: + subprocess.run(command, check=True) + scratch.replace(dest) + return dest + + def text(self, url): + data = self.file(url).read_bytes() + if data.startswith(b"\x1f\x8b"): + data = gzip.decompress(data) + return data.decode() + + def json(self, url): + return json.loads(self.text(url)) + + +def validate(watch): + if not isinstance(watch, dict) or len(PROVIDERS & watch.keys()) != 1: + raise ValueError("watch must select exactly one release provider") + provider = next(iter(PROVIDERS & watch.keys())) + allowed = PROVIDERS | {"pattern", "path", "package", "branch", "variables", "fields", + "submodules", "allow_prerelease", "unescape_json", "filenames", + "sequence", "version", "revision", "revision_variable", + "mutable_sources", "member", "dist_tag"} + if watch.keys() - allowed: + raise ValueError(f"unknown watch fields: {sorted(watch.keys() - allowed)}") + value = watch[provider] + if not isinstance(value, str) or not value: + raise ValueError(f"invalid watch.{provider}") + if provider == "github": + if not re.fullmatch(r"[\w.-]+/[\w.-]+", value): + raise ValueError("invalid GitHub repository") + elif provider in {"npm", "pypi"}: + if not re.fullmatch(r"(?:@[\w.-]+/)?[\w.-]+", value): + raise ValueError("invalid registry package") + else: + https(value) + if "pattern" in watch or provider in {"github", "git_tags", "regex", "archive", "redirect"}: + if not isinstance(watch.get("pattern"), str): + raise ValueError("watch needs an explicit release pattern") + pattern = re.compile(watch["pattern"]) + if "version" not in pattern.groupindex: + raise ValueError("release pattern needs a named version group") + if provider == "json" and (not isinstance(watch.get("path"), str) or not watch["path"]): + raise ValueError("JSON watch needs a version path") + if provider == "debian": + name = watch.get("package", "") + if not isinstance(name, str) or not re.fullmatch(r"[a-z0-9][a-z0-9+.-]*", name): + raise ValueError("Debian watch needs an exact package name") + if provider == "git_branch": + branch = watch.get("branch", "") + if not isinstance(branch, str) or not branch or branch.startswith("-"): + raise ValueError("git branch watch needs an explicit branch") + run(["git", "check-ref-format", "refs/heads/" + branch]) + for field in ("variables", "submodules", "fields"): + mapping = watch.get(field, {}) + if not isinstance(mapping, dict): + raise ValueError(f"watch.{field} must be a string mapping") + for name, value in mapping.items(): + pattern = r"[a-z][a-z0-9_]*" if field == "fields" else r"_[a-z][a-z0-9_]*" + if not re.fullmatch(pattern, name) or not isinstance(value, str) or not value: + raise ValueError(f"invalid watch.{field} mapping") + if "submodules" in watch and provider != "github": + raise ValueError("submodules require a GitHub watch") + if watch.get("variables", {}).keys() & watch.get("submodules", {}).keys(): + raise ValueError("a release variable cannot also be a submodule") + for path in watch.get("submodules", {}).values(): + if path.startswith("/") or any(part in {"", ".", ".."} for part in path.split("/")): + raise ValueError("submodule path must be relative to the release repository") + for field in ("allow_prerelease", "unescape_json", "filenames", "sequence"): + if field in watch and not isinstance(watch[field], bool): + raise ValueError(f"watch.{field} must be boolean") + for field in ("version", "revision", "member", "dist_tag"): + if field in watch and (not isinstance(watch[field], str) or not watch[field]): + raise ValueError(f"watch.{field} must be a string template") + if "revision_variable" in watch: + name = watch["revision_variable"] + if not isinstance(name, str) or name not in watch.get("variables", {}) or not watch.get("revision"): + raise ValueError("revision_variable requires a declared variable and revision template") + for field in ("mutable_sources",): + entries = watch.get(field, []) + if not isinstance(entries, list) or any(not isinstance(v, str) or not re.fullmatch(r"source(?:_[a-z0-9_]+)?:[0-9]+", v) for v in entries): + raise ValueError(f"watch.{field} must name source-array:index entries") + return provider + + +def json_path(data, path): + for key in path.split("."): + data = data[int(key)] if isinstance(data, list) else data[key] + return data + + +def candidate(watch, values): + values = {k: str(v) for k, v in values.items() if v is not None} + version = watch.get("version", "{version}").format_map(values) + if not VERSION.fullmatch(version): + raise ValueError(f"unusable upstream version: {version!r}") + revision = watch.get("revision", "").format_map(values) + if revision and not re.fullmatch(r"[0-9]+", revision): + raise ValueError("upstream release revision must be numeric") + return {"pkgver": version, "values": values, + "published_at": values.get("published_at"), + "revision": revision} + + +def matches(watch, text, extra=None, full=False): + pattern = re.compile(watch["pattern"]) + found = [pattern.fullmatch(text)] if full else pattern.finditer(text) + for match in found: + if match: + yield candidate(watch, {**(extra or {}), **match.groupdict()}) + + +def discover(watch, fetch): + provider = validate(watch) + feed = watch[provider] + results = [] + if provider == "github": + releases = fetch.json(f"https://api.github.com/repos/{feed}/releases?per_page=100") + if not isinstance(releases, list): + raise ValueError("GitHub did not return a release list") + for release in releases: + if release.get("draft") or (release.get("prerelease") and not watch.get("allow_prerelease")): + continue + for item in matches(watch, release["tag_name"], {"tag": release["tag_name"], "published_at": release["published_at"]}, full=True): + item["assets"] = release.get("assets", []) + results.append(item) + elif provider == "git_tags": + refs = run(["git", "ls-remote", "--tags", feed], text=True) + tags = {} + for line in refs.splitlines(): + commit, ref = line.split() + tag = ref.removeprefix("refs/tags/") + if tag.endswith("^{}"): + tags[tag[:-3]] = commit + else: + tags.setdefault(tag, commit) + for tag, commit in tags.items(): + results.extend(matches(watch, tag, {"tag": tag, "commit": commit}, full=True)) + elif provider == "git_branch": + with tempfile.TemporaryDirectory(prefix="upstream-git-") as work: + subprocess.run(["git", "clone", "--quiet", "--bare", "--filter=blob:none", "--single-branch", "--branch", watch["branch"], feed, work], check=True) + commit = run(["git", "-C", work, "rev-parse", "HEAD"], text=True).strip() + count = run(["git", "-C", work, "rev-list", "--count", "HEAD"], text=True).strip() + date = run(["git", "-C", work, "show", "-s", "--format=%cs", "HEAD"], text=True).strip().replace("-", "") + timestamp = run(["git", "-C", work, "show", "-s", "--format=%cI", "HEAD"], text=True).strip() + results.append(candidate(watch, {"version": date, "date": date, "count": count, "commit": commit, "published_at": timestamp})) + elif provider == "npm": + data = fetch.json("https://registry.npmjs.org/" + quote(feed, safe="")) + version = data["dist-tags"][watch.get("dist_tag", "latest")] + results.append(candidate(watch, {"version": version, "published_at": data.get("time", {}).get(version)})) + elif provider == "pypi": + data = fetch.json(f"https://pypi.org/pypi/{feed}/json") + version = data["info"]["version"] + dates = [r["upload_time_iso_8601"] for r in data["releases"].get(version, []) if not r.get("yanked")] + if not dates: + raise ValueError("PyPI release has no unyanked files") + results.append(candidate(watch, {"version": version, "published_at": max(dates)})) + elif provider == "debian": + for stanza in re.split(r"\n\s*\n", fetch.text(feed).replace("\r", "")): + fields = dict(re.findall(r"^([A-Za-z0-9-]+): (.*)$", stanza, re.M)) + if fields.get("Package") != watch["package"]: + continue + if "pattern" in watch: + results.extend(matches(watch, fields["Version"], full=True)) + else: + results.append(candidate(watch, {"version": fields["Version"]})) + elif provider == "json": + data = fetch.json(feed) + values = {"version": json_path(data, watch["path"])} + values.update({name: json_path(data, path) for name, path in watch.get("fields", {}).items()}) + results.append(candidate(watch, values)) + elif provider == "redirect": + final_url = run(["curl", "--proto", "=https", "--proto-redir", "=https", "-fsSLI", "--max-time", "60", "-o", "/dev/null", "-w", "%{url_effective}", feed], text=True) + results.extend(matches(watch, final_url)) + elif provider == "regex": + text = fetch.text(feed) + if watch.get("unescape_json"): + text = text.replace('\\"', '"') + results.extend(matches(watch, text)) + elif provider == "archive": + file = fetch.file(feed) + if zipfile.is_zipfile(file): + with zipfile.ZipFile(file) as archive: + names = archive.namelist() + if watch.get("filenames"): + results.extend(matches(watch, "\n".join(names))) + for name in ([] if watch.get("filenames") else names): + if re.fullmatch(watch.get("member", ".*"), name): + results.extend(matches(watch, archive.read(name).decode())) + elif file.read_bytes()[:8] == b"!\n": + names = run(["bsdtar", "-tf", str(file)], text=True).splitlines() + controls = [name for name in names if name.startswith("control.tar")] + if len(controls) != 1: + raise ValueError("deb does not contain exactly one control archive") + data = run(["bsdtar", "-xOf", str(file), controls[0]]) + with tarfile.open(fileobj=io.BytesIO(data)) as archive: + members = [m for m in archive if m.name.removeprefix("./") == "control"] + if len(members) != 1: + raise ValueError("deb control file is missing or ambiguous") + results.extend(matches(watch, archive.extractfile(members[0]).read().decode())) + else: + with tarfile.open(file) as archive: + for member in archive: + if member.isfile() and re.fullmatch(watch.get("member", ".*"), member.name): + results.extend(matches(watch, archive.extractfile(member).read().decode())) + if not results: + raise ValueError(f"no matching releases in {feed}") + return results + + +def select_release(releases, min_age=0, now=None, bypass=False): + now = now or dt.datetime.now(dt.timezone.utc) + best = None + for release in releases: + if min_age and not bypass: + value = release.get("published_at") + if not value or not re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:?\d\d)", value): + raise ValueError("release age cannot be established") + if (now - dt.datetime.fromisoformat(value.replace("Z", "+00:00"))).total_seconds() < min_age: + continue + order = vercmp(release["pkgver"], best["pkgver"]) if best else 1 + if best and order == 0: + order = vercmp(release["revision"] or "0", best["revision"] or "0") + if order > 0: + best = release + return best + + +DUMP = r''' +source "$1" >/dev/null || exit 1 +set +u +for __watch_name in pkgver pkgrel epoch arch $(compgen -A variable | LC_ALL=C sort); do + case "$__watch_name" in + pkgver|pkgrel|epoch|arch|source|source_*|md5sums*|sha1sums*|sha224sums*|sha256sums*|sha384sums*|sha512sums*|b2sums*|_*) + [[ $__watch_name == __watch_* ]] && continue + declare -n __watch_value="$__watch_name" + printf '%s\0' "$__watch_name" "${#__watch_value[@]}" "${__watch_value[@]}" + unset -n __watch_value + ;; + esac +done +''' + + +def read_recipe(path, arch="x86_64"): + with tempfile.TemporaryDirectory(prefix="recipe-read-") as work: + env = {**os.environ, "CARCH": arch, "SRCDEST": work, "srcdir": work, "pkgdir": work} + data = run(["bash", "-c", DUMP, "_", str(path.resolve())], cwd=path.parent, env=env).decode().split("\0") + result = {} + index = 0 + while index < len(data) - 1: + name, size = data[index:index + 2] + index += 2 + size = int(size) + result[name] = data[index:index + size] + index += size + return result + + +def scalar(recipe, name, default=""): + return recipe.get(name, [default])[0] if recipe.get(name) else default + + +def replace_scalar(text, name, value): + if not SCALAR.fullmatch(value): + raise ValueError(f"unsafe {name} value") + pattern = re.compile(r"^" + re.escape(name) + r"=.*$", re.M) + if len(pattern.findall(text)) != 1: + raise ValueError(f"expected one top-level {name}= assignment") + return pattern.sub(lambda _: f"{name}={value}", text) + + +def replace_array(text, name, values): + starts = list(re.finditer(r"^" + re.escape(name) + r"=\(", text, re.M)) + if len(starts) != 1: + raise ValueError(f"expected one top-level {name}= array") + start = starts[0] + depth, quote_char, escaped, comment = 1, None, False, False + for index in range(start.end(), len(text)): + char = text[index] + if comment: + if char == "\n": comment = False + elif escaped: + escaped = False + elif char == "\\" and quote_char != "'": + escaped = True + elif quote_char: + if char == quote_char: quote_char = None + elif char in "\"'": quote_char = char + elif char == "#" and (index == 0 or text[index - 1].isspace()): comment = True + elif char == "(": depth += 1 + elif char == ")": + depth -= 1 + if depth == 0: + replacement = name + "=(" + " ".join("'" + value + "'" for value in values) + ")" + return text[:start.start()] + replacement + text[index + 1:] + raise ValueError(f"unclosed {name} array") + + +def bump_pkgrel(value): + if not re.fullmatch(r"[0-9]+(?:\.[0-9]+)?", value): + raise ValueError(f"invalid pkgrel: {value}") + components = value.split(".") + components[-1] = str(int(components[-1]) + 1) + return ".".join(components) + + +def complete_version(recipe): + return f"{scalar(recipe, 'epoch', '0')}:{scalar(recipe, 'pkgver')}-{scalar(recipe, 'pkgrel')}" + + +def hash_file(path, algorithm): + with path.open("rb") as stream: + return hashlib.file_digest(stream, HASHES.get(algorithm, algorithm)).hexdigest() + + +def source_url(source): + return source.split("::", 1)[-1] + + +def git_source_file(url, cache): + base, fragment = url.removeprefix("git+").split("#", 1) + kind, ref = fragment.split("=", 1) + https(base) + if kind not in {"tag", "commit"} or (kind == "commit" and not re.fullmatch(r"[0-9a-f]{40}", ref)): + raise ValueError("VCS sources must name an immutable commit or a checksummed tag") + if kind == "tag": + run(["git", "check-ref-format", "refs/tags/" + ref]) + dest = cache / (hashlib.sha256(url.encode()).hexdigest() + ".git.tar") + if not dest.exists(): + with tempfile.TemporaryDirectory(prefix="upstream-source-", dir=cache) as work: + subprocess.run(["git", "init", "--quiet", "--bare", work], check=True) + subprocess.run(["git", "-C", work, "fetch", "--quiet", "--depth=1", base, "refs/tags/" + ref if kind == "tag" else ref], check=True) + scratch = Path(work) / "source.tar" + with scratch.open("wb") as output: + subprocess.run(["git", "-c", "core.abbrev=no", "-C", work, "archive", "--format", "tar", "FETCH_HEAD"], stdout=output, check=True) + # The cache must never retain partial archives after a git failure. + scratch.replace(dest) + return dest + + +def updated_checksums(before, after, package, fetch, release, watch): + arrays = {} + source_names = {key for key in before if key == "source" or key.startswith("source_")} + if source_names != {key for key in after if key == "source" or key.startswith("source_")}: + raise ValueError("release changed the set of source architectures") + for source_name in sorted(source_names): + old_sources, sources = before[source_name], after[source_name] + suffix = source_name.removeprefix("source") + names = [name for name in before if SUM.fullmatch(name) and (SUM.fullmatch(name)[2] or "") == suffix] + if not sources: + continue + if len(sources) != len(old_sources) or not names: + raise ValueError(f"{source_name}: sources changed shape or have no checksums") + for name in names: + if len(before[name]) != len(sources): + raise ValueError(f"{name}: source/checksum count mismatch") + values = [] + algorithm = SUM.fullmatch(name)[1] + for index, source in enumerate(sources): + old = before[name][index] + if source == old_sources[index] and f"{source_name}:{index}" not in watch.get("mutable_sources", []): + values.append(old) + continue + url = source_url(source) + # A release API digest can supply SHA256 without downloading a + # large asset, but only when its exact declared URL matches. + assets = [a for a in release.get("assets", []) if a.get("browser_download_url") == url] + if algorithm == "sha256" and len(assets) == 1 and re.fullmatch(r"sha256:[0-9a-f]{64}", assets[0].get("digest") or ""): + values.append("SKIP" if old == "SKIP" else assets[0]["digest"][7:]) + continue + if url.startswith("git+https://"): + file = git_source_file(url, fetch.cache) + elif url.startswith("https://"): + file = fetch.file(url) + elif "://" not in url: + file = (package / url).resolve() + if not file.is_relative_to(package.resolve()) or not file.is_file(): + raise ValueError(f"unsafe local source: {url}") + else: + raise ValueError(f"unsupported source transport: {url}") + # Preserve existing signature/prepare()-verified sources. Never + # introduce SKIP; still fetch changed URLs to verify availability. + values.append("SKIP" if old == "SKIP" else hash_file(file, algorithm)) + if values != before[name]: + arrays[name] = values + return arrays + + +def resolve_release_fields(watch, release, fetch): + values = release["values"].copy() + if "github" in watch and any("{commit}" in value for value in watch.get("variables", {}).values()): + ref = fetch.json(f"https://api.github.com/repos/{watch['github']}/git/ref/tags/{quote(values['tag'], safe='')}")['object'] + if ref['type'] == 'tag': + ref = fetch.json(f"https://api.github.com/repos/{watch['github']}/git/tags/{ref['sha']}")['object'] + if ref['type'] != 'commit' or not re.fullmatch(r"[0-9a-f]{40}", ref['sha']): + raise ValueError("release tag does not resolve to a commit") + values['commit'] = ref['sha'] + variables = {k: template.format_map(values) for k, template in watch.get('variables', {}).items()} + for name, path in watch.get('submodules', {}).items(): + entry = fetch.json(f"https://api.github.com/repos/{watch['github']}/contents/{quote(path, safe='/')}?ref={quote(values['tag'], safe='')}") + if not entry.get('submodule_git_url') or not re.fullmatch(r"[0-9a-f]{40}", entry.get('sha', '')): + raise ValueError(f"release does not contain submodule {path}") + variables[name] = entry['sha'] + if any(not SCALAR.fullmatch(value) for value in variables.values()): + raise ValueError("unsafe release variable value") + release['variables'] = variables + return release + + +def sync(package, fetch, min_age=0, check=False): + metadata = json.loads((package / ".omarchy/package.json").read_text()) + if metadata.get("sync") is False: + return {"status": "skipped", "reason": "upstream updates held by sync=false"} + watch = metadata["upstream"]["watch"] + validate(watch) + path = package / "PKGBUILD" + original = path.read_text() + before = read_recipe(path) + release = select_release(discover(watch, fetch), min_age, bypass=os.environ.get("BYPASS_MIN_RELEASE_AGE") == "1") + if release is None: + return {"status": "skipped", "reason": "minimum release age"} + current = scalar(before, "pkgver") + if watch.get('sequence'): + prefix, counter, identity = current.rsplit('.', 2) + new_prefix, new_identity = release['values']['version'], release['values']['hash'] + if new_prefix == prefix: + release['pkgver'] = current if new_identity == identity else f"{prefix}.{int(counter) + 1}.{new_identity}" + order = vercmp(release["pkgver"], current) + if order < 0: + return {"status": "skipped", "current": current, "available": release["pkgver"], "reason": "upstream is older"} + if order == 0 and not watch.get("revision_variable"): + return {"status": "skipped", "current": current, "reason": "already current"} + release = resolve_release_fields(watch, release, fetch) + changed_variables = {k: v for k, v in release["variables"].items() if scalar(before, k) != v} + if order == 0 and not changed_variables: + return {"status": "skipped", "current": current, "reason": "already current"} + if order == 0 and changed_variables: + # Only a declared, forward-moving release revision can rebuild the same + # version. A changed hash/commit alone is an immutable-release violation. + revision_field = watch.get("revision_variable") + if revision_field not in changed_variables or vercmp(changed_variables[revision_field], scalar(before, revision_field, "0")) <= 0: + raise ValueError("release metadata changed without a newer version/revision") + new_pkgrel = "1" if order > 0 else bump_pkgrel(scalar(before, "pkgrel")) + text = replace_scalar(original, "pkgver", release["pkgver"]) + text = replace_scalar(text, "pkgrel", new_pkgrel) + for name, value in release["variables"].items(): + text = replace_scalar(text, name, value) + if check: + return {"status": "available", "current": current, "release": release} + scratch = path.with_name("PKGBUILD.sync-upstream") + try: + scratch.write_text(text) + after = read_recipe(scratch) + if scalar(after, "pkgver") != release["pkgver"] or scalar(after, "pkgrel") != new_pkgrel: + raise ValueError("recipe did not retain the release version") + if vercmp(complete_version(after), complete_version(before)) <= 0: + raise ValueError("complete package version must increase") + if before["arch"] != after["arch"]: + raise ValueError("release changed supported architectures") + arrays = updated_checksums(before, after, package, fetch, release, watch) + for name, values in arrays.items(): + text = replace_array(text, name, values) + scratch.write_text(text) + subprocess.run(["bash", "-n", str(scratch)], check=True) + for arch in before["arch"]: + result = read_recipe(scratch, "x86_64" if arch == "any" else arch) + if complete_version(result) != complete_version(after): + raise ValueError(f"{arch}: inconsistent release version") + for name, values in arrays.items(): + if result.get(name) != values: + raise ValueError(f"{arch}: rewritten {name} differs from the checked source hashes") + for name in after: + if name == "source" or name.startswith("source_"): + if result.get(name) != after[name]: + raise ValueError(f"{arch}: conditional {name} differs from the checked sources; use source_ arrays") + scratch.chmod(path.stat().st_mode) + scratch.replace(path) + return {"status": "updated", "before": complete_version(before), "after": complete_version(after)} + finally: + scratch.unlink(missing_ok=True) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("command", choices=["sync", "check", "validate"]) + parser.add_argument("package", type=Path) + parser.add_argument("--min-age", type=int, default=0) + args = parser.parse_args() + package = args.package.resolve() + if args.command == "validate": + validate(json.loads((package / ".omarchy/package.json").read_text())["upstream"]["watch"]) + return + with tempfile.TemporaryDirectory(prefix="upstream-watch-") as cache: + fetch = Fetcher(os.environ.get("UPSTREAM_CACHE_DIR", cache)) + print(json.dumps(sync(package, fetch, args.min_age, check=args.command == "check"))) + + +if __name__ == "__main__": + try: + main() + except (ValueError, KeyError, TypeError, IndexError, re.error, OSError, subprocess.CalledProcessError) as error: + print(f"upstream watch failed: {error}", file=sys.stderr) + sys.exit(1) diff --git a/pkgbuilds/1password-beta/.omarchy/package.json b/pkgbuilds/1password-beta/.omarchy/package.json index 327f9cf..61c481a 100644 --- a/pkgbuilds/1password-beta/.omarchy/package.json +++ b/pkgbuilds/1password-beta/.omarchy/package.json @@ -1,5 +1,19 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "a0f4262f94eb8a5b604146e71d42a3bb522feedf" + "upstream": { + "watch": { + "debian": "https://downloads.1password.com/linux/debian/amd64/dists/beta/main/binary-amd64/Packages", + "package": "1password", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)~(?P[0-9]+)\\.BETA", + "version": "{version}_{build}.BETA", + "variables": { + "_tarver": "{version}-{build}.BETA" + } + } + }, + "origin": { + "aur": "1password-beta", + "commit": "18d2de51dc01c9a58c1e8e96262f7afadcbf0648" + } } diff --git a/pkgbuilds/1password-beta/.omarchy/post-sync.sh b/pkgbuilds/1password-beta/.omarchy/post-sync.sh deleted file mode 100755 index 40bf26a..0000000 --- a/pkgbuilds/1password-beta/.omarchy/post-sync.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Keep the AUR x86_64 checksums and add aarch64 sources. The aarch64 -# artifacts are signed by 1Password's validpgpkeys, so we skip checksums there -# instead of baking version-specific hashes into Omarchy metadata. -set +u -CARCH=x86_64 source PKGBUILD -set -u - -if declare -p sha256sums >/dev/null 2>&1 && [[ ${#sha256sums[@]} -ge 2 ]]; then - x86_sums=("${sha256sums[@]}") -elif declare -p sha256sums_x86_64 >/dev/null 2>&1 && [[ ${#sha256sums_x86_64[@]} -ge 2 ]]; then - x86_sums=("${sha256sums_x86_64[@]}") -else - echo "Unable to read x86_64 checksums from PKGBUILD" >&2 - exit 1 -fi - -sha256_from_url() { - curl -fsSL "$1" | sha256sum | awk '{ print $1 }' -} - -arm_url="https://downloads.1password.com/linux/tar/beta/aarch64/1password-${_tarver}.arm64.tar.gz" -arm_tar_sum=$(sha256_from_url "$arm_url") -arm_sig_sum=$(sha256_from_url "$arm_url.sig") - -emit_archdir() { - cat <<'EOF' -case "${CARCH}" in - x86_64) - _archdir="x64" - ;; - aarch64) - _archdir="arm64" - ;; -esac -EOF -} - -emit_sources() { - cat <> "$tmpfile" - ;; - "arch=('x86_64')") - echo "arch=('x86_64' 'aarch64')" >> "$tmpfile" - ;; - source=\(*) - emit_sources >> "$tmpfile" - ;; - sha256sums=\(*) - [[ "$line" == *")" ]] || skip_checksums=true - ;; - *) - line=${line//1password-\$\{_tarver\}.x64/1password-\$\{_tarver\}.\$\{_archdir\}} - printf '%s\n' "$line" >> "$tmpfile" - ;; - esac -done < PKGBUILD - -mv "$tmpfile" PKGBUILD diff --git a/pkgbuilds/1password-beta/PKGBUILD b/pkgbuilds/1password-beta/PKGBUILD index 0357c40..f351980 100644 --- a/pkgbuilds/1password-beta/PKGBUILD +++ b/pkgbuilds/1password-beta/PKGBUILD @@ -1,6 +1,6 @@ pkgname=1password-beta -_tarver=8.12.34-29.BETA +_tarver=8.12.38-25.BETA case "${CARCH}" in x86_64) _archdir="x64" @@ -9,8 +9,8 @@ case "${CARCH}" in _archdir="arm64" ;; esac -pkgver=${_tarver//-/_} -pkgrel=29.1 +pkgver=8.12.38_25.BETA +pkgrel=25.2 conflicts=('1password' '1password-beta-bin') pkgdesc="Password manager and secure wallet" arch=('x86_64' 'aarch64') @@ -22,10 +22,10 @@ source=() sha256sums=() source_x86_64=(https://downloads.1password.com/linux/tar/beta/x86_64/1password-${_tarver}.x64.tar.gz{,.sig}) source_aarch64=(https://downloads.1password.com/linux/tar/beta/aarch64/1password-${_tarver}.arm64.tar.gz{,.sig}) -sha256sums_x86_64=('6894b283a534cf94b07903fb38966a0aab2e37f75ee3848ce340308819aadddb' - '8d4df4d0a80d2be7aad7d91ad964a750c8f32db5007261045003c191690c8246') -sha256sums_aarch64=('c77ce6ddf36dbd6054c64d91b7f644274d3218f465fd60e211d0296f6443124a' - '91c7249a1cf5e7924ef2810cb0cb1b893d8a9a424b373b433f45d7aaa5a8369b') +sha256sums_x86_64=('c6d302a2c7404a7ded34a3c4f1c401a43eafeed8b147d128dcb416284c2c2b71' + 'cc0f00054749c32d77fba31a12a8dece812e409f4b9d81850d2f9b50fab55dca') +sha256sums_aarch64=('1fd62cd0df90098dd5e50d22e9a6c0a5221f9db6355b7c848db7af7076b395fd' + '4d273b71ab987dcadad9e4fa7cfac7e0173dc4dbe7908c9a3e76af274313dd76') validpgpkeys=('3FEF9748469ADBE15DA7CA80AC2D62742012EA22') package() { @@ -42,7 +42,7 @@ package() { "${pkgdir}/usr/share/icons/hicolor/${resolution}/apps/1password.png" done # Install desktop file - install -Dm0644 resources/1password.desktop -t "${pkgdir}"/usr/share/applications/ + install -Dm0644 resources/com.onepassword.OnePassword.desktop -t "${pkgdir}"/usr/share/applications/ # Fill in policy kit file with a list of (the first 10) human users of the system. export POLICY_OWNERS @@ -65,8 +65,7 @@ EOF" > ./com.1password.1Password.policy # Cleanup un-needed files rm "${pkgdir}"/opt/1Password/com.1password.1Password.policy "${pkgdir}"/opt/1Password/com.1password.1Password.policy.tpl "${pkgdir}"/opt/1Password/install_biometrics_policy.sh rm -r "${pkgdir}"/opt/1Password/resources/icons/ - rm "${pkgdir}"/opt/1Password/resources/1password.desktop "${pkgdir}"/opt/1Password/resources/custom_allowed_browsers - + rm "${pkgdir}"/opt/1Password/resources/com.onepassword.OnePassword.desktop "${pkgdir}"/opt/1Password/resources/custom_allowed_browsers # Symlink /usr/bin executable to opt install -dm0755 "${pkgdir}"/usr/bin ln -s /opt/1Password/1password "${pkgdir}"/usr/bin/1password diff --git a/pkgbuilds/1password-cli/.omarchy/package.json b/pkgbuilds/1password-cli/.omarchy/package.json index f879ccc..70b0c87 100644 --- a/pkgbuilds/1password-cli/.omarchy/package.json +++ b/pkgbuilds/1password-cli/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "b0d208821677a5dbb883a8b92f06a5c92b9e861a" + "upstream": { + "watch": { + "json": "https://app-updates.agilebits.com/check/1/0/CLI2/en/0", + "path": "version" + } + }, + "origin": { + "aur": "1password-cli", + "commit": "b0d208821677a5dbb883a8b92f06a5c92b9e861a" + } } diff --git a/pkgbuilds/1password/PKGBUILD b/pkgbuilds/1password/PKGBUILD index 2479352..03d87e7 100644 --- a/pkgbuilds/1password/PKGBUILD +++ b/pkgbuilds/1password/PKGBUILD @@ -1,6 +1,6 @@ pkgname=1password -pkgver=8.12.34 -pkgrel=36 +pkgver=8.12.36 +pkgrel=2 conflicts=('1password-beta' '1password-beta-bin') pkgdesc="Password manager and secure wallet" arch=('x86_64' 'aarch64') @@ -16,11 +16,13 @@ source_aarch64=( "https://downloads.1password.com/linux/tar/stable/aarch64/1password-${pkgver}.arm64.tar.gz" "https://downloads.1password.com/linux/tar/stable/aarch64/1password-${pkgver}.arm64.tar.gz.sig" ) -sha256sums_x86_64=('297784aa66770b645607a7f04c9ba2c4aebed4f46d21202487f521ba572b7b13' - 'ec085bef60de748895d3c51a8208301ba2ac8fb47db99334539ba4bd1d3260d7' +sha256sums_x86_64=( + '393c93c8025fee5dda76a4d0f1e478e98526cc946e58a22efe26f540ea2b5729' + '251177694bfdc63c431021e2bd2ed64f241b8a0247d5dd1c46d4d2dadffe7b97' ) -sha256sums_aarch64=('ea5102363d6cf3442b96a7abd6743da8c1d261f56a628e1a3c183d84fa65fdcb' - 'f44db73fa44c3f68c3ab78a9cce140be9355de1dd9be4ce731c9d6597960c907' +sha256sums_aarch64=( + '4b58851b3bf52a7bbc79243fc6b9cba9591cabe8b7eb2d0e271593bad2192af9' + 'b5ba754ec22f9ecd980986582cb3ea0c6af45b276bfe8897724bf4abfa17e27f' ) validpgpkeys=('3FEF9748469ADBE15DA7CA80AC2D62742012EA22') @@ -49,7 +51,8 @@ package() { "${pkgdir}/usr/share/icons/hicolor/${resolution}/apps/1password.png" done # Install desktop file - install -Dm0644 resources/1password.desktop -t "${pkgdir}"/usr/share/applications/ + install -Dm0644 resources/com.onepassword.OnePassword.desktop \ + "${pkgdir}/usr/share/applications/1password.desktop" # 1Password reads the display scale itself, the way Electron apps do, and # comes up oversized next to every other window on a scaled monitor. Pin it @@ -78,7 +81,7 @@ EOF" > ./com.1password.1Password.policy # Cleanup un-needed files rm "${pkgdir}"/opt/1Password/com.1password.1Password.policy "${pkgdir}"/opt/1Password/com.1password.1Password.policy.tpl "${pkgdir}"/opt/1Password/install_biometrics_policy.sh rm -r "${pkgdir}"/opt/1Password/resources/icons/ - rm "${pkgdir}"/opt/1Password/resources/1password.desktop "${pkgdir}"/opt/1Password/resources/custom_allowed_browsers + rm "${pkgdir}"/opt/1Password/resources/com.onepassword.OnePassword.desktop "${pkgdir}"/opt/1Password/resources/custom_allowed_browsers # Symlink /usr/bin executable to opt install -dm0755 "${pkgdir}"/usr/bin diff --git a/pkgbuilds/aether/.omarchy/package.json b/pkgbuilds/aether/.omarchy/package.json index 153a088..3664d9b 100644 --- a/pkgbuilds/aether/.omarchy/package.json +++ b/pkgbuilds/aether/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "0f047f40a200121075ca3cedce59ddf226f2a0f1" + "upstream": { + "watch": { + "github": "omacom/aether", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "aether", + "commit": "0f047f40a200121075ca3cedce59ddf226f2a0f1" + } } diff --git a/pkgbuilds/asusctl/.omarchy/package.json b/pkgbuilds/asusctl/.omarchy/package.json index 1b2f22a..19004c8 100644 --- a/pkgbuilds/asusctl/.omarchy/package.json +++ b/pkgbuilds/asusctl/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "b0ec6ca495eb331a684db91b0fe12085a868b632" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://github.com/OpenGamingCollective/asusctl.git", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "asusctl", + "commit": "b0ec6ca495eb331a684db91b0fe12085a868b632" + } } diff --git a/pkgbuilds/asusctl/PKGBUILD b/pkgbuilds/asusctl/PKGBUILD index 470e7e6..97e8b8a 100644 --- a/pkgbuilds/asusctl/PKGBUILD +++ b/pkgbuilds/asusctl/PKGBUILD @@ -4,7 +4,7 @@ pkgbase=asusctl pkgname=(asusctl rog-control-center) -pkgver=6.4.0 +pkgver=6.5.0 pkgrel=1 pkgdesc="Daemon and tools to control your ASUS ROG laptop" arch=('x86_64') @@ -12,7 +12,7 @@ url="https://asus-linux.org" license=('MPL-2.0') makedepends=('cargo' 'fontconfig') source=("${pkgbase}-${pkgver}.tar.gz::https://github.com/OpenGamingCollective/asusctl/archive/${pkgver}.tar.gz") -b2sums=('e90074e904f364386ad661784bd9fc2e929e83ea06ac62fd1d34eb03490cefe8aae3bed2722162f1e8ea5d69248138cb5fd9f90c3a18443d1d8c04cf732b928a') +b2sums=('4179e08a60f9480b62e41d84faade1f46407140a213ca4d60c8699837a2486bda8e66b4ca43fa06149667d02e3d060c3efd792348f9a93a675f762d6ea2d05f8') prepare() { cd "${pkgbase}-${pkgver}" diff --git a/pkgbuilds/basecamp-cli/.omarchy/package.json b/pkgbuilds/basecamp-cli/.omarchy/package.json index 0beb986..37b7a70 100644 --- a/pkgbuilds/basecamp-cli/.omarchy/package.json +++ b/pkgbuilds/basecamp-cli/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "1e7bd48ef192b3d6a2854916e75e14ea43f6f0b7" + "upstream": { + "watch": { + "github": "basecamp/basecamp-cli", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "basecamp-cli", + "commit": "89c4eb5a0ac9ffe98aecd4ea8b789cf1fee42bf1" + } } diff --git a/pkgbuilds/basecamp-cli/PKGBUILD b/pkgbuilds/basecamp-cli/PKGBUILD index b7ed938..b0bd117 100644 --- a/pkgbuilds/basecamp-cli/PKGBUILD +++ b/pkgbuilds/basecamp-cli/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Basecamp pkgname=basecamp-cli -pkgver=0.10.0 -pkgrel=1 +pkgver=0.11.0 +pkgrel=2 pkgdesc="CLI for Basecamp project management" arch=('x86_64' 'aarch64') url="https://github.com/basecamp/basecamp-cli" @@ -13,10 +13,10 @@ optdepends=( 'zsh: for zsh shell completions' 'fish: for fish shell completions' ) -source_x86_64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.10.0/basecamp_${pkgver}_linux_amd64.tar.gz") -source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.10.0/basecamp_${pkgver}_linux_arm64.tar.gz") -sha256sums_x86_64=('ca8b3a085f90323c8ab1694b83ff624cf10c459618ca6010ce204534f3f01987') -sha256sums_aarch64=('dd1b5db88b9b309e95ae1ec0d642d23f4955a3abe159bffe00796a0ef6030c1d') +source_x86_64=("https://github.com/basecamp/basecamp-cli/releases/download/v${pkgver}/basecamp_${pkgver}_linux_amd64.tar.gz") +source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v${pkgver}/basecamp_${pkgver}_linux_arm64.tar.gz") +sha256sums_x86_64=('425ffab1251c4315c5f731f8367c3c8c37b54b6b1050eafdbe369e11e1a1ce51') +sha256sums_aarch64=('9c433b12a704402a98b238abb3128a0844a0bc682064adb260b11bc228b3595e') package() { install -Dm755 "basecamp" "${pkgdir}/usr/bin/basecamp" diff --git a/pkgbuilds/bun-bin/.omarchy/package.json b/pkgbuilds/bun-bin/.omarchy/package.json index dd5909b..ddb8163 100644 --- a/pkgbuilds/bun-bin/.omarchy/package.json +++ b/pkgbuilds/bun-bin/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "195b828d52ce9a181215f753927123e7ef2af252" + "upstream": { + "watch": { + "github": "oven-sh/bun", + "pattern": "bun-v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "bun-bin", + "commit": "195b828d52ce9a181215f753927123e7ef2af252" + } } diff --git a/pkgbuilds/bun-bin/PKGBUILD b/pkgbuilds/bun-bin/PKGBUILD index 77c3f3e..263148a 100644 --- a/pkgbuilds/bun-bin/PKGBUILD +++ b/pkgbuilds/bun-bin/PKGBUILD @@ -3,7 +3,7 @@ # Contributor: 37h4n (aarch64 support added by Ethan Reece ) # Contributor: sh!zeeg (shizeeque@gmail.com) support for non-avx2 CPUs, shell completions. pkgname=bun-bin -pkgver=1.3.11 +pkgver=1.4.2 pkgrel=1 pkgdesc="All-in-one JavaScript runtime built for speed, with bundler, transpiler, test runner, and package manager. Includes bunx, shell completions and support for baseline CPUs" arch=('x86_64' 'aarch64') @@ -12,11 +12,8 @@ license=('MIT') provides=('bun') conflicts=('bun') options=('!debug') -sha256sums_x86_64=('8611ba935af886f05a6f38740a15160326c15e5d5d07adef966130b4493607ed' - 'abe346f63414547cdf6b35b7a649a490c728b93d006226156923918a84c0e59b' - '9b296bcc20090b5ea079dc1bca15913a32246121169bcf54cbdb7384d6f5b32b') -sha256sums_aarch64=('d13944da12a53ecc74bf6a720bd1d04c4555c038dfe422365356a7be47691fdf' - '9b296bcc20090b5ea079dc1bca15913a32246121169bcf54cbdb7384d6f5b32b') +sha256sums_x86_64=('36368faef7527875d5ffa52e53cd48021741f2a83eb6208a8dd64068d422a913' 'c678040f14fe0440eb839d37cbd0ce4c051a32da72806ac97de6a6aab6bf728f' '9b296bcc20090b5ea079dc1bca15913a32246121169bcf54cbdb7384d6f5b32b') +sha256sums_aarch64=('54328bbc2d9c8e0c9f892c544d66c57a83b84139e34909e5ee81758f1ac8fda7' '9b296bcc20090b5ea079dc1bca15913a32246121169bcf54cbdb7384d6f5b32b') source_x86_64=( "bun-x64.zip::https://github.com/oven-sh/bun/releases/download/bun-v${pkgver}/bun-linux-x64.zip" "bun-x64-baseline.zip::https://github.com/oven-sh/bun/releases/download/bun-v${pkgver}/bun-linux-x64-baseline.zip" diff --git a/pkgbuilds/claude-code/.omarchy/package.json b/pkgbuilds/claude-code/.omarchy/package.json index 5ce322c..7a2b0a1 100644 --- a/pkgbuilds/claude-code/.omarchy/package.json +++ b/pkgbuilds/claude-code/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "87aa5d5f2771c83dae737fe1bcfbc129110291fb" + "upstream": { + "watch": { + "regex": "https://downloads.claude.ai/claude-code-releases/latest", + "pattern": "^(?P[0-9]+(?:\\.[0-9]+)*)\\s*$" + } + }, + "origin": { + "aur": "claude-code", + "commit": "27f61daa48ebdca87c9b2c7c83c162100d233ccc" + } } diff --git a/pkgbuilds/claude-code/PKGBUILD b/pkgbuilds/claude-code/PKGBUILD index ca3c84a..4004045 100644 --- a/pkgbuilds/claude-code/PKGBUILD +++ b/pkgbuilds/claude-code/PKGBUILD @@ -4,7 +4,7 @@ # Automation repository: https://github.com/fabifont/claude-code-aur pkgname=claude-code -pkgver=2.1.263 +pkgver=2.1.273 pkgrel=1 pkgdesc="An agentic coding tool that lives in your terminal" arch=('x86_64' 'aarch64') @@ -29,8 +29,8 @@ source_x86_64=("claude-${pkgver}-x86_64::https://downloads.claude.ai/claude-code source_aarch64=("claude-${pkgver}-aarch64::https://downloads.claude.ai/claude-code-releases/${pkgver}/linux-arm64/claude") sha256sums=('SKIP') -sha256sums_x86_64=('26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba') -sha256sums_aarch64=('7d25d7c8ae6c6e009cc7dae4e817f674179fd31fb7761bcd56fee4c2902b4c03') +sha256sums_x86_64=('6c752e2cc7c110c9df15f26d8d134d438c5ae95dbd610efc1a308bf7f9c5f6c1') +sha256sums_aarch64=('103cfab4d6ae898b6af692336fb662ffcc607075cc6408892bd350b3f549ebee') package() { install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude" diff --git a/pkgbuilds/claude-desktop/.omarchy/package.json b/pkgbuilds/claude-desktop/.omarchy/package.json new file mode 100644 index 0000000..c089113 --- /dev/null +++ b/pkgbuilds/claude-desktop/.omarchy/package.json @@ -0,0 +1,12 @@ +{ + "source": "local", + "release_ring": "fast", + "upstream": { + "debian": "https://downloads.claude.ai/claude-desktop/apt/stable/dists/stable/main/binary-amd64/Packages", + "package": "claude-desktop", + "sources": { + "x86_64": ["https://downloads.claude.ai/claude-desktop/apt/stable/pool/main/c/claude-desktop/claude-desktop_{pkgver}_amd64.deb"], + "aarch64": ["https://downloads.claude.ai/claude-desktop/apt/stable/pool/main/c/claude-desktop/claude-desktop_{pkgver}_arm64.deb"] + } + } +} diff --git a/pkgbuilds/claude-desktop/PKGBUILD b/pkgbuilds/claude-desktop/PKGBUILD new file mode 100644 index 0000000..308d94a --- /dev/null +++ b/pkgbuilds/claude-desktop/PKGBUILD @@ -0,0 +1,99 @@ +# Maintainer: Spencer Bull + +# Omarchy tracks Anthropic's own Debian repository, the only channel the +# Linux beta ships through. The declarative "debian" upstream provider in +# .omarchy/package.json rewrites the version and checksums below from that +# repository's package index. + +pkgname=claude-desktop +pkgver=2.110.0 +pkgrel=1 +pkgdesc="Official Claude desktop app with Claude Code" +arch=('x86_64' 'aarch64') +url="https://claude.ai" +license=('custom') + +depends=( + 'alsa-lib' + 'at-spi2-core' + 'bash' + 'gcc-libs' + 'glibc' + 'gtk3' + 'libdrm' + 'libnotify' + 'libsecret' + 'libxcb' + 'libxtst' + 'mesa' + 'nss' + 'util-linux-libs' + 'xdg-desktop-portal' + 'xdg-utils' +) + +optdepends=( + 'gnome-keyring: store credentials in a system keyring' + 'bubblewrap: Claude Code sandboxing' + 'socat: Claude Code sandboxing' +) +# Cowork is x86_64-only here: Arch Linux ARM ships no UEFI firmware package, +# so on aarch64 the app's /usr/share/AAVMF probe has nothing to resolve to. +optdepends_x86_64=( + 'qemu-system-x86: run Cowork tasks in a local virtual machine' + 'edk2-ovmf: UEFI firmware for the Cowork virtual machine' + 'virtiofsd: file sharing with the Cowork virtual machine' +) + +makedepends=('libarchive') +options=('!debug' '!strip') + +# Omarchy: same treatment as openai-codex-desktop. The build image compresses +# with zstd at its default level, which leaves this 560 MB Electron tree at +# 210 MB. Maximum zstd takes it to 160 MB for ~3 extra minutes of build time +# -- worth it for a package every user re-downloads on each of Anthropic's +# frequent releases. +COMPRESSZST=(zstd -c -z -q --ultra -22 --threads=0 -) + +_deb_x86_64="claude-desktop_${pkgver}_amd64.deb" +_deb_aarch64="claude-desktop_${pkgver}_arm64.deb" +source=('claude-desktop-launcher.sh') +_pool="https://downloads.claude.ai/claude-desktop/apt/stable/pool/main/c/claude-desktop" +source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64}") +source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64}") +noextract=("${_deb_x86_64}" "${_deb_aarch64}") +sha256sums=('edfdbc63b65891ef7c481b07086c7e630fc102c042b6ed65331a52fcaf72b14a') +sha256sums_x86_64=('f44cb8b52f6e9171ac2e67cbcc8070c4974a2f0a9b9f141b430b32b4ff541109') +sha256sums_aarch64=('de9f24034f33dcadc53bedee92da6604e2c9268497ebff5447fedc092c884e71') + +package() { + cd "${srcdir}" + + local deb_var="_deb_${CARCH}" + local deb="${!deb_var}" + + bsdtar -xOf "${deb}" data.tar.xz | + bsdtar --no-same-owner -xf - -C "${pkgdir}" + + # Upstream's /usr/bin/claude-desktop is a bare symlink to the Electron + # binary; replace it with a launcher that picks the right Ozone platform. + rm "${pkgdir}/usr/bin/claude-desktop" + install -Dm755 claude-desktop-launcher.sh "${pkgdir}/usr/bin/claude-desktop" + + install -Dm644 "${pkgdir}/usr/share/doc/claude-desktop/copyright" \ + "${pkgdir}/usr/share/licenses/${pkgname}/copyright" + + # Cowork probes Debian's paths for its VM stack; map them onto Arch's. + # The links dangle harmlessly until the matching optdepends are installed. + if [[ "${CARCH}" == x86_64 ]]; then + install -d "${pkgdir}/usr/libexec" + ln -s ../lib/virtiofsd "${pkgdir}/usr/libexec/virtiofsd" + # The app derives the VARS path from the CODE path, so both need a link. + install -d "${pkgdir}/usr/share/edk2" + ln -s x64/OVMF_CODE.4m.fd "${pkgdir}/usr/share/edk2/OVMF_CODE_4M.fd" + ln -s x64/OVMF_VARS.4m.fd "${pkgdir}/usr/share/edk2/OVMF_VARS_4M.fd" + fi + + # Debian package-policy files are not used on Arch Linux. + rm -rf "${pkgdir}/usr/share/doc" "${pkgdir}/usr/share/lintian" +} diff --git a/pkgbuilds/claude-desktop/claude-desktop-launcher.sh b/pkgbuilds/claude-desktop/claude-desktop-launcher.sh new file mode 100644 index 0000000..b6db841 --- /dev/null +++ b/pkgbuilds/claude-desktop/claude-desktop-launcher.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -euo pipefail + +user_flags=() +config_home="${XDG_CONFIG_HOME:-}" +[[ -n "$config_home" || -z "${HOME:-}" ]] || config_home="$HOME/.config" +flags_file="${config_home:+$config_home/claude-desktop-flags.conf}" + +if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then + while IFS= read -r line || [[ -n "$line" ]]; do + line="${line%%#*}" + [[ -n "${line//[[:space:]]/}" ]] || continue + read -r -a flags <<<"$line" + user_flags+=("${flags[@]}") + done <"$flags_file" +fi + +# Chromium's own Ozone detection falls back to XWayland often enough to matter, +# and the result is a blurry window on every scaled display. Ask for Wayland +# directly, unless the user has already picked a platform themselves. +platform_flags=() +if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then + platform_flags=(--ozone-platform=wayland) + + for flag in "${user_flags[@]}" "$@"; do + case "$flag" in + --ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;; + esac + done +fi + +exec /usr/lib/claude-desktop/claude-desktop "${platform_flags[@]}" "${user_flags[@]}" "$@" diff --git a/pkgbuilds/cliamp/.omarchy/package.json b/pkgbuilds/cliamp/.omarchy/package.json index 78c2988..94f9d65 100644 --- a/pkgbuilds/cliamp/.omarchy/package.json +++ b/pkgbuilds/cliamp/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "0e012cbfe1ea6cbbf15e2ada9cf93cca7aaa9ca0" + "source": "local", + "upstream": { + "watch": { + "github": "bjarneo/cliamp", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "cliamp", + "commit": "92fb021a1c78f75043cbbd402aca8b515af069ec" + } } diff --git a/pkgbuilds/cliamp/PKGBUILD b/pkgbuilds/cliamp/PKGBUILD index bc972e0..84d4644 100644 --- a/pkgbuilds/cliamp/PKGBUILD +++ b/pkgbuilds/cliamp/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: bjarneo pkgname=cliamp -pkgver=2.0.1 +pkgver=2.2.0 pkgrel=1 pkgdesc='A retro terminal music player inspired by Winamp 2.x' arch=('x86_64' 'aarch64') @@ -11,7 +11,7 @@ optdepends=('pipewire-alsa: audio output on PipeWire systems' 'pulseaudio-alsa: audio output on PulseAudio systems') makedepends=('go') source=("${pkgname}-${pkgver}.tar.gz::https://github.com/bjarneo/cliamp/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('2c5885665dba5ed2e8dc156bce64751199a92efed7f63959c65e985759b73732') +sha256sums=('54ffbba6983880c915d2c13c83ca1339de2d2a3c5af3bb0a176923faa9afc015') build() { cd "${pkgname}-${pkgver}" diff --git a/pkgbuilds/crush-bin/.omarchy/package.json b/pkgbuilds/crush-bin/.omarchy/package.json index fa3a56f..857d5c1 100644 --- a/pkgbuilds/crush-bin/.omarchy/package.json +++ b/pkgbuilds/crush-bin/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "1a56795cad77f703b8d0e5edefcd30926c6091c6" + "upstream": { + "watch": { + "github": "charmbracelet/crush", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "crush-bin", + "commit": "9c63847ccd3650646141f84384545c8fe2d68bb7" + } } diff --git a/pkgbuilds/crush-bin/PKGBUILD b/pkgbuilds/crush-bin/PKGBUILD index df1b0b6..564caf8 100644 --- a/pkgbuilds/crush-bin/PKGBUILD +++ b/pkgbuilds/crush-bin/PKGBUILD @@ -3,7 +3,7 @@ # Maintainer: caarlos0 pkgname='crush-bin' -pkgver=0.92.0 +pkgver=0.95.0 pkgrel=1 pkgdesc='A powerful terminal-based AI assistant for developers, providing intelligent coding assistance directly in your terminal.' url='https://charm.sh/crush' @@ -13,16 +13,16 @@ provides=('crush') conflicts=('crush') source_aarch64=("${pkgname}_${pkgver}_aarch64.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_arm64.tar.gz") -sha256sums_aarch64=('1b6f0384297a1e77d01df41db1f56fc0872f1e9c9d99634c4b2c208b7bb5d935') +sha256sums_aarch64=('b42291307abe5572afb972fba9b8780f63a2058f37fb0dc61ab4c7b435314a8a') source_armv7h=("${pkgname}_${pkgver}_armv7h.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_armv7.tar.gz") -sha256sums_armv7h=('dac6c6d57e3ffc84d24883ce63b84e9a940ca243d9ea6954a732f0f7da862a2a') +sha256sums_armv7h=('756363804b6475ab6bf811f175a93766f47372beddb6e4a7b6e365ecba3f90ae') source_i686=("${pkgname}_${pkgver}_i686.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_i386.tar.gz") -sha256sums_i686=('29f2586dfc6b065a30d5f740ff2ca1ed03fbccdcd84a8bf826f1882f0f3660a1') +sha256sums_i686=('ce7b0dec1f1d9aa5a6e339f04e835bc6b3a335caf816dcb6c83a2f808d9fcd3b') source_x86_64=("${pkgname}_${pkgver}_x86_64.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_x86_64.tar.gz") -sha256sums_x86_64=('7d9aca9ab7808ee828a0bb0fde01d96fc480ce0a66a5ff617aa27ac09e23cbd5') +sha256sums_x86_64=('edef832ff1fc03e420a0410b9653547feb816bda7e0458589434ea4164210f5e') package() { case "$CARCH" in diff --git a/pkgbuilds/cua-driver-bin/PKGBUILD b/pkgbuilds/cua-driver-bin/PKGBUILD index 9e5bbe9..07ce954 100644 --- a/pkgbuilds/cua-driver-bin/PKGBUILD +++ b/pkgbuilds/cua-driver-bin/PKGBUILD @@ -11,14 +11,14 @@ # The binary carries its own updater: `cua-driver update --apply` pipes the # vendor installer into bash, which would install a second copy under # ~/.cua-driver and link it into ~/.local/bin, stepping around pacman and -# this repository's release gate. Upstream offers no switch for that path, -# and a /usr/bin wrapper would not cover it either, since the MCP -# configurations the binary generates record the resolved executable. So -# prepare() rewrites the installer URL inside the binary to point at pm.sh, -# a stand-in that declines and names pacman instead. +# this repository's release gate. Driver 0.27.0 detects pacman ownership, but +# deliberately treats failed and timed-out ownership queries as unmanaged. +# A /usr/bin wrapper would not cover MCP configurations that record the +# resolved executable, so prepare() rewrites the installer URL inside the +# binary to point at pm.sh, a stand-in that declines and names pacman instead. pkgname=cua-driver-bin -pkgver=0.24.0 +pkgver=0.28.1 pkgrel=1 pkgdesc="Computer-use driver for native GUI apps: accessibility-tree snapshots and input injection" arch=('x86_64' 'aarch64') @@ -46,8 +46,8 @@ source_x86_64=("https://github.com/trycua/cua/releases/download/cua-driver-rs-v$ source_aarch64=("https://github.com/trycua/cua/releases/download/cua-driver-rs-v${pkgver}/cua-driver-rs-${pkgver}-linux-arm64.tar.gz") sha256sums=('c0779290c1d4783169aa3dbfb55feb505e563ef8a004bbf55298ceffcfbda8d9' 'c76e251c3ed424200eac52bec35ba534336307fabd83a175ab0b47e2084ab0d8') -sha256sums_x86_64=('e313e4072bde730f16466b90388c7602954ff25714bf73f701d7218eeb7747d2') -sha256sums_aarch64=('6d7969715e6be6e1d635fc0017040825d132142c8503130b1ce08fb6ee71c8c9') +sha256sums_x86_64=('a068b6e477893b77ced74bceccf7db7483cf140e8d54150ce5849b6252b90bcf') +sha256sums_aarch64=('a863951ef0699fd25091adb87bd114d69709b887fdfc059e795aca49ef8ac19c') case "${CARCH}" in x86_64) _platform="linux-x86_64" ;; @@ -56,7 +56,7 @@ esac _vendor_tree="cua-driver-rs-${pkgver}-${_platform}" # Rust strings carry their length out of band, so the replacement has to be -# exactly as long as the original: 32 bytes, which is what fixes the +# exactly as long as the original, which is what fixes the # stand-in's short name and location. _vendor_installer='https://cua.ai/driver/install.sh' _pacman_installer='file:///usr/lib/cua-driver/pm.sh' @@ -69,14 +69,14 @@ prepare() { return 1 fi - # The URL appears twice: once in the updater and once in the printed - # reinstall one-liner. Any other count means upstream moved the updater - # and this rewrite needs another look, so the build stops rather than - # shipping a live self-updater. - local found + # In 0.28.1 the URL appears in the updater, the printed reinstall command, + # and two embedded copies of Skills/cua-driver/README.md. Rewrite all four + # so the embedded instructions also defer to pacman. Any other count means + # the release layout changed and needs review before packaging. + local expected=4 found found=$(grep -obUaF "${_vendor_installer}" cua-driver | wc -l) - if (( found != 2 )); then - echo "expected the vendor installer URL twice in cua-driver, found ${found}" >&2 + if (( found != expected )); then + echo "expected the vendor installer URL ${expected} times in cua-driver, found ${found}" >&2 return 1 fi @@ -87,7 +87,7 @@ prepare() { if (( size_before != size_after )) \ || grep -qUaF "${_vendor_installer}" cua-driver \ - || (( $(grep -obUaF "${_pacman_installer}" cua-driver | wc -l) != 2 )); then + || (( $(grep -obUaF "${_pacman_installer}" cua-driver | wc -l) != expected )); then echo "installer URL rewrite did not land cleanly in cua-driver" >&2 return 1 fi diff --git a/pkgbuilds/cua-hyprland-plugin/.omarchy/package.json b/pkgbuilds/cua-hyprland-plugin/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/cua-hyprland-plugin/DOWNSTREAM-PROVENANCE.json b/pkgbuilds/cua-hyprland-plugin/DOWNSTREAM-PROVENANCE.json new file mode 100644 index 0000000..7cf8f23 --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/DOWNSTREAM-PROVENANCE.json @@ -0,0 +1,57 @@ +{ + "files": { + "CMakeLists.txt": "7e874a595e1abd708cb0626bd9f0f58d79b4b4bbc6d99b45a0cbe1c5c53b43bc", + "LICENSE.md": "c0779290c1d4783169aa3dbfb55feb505e563ef8a004bbf55298ceffcfbda8d9", + "SOURCE-PROVENANCE.json": "54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75", + "cmake/DetectHyprlandAPI.cmake": "216133ec0eb141c3696bf3770a23e63e46521c9e91a0245a7cb75c20a75ba2c5", + "cmake/VerifyRuntime.cmake": "5869f79a418e7aa6178d2b9166c36cd01c3093c2579b647624968244db57b761", + "include/cua_hyprland/protocol.hpp": "7051463b3c61a2dc93c388e66b6136b7bb524f8694350f249f9d2b5c55826493", + "include/cua_hyprland/session.hpp": "e6a968e4f2ac28122cb7413a0e318f6222d2a0a1b7b0c45f4ab419a78639ebea", + "include/cua_hyprland/status.hpp": "56a9656647c0f4eeb0c588cd4b98a77df198d1f90421f973e9a80e6802f61495", + "src/drag_geometry.hpp": "c5b783d15ff197f22938f08f8d176bab5d45544fe989d150f15cb99295acedb8", + "src/foreground_route.hpp": "4aa016c237b33c15e352a9f5f64bbbca7e1a9c1671ffd593a95a0d87994fb519", + "src/inject_server.cpp": "0935283580c50fcf0e4ad956f858885700536002b1d86d2f078da9c4404ee9e8", + "src/inject_server.hpp": "67de008b4d6983371207bb22a57bab154b1dedda9b38d1207d3ac8cb379382eb", + "src/input_client_deadline.hpp": "00a91a789ff698820607449ff7152e2fb50d0f315e0fa9c5c3855752bfffe2ef", + "src/input_experiment.cpp": "7017748c782b64b0bc1d257f4ade1a8d46fd19ce940ecc21f22d628614fcef37", + "src/input_experiment.hpp": "9da2ddab7f0de6e9cf02aea53e9119acef17ef8513af849bb5aa3d137ebd12c3", + "src/input_grant.hpp": "90b544b2f559bacd920b85ab5915f09ff201de3c05a052ce1b71ff71f767e6a6", + "src/keyboard_layout.hpp": "bc2ec039ac1974caebbb66fe4acb7a2a81832c9054b3aa644a9adcc8954a2467", + "src/owned_socket_path.hpp": "8e784656d944c700f3ded383c93a8cadf846dabdaa4cc12673bcb637c26d9fd1", + "src/passive_pointer_target.hpp": "ede36fd9fd6e95ae5923c751f12591084392be9d2270eb06ad64eb4f245169fa", + "src/plugin.cpp": "712fd73ef8e9046e0fd91531b7bf5da50ce37ccca9df174160137e1924a74b09", + "src/primary_trace.cpp": "e9468d1a3f3be2a90d47bf8c4a638ad8a60fe10b6297582ab7825751cb707aa9", + "src/primary_trace.hpp": "8d62535fb0b24a02bb80d9a8dcd540b39204afb2f3b4bcb5cabd5275c3b5eaa7", + "src/protocol.cpp": "bd083d65efb05e80946566dc535b1a6fadaa581c66ec327eff41796feba795b1", + "src/seat_lifetime.hpp": "386cf5c72c178f8eec0824f2a7d46fa755b0bb000861f9a6e00802f6b81fb779", + "src/session.cpp": "0105c7ba5f9e2dbdd9a21f48be0bc1f2bde930f6aa19f77e2216403d7790e4df", + "src/status.cpp": "e46e81e5e8ae3b1f50af5dcaa6c1776e236321c788fe61b402c9923c797b1270", + "tests/agent_keymap_test.py": "9b112f520a97a77a0d55f1009cd2594988d3c1e8e7bcaa39f213ffad2644dc56", + "tests/cmake-api/CMakeLists.txt": "6a66c8f98023f029998af917fcd3ec6b1388607bdf7acaf5bdfaef9141962685", + "tests/cmake-api/include/src/plugins/PluginAPI.hpp": "86c8ad51e668908d18928cef1b04e8e6d32a33894525640b52f0b31769c870a0", + "tests/desktop_fault_policy_fixture.cpp": "ac24d675ebc64cc98148e852eb5aba5858bffc06332678d14276d04b317aaf15", + "tests/desktop_fault_policy_test.py": "c3f624c0239036babd23eaaf1bb6b722f3a0c3321a5d3d6668ae7ab4910ca95c", + "tests/drag_geometry_test.cpp": "d32ea649d008fc051fe18555d6fbc54ba5d057b61880b648c5df8aa076a53fbc", + "tests/foreground_modifiers_test.py": "abf0ddde2d51c6639c8bdca8fdda51cdc8f922b57575a00fc8925c683ccc3bb2", + "tests/foreground_route_test.cpp": "1024168828b13ee6abd8242941e73c042e9381b39108e3ada74823039c7e7932", + "tests/input_client_deadline_test.cpp": "d62373a7815d531f1269c9a838773595f43e8bcef6482fa140edb162e59a6cac", + "tests/input_grant_test.cpp": "1f327b7ee678189ebad6a50bb1b9bd06767521cebc9cfb478d92de4a8bf7e7fe", + "tests/keyboard_layout_test.cpp": "3bb0fade4675d7ad92a81eb4a1c5201dd1d01bcc418b7ea59a4284dc235e5fb2", + "tests/mock-hyprland/mock.hpp": "3aeb1a4b9d6b83506b66c129d3fa812330fad4509fe218a0dbb99dd2bb5b6319", + "tests/mock-hyprland/src/config/values/types/BoolValue.hpp": "47cf2cca89f71a273573968cb9b8ba46a1496841d6c892756ebf123500a7ecb3", + "tests/mock-hyprland/src/plugins/PluginAPI.hpp": "5654d90ec9090a88bea3d31f8a79617d4c79742b09068648e64448319d395110", + "tests/owned_socket_path_test.cpp": "eaff6b5c6f148eca6c8650ee3dc212a5e892f002ee4f68b2a1290c7205ee7e42", + "tests/passive_pointer_target_test.cpp": "2aeef1de1dc8932b26ab8c41b83fb16a4289ff96c177a5816088f07b9a168948", + "tests/plugin_api_test.cpp": "1e7e200c309996ee945c88e172273ae942be2837e24564d422dee79d8b77d8a2", + "tests/plugin_input_lifetime_test.cpp": "82e57b335ea1216ea24cca07fe4feebafebdebb779785fde20b8dab6ee222e1b", + "tests/protocol_test.cpp": "119cfe0df81c0c00036a2d181764eda7601d6ee72459c2275a96226d4f670447", + "tests/seat_lifetime_test.cpp": "b07570edbe0a142f97c54560eeb93e8327c435ab3b8cbc7496d55175e387b78a", + "tests/status_test.cpp": "b8990efc53ec3820cfe498c920b9220c4b70615ad585468558e032e7619e32f3", + "tests/transport_test.cpp": "deef114a950a27eaff0a530165ddf7db0bfc0fe7e8fb55bdbb66135c8e0c04c9", + "verify.py": "fb35d62313ff4661f892f88666919b33b160f8b6d4fb2d5d52610708bf7f4a54" + }, + "patch_sha256": "e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7", + "schema": 1, + "upstream_manifest_sha256": "54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75", + "upstream_revision": "cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7" +} diff --git a/pkgbuilds/cua-hyprland-plugin/PKGBUILD b/pkgbuilds/cua-hyprland-plugin/PKGBUILD new file mode 100644 index 0000000..9fba2d2 --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/PKGBUILD @@ -0,0 +1,207 @@ +# Verified upstream kit plus a separately pinned Omarchy keyboard-remap patch. +# Normal reruns need a fresh build directory; makepkg -e reuses verified extracted trees. +# Profile kit: original source bytes and separately committed packaging tooling. +# shellcheck shell=bash disable=SC2034,SC2154 +pkgname=cua-hyprland-plugin +pkgver=0.26.1 +pkgrel=5 +pkgdesc='Cua input candidate for reviewed profile omarchy-hyprland-0562r3-remaps' +arch=('x86_64') +url='https://github.com/trycua/cua' +license=('MIT') +depends=('hyprland=0.56.2-3' 'aquamarine=0.15.0-2' 'glibc=2.44+r24+g16be1518495f-1' 'hyprcursor=0.1.13-7' 'hyprgraphics=0.5.1-4' 'hyprlang=0.6.8-5' 'hyprutils=0.14.2-1' 'libgcc=16.2.1+r23+gd564253eb6c8-1' 'libstdc++=16.2.1+r23+gd564253eb6c8-1' 'libxkbcommon=1.13.2-1' 'wayland=1.26.0-1' 'python>=3.11' 'binutils') +makedepends=('cmake>=3.30' 'ninja' 'pkgconf' 'gcc' 'patch') +options=('!strip' '!debug' '!lto') +_stem='cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7' +_archive_sha256='47bca9e018f32f4fcfe683f91c7475c60368f3b65d318cc35c1f2de88a4ee9ab' +_kit_sha256='089f447e11cacd8c2d3d6cd56528776417c51d353b9677c47c42bba1ef79c9f9' +_profile_sha256='fc3034649af98f7f81178ef575660143c249664b9ff5d0e1ccaf79d7580df17b' +_verifier_sha256='480f649cbd35a0ddc7f49dc0a3a44785402c9e0ec9653b0f8ea4965d52d7f900' +_cxx="${CUA_RELEASE_CXX:-/usr/bin/g++}" +_download_name='cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7-profile-omarchy-stable-20260910-kit-1.1.0-5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a-67f89ceb47edd973aa748820eadd02ddbfb3d3e4.tar.gz' +_download_sha256='a89bfa7f6490f598719dfabdd2a5badc137e27b0764fd25e6a05e1f969433520' +source=('https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0-omarchy-stable-20260910/cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7-profile-omarchy-stable-20260910-kit-1.1.0-5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a-67f89ceb47edd973aa748820eadd02ddbfb3d3e4.tar.gz' + 'PROFILE.json') +noextract=("$_download_name") +sha256sums=('a89bfa7f6490f598719dfabdd2a5badc137e27b0764fd25e6a05e1f969433520' + 'fc3034649af98f7f81178ef575660143c249664b9ff5d0e1ccaf79d7580df17b') + +# Downstream inputs are also checked explicitly when makepkg integrity is skipped. +declare -gA _downstream_sha256=( + ['independent-keymaps.patch']='e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7' + ['DOWNSTREAM-PROVENANCE.json']='e0c95350ec3ff2dd54a05e9377d79ddffffdb1cf494d05553a90207a3a919f7e' + ['downstream.py']='7c9725805ad038737bd4d346c85f27991ce9ddfff436a32ebf515cad1bf98ca1' + ['downstream_test.py']='7b9a992979b22a5e061fdf011ac6be59a3f1ae6e0d7c0a8ff631f74e71984c5a' +) +source+=('independent-keymaps.patch' 'DOWNSTREAM-PROVENANCE.json' 'downstream.py' 'downstream_test.py') +sha256sums+=('e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7' 'e0c95350ec3ff2dd54a05e9377d79ddffffdb1cf494d05553a90207a3a919f7e' '7c9725805ad038737bd4d346c85f27991ce9ddfff436a32ebf515cad1bf98ca1' '7b9a992979b22a5e061fdf011ac6be59a3f1ae6e0d7c0a8ff631f74e71984c5a') + +_verify_download() { + python3 -I - "$SRCDEST/$_download_name" "$_download_sha256" "$srcdir" "$1" "$SRCDEST/PROFILE.json" <<'CUA_DOWNLOAD_PY' +import hashlib +import io +import json +from pathlib import Path, PurePosixPath +import sys +import tarfile + +expected = {'KIT-PROVENANCE.json': '7beb736adfd334eed52e84070177634269e3a09f8bb25971b38606933ff4c997', 'PKGBUILD': 'b945a6a6eda13d0e382770edd5419485e3196041956663eb38f5183b05d30db3', 'PROFILE-PKGBUILD.in': 'c350d1b2375946cb0166893d67a1fc01344ee5e3215bbe801b4d54bb361d6bce', 'PROFILE-USAGE.md': 'c14d8e8103fccab59e558758f4249f86bce700a8723cd4ba1b97b1102e02bbd2', 'PROFILE.json': '5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a', 'SHA256SUMS': '34a2126bcfab983f171382aafac3ef218475b652f4583c58f4a04088044cb935', 'SOURCE-PROVENANCE.json': '54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75', 'cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7.tar.gz': '47bca9e018f32f4fcfe683f91c7475c60368f3b65d318cc35c1f2de88a4ee9ab', 'lifecycle.py': 'b18dceb8b8e05b93586ddd3a2f1d90d70ae1c36088e54fc05c89e08585290990', 'profile_bundle.py': 'ac883883814787da477c037f017939ee92c9fb5f46f373af7de1331b24f1f6da', 'profile_verify.py': '480f649cbd35a0ddc7f49dc0a3a44785402c9e0ec9653b0f8ea4965d52d7f900'} +stem = 'cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7' + +def require(condition, message): + if not condition: + raise SystemExit(message) + +def digest(data): + return hashlib.sha256(data).hexdigest() + +archive, checksum, srcdir, mode, profile_path = sys.argv[1:] +archive, srcdir = Path(archive), Path(srcdir) +require(mode in {'check', 'extract'}, 'invalid kit verification mode') +require(archive.is_file() and not archive.is_symlink(), 'outer archive must be a regular file') +data = archive.read_bytes() +require(digest(data) == checksum, 'outer archive checksum mismatch') +payload = {} +with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as contents: + for member in contents: + require(member.isfile() and not member.issparse() and not member.pax_headers, + 'nonregular outer kit member') + require(member.name in expected and member.name not in payload, 'outer kit inventory mismatch') + content = contents.extractfile(member).read() + require(digest(content) == expected[member.name], 'outer kit member checksum mismatch') + payload[member.name] = content +require(payload.keys() == expected.keys(), 'outer kit inventory mismatch') +# Arch's -3 package has the same compositor and all 498 headers/pkg-config +# files as -2. Derive a version-only profile with the original source/tooling +# and byte checks intact; record its own profile and kit provenance digests. +profile_data = Path(profile_path).read_bytes() +require(digest(profile_data) == 'fc3034649af98f7f81178ef575660143c249664b9ff5d0e1ccaf79d7580df17b', + 'local profile checksum mismatch') +profile = json.loads(payload['PROFILE.json']) +profile.update(profile_id='omarchy-hyprland-0562r3-remaps', package_release=5) +profile['hyprland']['package_version'] = '0.56.2-3' +require(json.loads(profile_data) == profile, 'local profile changes more than the reviewed package revision') +payload['PROFILE.json'] = profile_data +provenance = json.loads(payload['KIT-PROVENANCE.json']) +provenance['profile_sha256'] = digest(profile_data) +payload['KIT-PROVENANCE.json'] = (json.dumps(provenance, sort_keys=True, indent=2) + '\n').encode() +recipe = payload['PKGBUILD'].decode() +for old, new in [('pkgrel=2\n', 'pkgrel=5\n'), ('omarchy-stable-20260910', profile['profile_id']), + ('hyprland=0.56.2-2', 'hyprland=0.56.2-3'), + ('5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a', digest(profile_data)), + ('7beb736adfd334eed52e84070177634269e3a09f8bb25971b38606933ff4c997', digest(payload['KIT-PROVENANCE.json']))]: + recipe = recipe.replace(old, new) +payload['PKGBUILD'] = recipe.encode() +payload['SHA256SUMS'] = ''.join(f'{digest(body)} {name}\n' for name, body in sorted(payload.items()) + if name != 'SHA256SUMS').encode() +expected.update({'PROFILE.json': 'fc3034649af98f7f81178ef575660143c249664b9ff5d0e1ccaf79d7580df17b', + 'KIT-PROVENANCE.json': '089f447e11cacd8c2d3d6cd56528776417c51d353b9677c47c42bba1ef79c9f9', + 'PKGBUILD': '0cbf2cd34c3c5038a5ed51e6bf84acd81844e4c2bb08ad7203959201bba61da9', + 'SHA256SUMS': 'd01b9e0be4c5bcf84cc2ecef9f11f44cedfc1ca5b31afbfcf52aa2efbd636a09'}) +for name, content in payload.items(): + require(digest(content) == expected[name], 'derived kit checksum mismatch: ' + name) +require(srcdir.is_dir() and not srcdir.is_symlink(), 'srcdir must be a real directory') +kit, source = srcdir / 'cua-profile-kit', srcdir / stem +if mode == 'extract': + require(not kit.exists() and not kit.is_symlink() and not source.exists() and not source.is_symlink(), + 'prepare requires fresh kit and source destinations; use a clean srcdir') + source_payload = {} + with tarfile.open(fileobj=io.BytesIO(payload[stem + '.tar.gz']), mode='r:gz') as contents: + for member in contents: + require(member.isfile() and not member.issparse() and not member.pax_headers and + member.name.startswith(stem + '/'), 'invalid source member') + name = member.name[len(stem) + 1:] + path = PurePosixPath(name) + require(name and path.as_posix() == name and not path.is_absolute() and + '..' not in path.parts and '\\' not in name and name not in source_payload, + 'unsafe or duplicate source path') + source_payload[name] = contents.extractfile(member).read() + kit.mkdir() + source.mkdir() + for name, content in payload.items(): + (kit / name).write_bytes(content) + for name, content in source_payload.items(): + destination = source / name + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(content) +require(kit.is_dir() and not kit.is_symlink(), 'kit must be a real directory') +require({path.name for path in kit.iterdir()} == expected.keys(), 'extracted kit inventory mismatch') +for name, checksum in expected.items(): + path = kit / name + require(path.is_file() and not path.is_symlink() and digest(path.read_bytes()) == checksum, + 'extracted kit checksum mismatch: ' + name) +CUA_DOWNLOAD_PY +} + +_verify() { + printf '%s %s\n' "$_download_sha256" "$SRCDEST/$_download_name" | sha256sum -c - || return 1 + _verify_download check || return 1 + # Explicit checks still apply to --skipinteg, --noextract and --repackage. + printf '%s %s\n' "$_archive_sha256" "$srcdir/cua-profile-kit/${_stem}.tar.gz" | sha256sum -c - || return 1 + printf '%s %s\n' "$_kit_sha256" "$srcdir/cua-profile-kit/KIT-PROVENANCE.json" | sha256sum -c - || return 1 + printf '%s %s\n' "$_profile_sha256" "$srcdir/cua-profile-kit/PROFILE.json" | sha256sum -c - || return 1 + printf '%s %s\n' "$_verifier_sha256" "$srcdir/cua-profile-kit/profile_verify.py" | sha256sum -c - || return 1 + python3 "$srcdir/cua-profile-kit/profile_verify.py" --kit "$srcdir/cua-profile-kit" --kit-sha256 "$_kit_sha256" \ + --archive "$srcdir/cua-profile-kit/${_stem}.tar.gz" --source "$srcdir/$_stem" --cxx "$_cxx" +} + +_downstream() { + local name + for name in independent-keymaps.patch DOWNSTREAM-PROVENANCE.json downstream.py downstream_test.py; do + printf '%s %s\n' "${_downstream_sha256[$name]}" "$SRCDEST/$name" | sha256sum -c - || return 1 + done + python3 -B "$SRCDEST/downstream.py" "$1" \ + --pristine "$srcdir/$_stem" --source "$srcdir/omarchy-source" \ + --patch "$SRCDEST/independent-keymaps.patch" --manifest "$SRCDEST/DOWNSTREAM-PROVENANCE.json" \ + --kit "$srcdir/cua-profile-kit" --kit-sha256 "$_kit_sha256" \ + --archive "$srcdir/cua-profile-kit/${_stem}.tar.gz" --cxx "$_cxx" "${@:2}" +} + +prepare() { + _verify_download extract || return 1 + _verify || return 1 + _downstream prepare +} + +build() { + _verify || return 1 + _downstream check || return 1 + cmake -S "$srcdir/omarchy-source" -B "$srcdir/build" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER="$_cxx" \ + -DPKG_CONFIG_EXECUTABLE=/usr/bin/pkgconf -DPKG_CONFIG_ARGN= \ + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=OFF -DCMAKE_PREFIX_PATH= \ + -DBUILD_TESTING=ON -DCUA_HYPRLAND_BUILD_PLUGIN=ON \ + -DCUA_HYPRLAND_EXPECTED_VERSION=0.56.2 \ + -DCUA_HYPRLAND_INPUT=ON -DCUA_HYPRLAND_TEST_INPUT=OFF \ + -DCUA_HYPRLAND_INPUT_TRACE=OFF -DCUA_HYPRLAND_TEST_OPERATOR_KEY= || return 1 + cmake --build "$srcdir/build" +} + +check() { + _verify || return 1 + _downstream check || return 1 + python3 -B "$SRCDEST/downstream_test.py" || return 1 + ( + unset LD_PRELOAD FAKEROOTKEY FAKED_MODE + ctest --test-dir "$srcdir/build" --output-on-failure --no-tests=error + ) +} + +package() { + check || return 1 + _downstream build --build "$srcdir/build" --output "$srcdir/BUILD-PROVENANCE.json" || return 1 + install -Dm755 "$srcdir/build/cua-hyprland-plugin.so" \ + "$pkgdir/usr/lib/cua/hyprland/cua-hyprland-plugin.so" || return 1 + install -Dm644 "$srcdir/$_stem/LICENSE.md" \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSE" || return 1 + install -Dm644 "$srcdir/$_stem/SOURCE-PROVENANCE.json" \ + "$pkgdir/usr/share/$pkgname/SOURCE-PROVENANCE.json" || return 1 + local name + install -Dm644 "$srcdir/BUILD-PROVENANCE.json" "$pkgdir/usr/share/$pkgname/BUILD-PROVENANCE.json" || return 1 + for name in KIT-PROVENANCE.json PROFILE.json profile_verify.py; do + install -Dm644 "$srcdir/cua-profile-kit/$name" "$pkgdir/usr/share/$pkgname/$name" || return 1 + done + for name in DOWNSTREAM-PROVENANCE.json independent-keymaps.patch; do + install -Dm644 "$SRCDEST/$name" "$pkgdir/usr/share/$pkgname/$name" || return 1 + done +} diff --git a/pkgbuilds/cua-hyprland-plugin/PROFILE.json b/pkgbuilds/cua-hyprland-plugin/PROFILE.json new file mode 100644 index 0000000..fe5eff2 --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/PROFILE.json @@ -0,0 +1,40 @@ +{ + "architecture": "x86_64", + "compiler": { + "comment": "GCC: (GNU) 16.2.1 20260810", + "sha256": "f04191f6a7b2cd7d9a62e1745872b8a6088791e5af6955488c69c9b2c4668bc9", + "version": "16.2.1 20260810" + }, + "hyprland": { + "header_version": "0.56.2", + "headers_sha256": "88a6875af00203627b264a5c1f9908781be4ad8d9cee4e577fef174e72dd0e28", + "package_version": "0.56.2-3", + "sha256": "da8fcacf347bcbed83edc40108c6e2298da095e22246bd764e9bb382786cebb2" + }, + "kit_version": "1.1.0", + "package_release": 5, + "profile_id": "omarchy-hyprland-0562r3-remaps", + "runtime": { + "basename": "libstdc++.so.6.0.36", + "packages": { + "aquamarine": "0.15.0-2", + "glibc": "2.44+r24+g16be1518495f-1", + "hyprcursor": "0.1.13-7", + "hyprgraphics": "0.5.1-4", + "hyprlang": "0.6.8-5", + "hyprutils": "0.14.2-1", + "libgcc": "16.2.1+r23+gd564253eb6c8-1", + "libstdc++": "16.2.1+r23+gd564253eb6c8-1", + "libxkbcommon": "1.13.2-1", + "wayland": "1.26.0-1" + }, + "sha256": "f5fc7380f2ae46fa4053a64be04e7b98109f1066a4bbfff3c37042488aa0be0e" + }, + "schema": 2, + "source": { + "archive_sha256": "47bca9e018f32f4fcfe683f91c7475c60368f3b65d318cc35c1f2de88a4ee9ab", + "driver_version": "0.26.1", + "manifest_sha256": "54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75", + "revision": "cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7" + } +} diff --git a/pkgbuilds/cua-hyprland-plugin/README.md b/pkgbuilds/cua-hyprland-plugin/README.md new file mode 100644 index 0000000..61946b4 --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/README.md @@ -0,0 +1,212 @@ +# Optional Cua Hyprland plugin + +This package targets **Omarchy stable x86_64**, with Inkscape `1.4.4-6` and two independent background-input lanes. Package release `5` includes the Omarchy patch for independent agent keymaps, operation-specific foreground checks, and compatible Num Lock state; the upstream native qualification below covers the unpatched source, not this change. Cua's native qualification is recorded in [the kit's qualification record](https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0-omarchy-stable-20260910/QUALIFICATION.md) and [Cua #3698](https://github.com/trycua/cua/pull/3698). Omabot replay and Omarchy's merge decision are recorded in [omarchy-pkgs #346](https://github.com/omacom/omarchy-pkgs/pull/346). Scheduling the recipe does not expand the qualified stable target. + +The plugin is optional. Cua Driver works independently, and installation does not load the plugin or enable input. The package follows the normal edge-to-RC-to-stable promotion path instead of the fast release ring. Its PKGBUILD limits builds to x86_64; only stable x86_64 is a qualified target. + +## Source and build profile + +The package uses the [Driver 0.26.1 plugin source](https://github.com/trycua/cua/releases/tag/cua-driver-rs-v0.26.1), +including the [desktop-fault cleanup repair](https://github.com/trycua/cua/pull/3702). +It is not a repackaging of the unmodified 0.24.0 plugin. + +The qualified upstream Driver pairing is `cua-driver-bin 0.27.0-1`, with input protocol v3. Driver 0.27.0 contains the bounded stale-geometry retry validated with the upstream module; its production plugin source is the base for the downstream patch used here. Discovery protocol v2 is separate. A newer Driver release is a changed pairing and requires affected replay before promotion. + +Profile `omarchy-hyprland-0562r3-remaps`, kit tooling `1.1.0`, and package release `5` pin: + +- Hyprland `0.56.2-3`, headers `0.56.2`, and measured executable/header hashes. +- GCC `16.2.1 20260810`, including compiler bytes and emitted ELF identity. +- Shared runtime `libstdc++.so.6.0.36`, its bytes, and exact ABI package versions. + +This profile derives from Cua's `omarchy-stable-20260910` profile. Arch's +Hyprland `-3` package splits out `hyprpm` and changes package dependencies; +its compositor executable and all 498 header/pkg-config files are byte-identical +to `-2`. Both executables have SHA-256 +`da8fcacf347bcbed83edc40108c6e2298da095e22246bd764e9bb382786cebb2`. +The checked-in `PROFILE.json` changes only the profile name, package release, +and exact Hyprland package version. Compiler, runtime, upstream source, executable, +and header identities remain unchanged; the separately recorded patch changes the build source. The download wrapper verifies the +original kit before deriving the updated profile, recipe, and provenance, +then verifies every derived member against its recorded digest. + +The native qualification below was recorded with package release `2` and +Hyprland `-2`. The downstream keymap change needs its own application and Driver replay before promotion. The `-3` dependency must reach a destination channel before +this artifact can be installed there; publication still follows edge β†’ RC β†’ stable. + +The generated `PKGBUILD` identifies the immutable kit download, outer checksum, +and member checksums. The kit records the full source and tooling revisions, +profile digest, and source archive/manifest digests. Do not infer compatibility +from a matching version label or substitute an unreviewed profile. + +The download wrapper verifies its complete inventory before executing downloaded +tooling. It preserves the source archive and its historical embedded verifier, +but explicitly uses the new kit's `profile_verify.py`. Source integrity, +package-owned headers, pkg-config selection, compiler probes, runtime equality, +and production flags remain mandatory. Packaging runs all bundled CTests even +with `--nocheck` or `--repackage`; `--skipinteg` does not bypass recipe checks. +Production input is built in; experimental signed input and tracing are off. + +The pristine upstream archive, manifest, and verifier remain unchanged. `independent-keymaps.patch` is applied to a separate source tree, and `DOWNSTREAM-PROVENANCE.json` pins the patch and every resulting source file. Build, check, and package revalidate both trees, including when makepkg integrity checks are skipped. `BUILD-PROVENANCE.json` records the upstream base under `source`, the applied change under `downstream`, and the final module digest; the downstream manifest and patch are installed beside it. This preserves the existing compiler, headers, runtime, and consumer checks without representing the modified module as an unmodified upstream build. + +## Keyboard behavior + +Each background lane owns a canonical US keymap and independent modifier state. The physical keyboard keeps its layout, Compose key, and remaps. No installation or activation step edits `input:kb_*`. Existing Driver keycodes are interpreted by the agent keyboard, so this does not add Unicode, IME, or new Driver text routes. + +Plain click, scroll, drag, and foreground activation do not require a canonical keyboard layout. Foreground keys still use the primary seat: the plugin checks the requested key and modifier sequence against its actual XKB map before activation or input. Unrelated remaps are accepted; a sequence whose symbols or modifier/lock transitions differ from the canonical meaning is refused with `unsupported_layout`. Arbitrary foreground layout translation remains outside protocol v3. + +Foreground typing preserves Num Lock and admits a requested key sequence only when its symbols and shortcut semantics still match the canonical meaning. Num Lock does not block unaffected letters, top-row digits, Enter, or compatible shortcuts; a keypad sequence whose meaning changes is refused. Caps Lock, other unsupported lock states, held or latched modifiers, and nonzero layout groups remain guarded. + +Both routes retain target/conflict checks and cancellation on desktop/keymap changes. `hyprctl -j cua:status` exposes `keyboard_layout_independent: true` and `foreground_numlock_compatible: true` for installers to distinguish this implementation from an older mapped module. The marker does not identify every future package revision; plugin updates still require a fresh desktop session. + +## Historical upstream qualification + +The initial app scope is native Wayland Inkscape `1.4.4-6` with the canonical +US keymap. Two lanes require independent Driver processes and distinct native +application clients, not merely two windows. This is concurrency inside one +desktop account, not multi-user or mutually untrusted-agent isolation. + +Cua's retained evidence covers background application effects, two-lane overlap, +third-owner refusal, primary-input preservation, conflicts, stale targets and +geometry, cancellation, desktop faults, recovery, and cold package transitions. +Production-package app checks and independent primary observers are separate +from trace-enabled diagnostics. Cua's retained canonical run +`433ce968ee164d5e8e3226e800db93a6` recorded all 128 required cells: 87 +deliveries and 41 expected refusals, with no failures or skips; its completion +report has SHA-256 +`1eda4cc008ea6b27d96c21d58f8041834398a43409642376bafe84ad38f0e112`. +That historical run used source-built released Driver 0.24.0; earlier real-app +checks separately used the then-published Omarchy `cua-driver-bin 0.24.0-1` +executable. A separate later Omabot replay reported 124 passes and four +failures, plus seven incomplete native cases. Cua subsequently passed those +four cells with the repair candidate shipped in Driver 0.27.0 and passed all +seven lifecycle cases. A final exact-release Fleet replay then passed all four +cells with Driver and harness source `082de4344b731ae4738ddc6a6f13f21bb3c49a85`, +released Driver binary SHA-256 +`bb1b65394e912246220f9f758c9efbbf6260cec16e3562e62a361fa95329377f`, +and evidence SHA-256 +`b6c47278a3db398ecbb4d7aed2bce44a467e2af37e6d4ccfc236d1e07aa70af7`. +See the linked qualification record and PR description for exact artifacts and observation limits. Omarchy replay of the 0.27.0 package pairing is recorded in #346; it does not qualify later Driver releases. + +Duplicate motion notifications are retained and counted. They are acceptable +only when pointer identity, coordinates, focus, held input, and foreground +interaction remain unchanged. Actual motionβ€”including moving away and backβ€” +fails isolation. After cancellation, an inert agent pointer may remain parked +if held input is released and authority is revoked. + +Current LibreOffice Calc `26.8`, Chromium/Electron raw background input, +XWayland, Unicode/IME, non-US layouts, and modified pointer gestures are outside +this profile. The plugin does not widen Driver's application admission. +Foreground input, capture, and accessibility have separate contracts; a +background refusal never authorizes a hidden foreground fallback or unlock. + +## Omabot replay before promotion + +Build the unsigned candidate in edge: + +```sh +./bin/build --package cua-hyprland-plugin --arch x86_64 --mirror edge +``` + +In a fresh worker matching the reviewed profile: + +1. Verify the downloaded kit and source identities against the reviewed recipe. + Record the actual channel snapshot, Driver, compiler, compositor, runtime, + applications, keymap, and resulting package/module hashes. +2. Require all bundled tests and native compatibility checks. Do not weaken + exact dependencies or replace the compositor to make the build pass. +3. Install through pacman and activate in a fresh session. Replay the declared + app, two-lane, refusal, primary-input, cancellation, and fault/recovery checks + against the actual packaged Driver and module. A Cua Fleet result is not an + Omabot result; matching source alone does not certify different binaries. +4. Verify restart-based upgrade, rollback, removal, and reinstallation. Retain + evidence that binds each result to the package and mapped module bytes. + +After the exact package and Driver pairing passes, advance the signed artifact with `bin/repo advance --from edge --to rc --package cua-hyprland-plugin`, validate RC, and then use `bin/repo advance --from rc --to stable --package cua-hyprland-plugin`. Do not rebuild independently in RC or stable. + +Portable tests, screenshots, health reports, and a successful build do not +replace native qualification. Recheck the published Driver package before +rollout and qualify any changed pairing explicitly. + +## Activation, updates, and removal + +The package installs the module at +`/usr/lib/cua/hyprland/cua-hyprland-plugin.so` and provenance plus the consumer +verifier under `/usr/share/cua-hyprland-plugin/`. There are no hooks, autoloading, +configuration edits, or hot replacement. + +Save your work and exit Hyprland before installing, replacing, or removing the +package. Install the exact reviewed package from a text console, then start a +fresh session. Before loading, run the consumer check with this package's derived +kit-provenance digest: + +```sh +python3 /usr/share/cua-hyprland-plugin/profile_verify.py \ + --kit /usr/share/cua-hyprland-plugin \ + --kit-sha256 089f447e11cacd8c2d3d6cd56528776417c51d353b9677c47c42bba1ef79c9f9 \ + --consumer /usr/lib/cua/hyprland/cua-hyprland-plugin.so +``` + +This check requires Python 3.11+, binutils `readelf`, and system `ldd`/`pacman`, +not a compiler or headers. If it fails, leave the plugin unloaded. It verifies +installed compatibility, not runtime mapping or input effects. + +After that check passes in the fresh session, load the module explicitly: + +```sh +hyprctl plugin load /usr/lib/cua/hyprland/cua-hyprland-plugin.so +hyprctl -j cua:status +``` + +Loading alone does not enable input. Use Omarchy's explicit Cua Input toggle when available; it verifies the installed profile and loaded capability and removes the legacy copied toggle's keyboard override. If it reports an older mapped plugin, disable Cua Input and log out and back in before enabling it again. Never hot-unload and reload the module. + +For manual activation, add only this plugin setting to a sourced Hyprland Lua configuration file, preserving all existing input settings: + +```lua +hl.config({ + plugin = { cua = { enabled = true } }, +}) +``` + +Then reload and inspect status: + +```sh +hyprctl reload +hyprctl -j cua:status +``` + +Continue only when status reports `keyboard_layout_independent: true`, `foreground_numlock_compatible: true`, input protocol v3, input capability, socket paths, and the expected compositor identity. Do not change `kb_layout`, `kb_options`, or NumLock for background input. If you previously followed the stock-US override instructions, remove only that Cua-specific override and reload to restore your underlying personal settings. + +Start Driver with `CUA_DRIVER_RS_ENABLE_WAYLAND=1`. In a new disposable Inkscape document, test an admitted background key operation and pointer operation, then verify the result in both a fresh snapshot and a saved/reopened SVG. Driver text-route restrictions still apply. Never test against an existing document or automatically replay an action with a partial or unknown outcome. + +To disable input, turn the Cua Input toggle off, or remove the manual `plugin.cua.enabled` setting and reload. Confirm that status reports input disabled. Retained inert agent pointers can remain until the compositor exits; disabling input does not unload the mapped module. + +Before an incompatible desktop update, remove operator-added plugin activation +settings, save work, and exit the graphical session. From a text console, run +`sudo pacman -R cua-hyprland-plugin`, then apply the normal desktop update and +verify a fresh session without the plugin. Declining removal preserves the +dependency refusal. Disabling input alone leaves exact dependencies installed; +do not force an upgrade past them. + +Retain the previous package with its matching compositor, runtime, Driver, and +provenance as a rollback set. Restore a consistent set outside the graphical +session, then repeat the fresh-session consumer and app checks. Do not hot +unload/reload or replace a mapped module. + +## Ownership and publication + +The [agreed ownership split](https://github.com/omacom/omarchy-pkgs/pull/346#issuecomment-5612834061) +assigns profiles, build kits, plugin fixes, and native input evidence to Cua. +Francesco (@f-trycua) is the Cua contact through this PR. Omarchy owns package +integration, dependency-change detection, Omabot validation, and signing and +publication decisions. Spencer (@spencerbull) and Emir (@emirb) jointly own +that Omarchy package and release path. Maintenance is best effort, with no +turnaround commitment. + +Edge detects upcoming incompatibilities; RC validates the intended stable +environment. Mirror/channel changes and changes to ABI dependencies, Driver, +or admitted apps request a new candidate and affected qualification. They do +not establish compatibility or authorize additional publication channels. + +This package participates in scheduled builds, but has no upstream polling, AUR synchronization, or automatic rebuild bump. A checked-in version change or missing artifact can queue it for the normal release pipeline. Build selection, promotion, `push`, and `upload-prebuilt` do not supply native qualification or authorize a broader support claim. Do not silently substitute newly rebuilt bytes during signing or publication. + +Before calling a release complete, install the signed published package on a fresh consumer, verify its signature and package/module digests, and perform a short activation, background-action, and cleanup smoke. Edge and RC artifacts are compatibility checkpoints, not qualified support for those environments. diff --git a/pkgbuilds/cua-hyprland-plugin/downstream.py b/pkgbuilds/cua-hyprland-plugin/downstream.py new file mode 100644 index 0000000..429b7f0 --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/downstream.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Verify the Omarchy patch separately from the unchanged upstream source kit.""" + +import argparse +import hashlib +import importlib.util +import json +from pathlib import Path, PurePosixPath +import shutil +import subprocess + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def digest(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def inventory(root): + require(root.is_dir() and not root.is_symlink(), "source must be a real directory") + result = {} + for path in root.rglob("*"): + require(not path.is_symlink() and (path.is_dir() or path.is_file()), "nonregular source entry") + if path.is_file(): + result[path.relative_to(root).as_posix()] = digest(path) + return result + + +def verify_inputs(pristine, patch, manifest): + require(manifest["schema"] == 1, "unsupported downstream schema") + require(patch.is_file() and not patch.is_symlink() and digest(patch) == manifest["patch_sha256"], + "downstream patch checksum mismatch") + require(digest(pristine / "SOURCE-PROVENANCE.json") == manifest["upstream_manifest_sha256"], + "downstream base manifest mismatch") + require(manifest["files"], "empty downstream inventory") + for name in manifest["files"]: + path = PurePosixPath(name) + require(name and not path.is_absolute() and path.as_posix() == name and + ".." not in path.parts and "\\" not in name, "invalid downstream path") + + +def verify_tree(source, manifest): + require(inventory(source) == manifest["files"], "patched source inventory/checksum mismatch") + + +def prepare(pristine, source, patch, manifest): + require(not source.exists() and not source.is_symlink(), "patched source requires a fresh destination") + shutil.copytree(pristine, source) + subprocess.run(["patch", "--batch", "--fuzz=0", "-p1", "-i", str(patch.resolve())], cwd=source, check=True) + verify_tree(source, manifest) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("mode", choices=("prepare", "check", "build")) + parser.add_argument("--pristine", required=True, type=Path) + parser.add_argument("--source", required=True, type=Path) + parser.add_argument("--patch", required=True, type=Path) + parser.add_argument("--manifest", required=True, type=Path) + parser.add_argument("--kit", required=True, type=Path) + parser.add_argument("--kit-sha256", required=True) + parser.add_argument("--archive", required=True, type=Path) + parser.add_argument("--cxx", required=True, type=Path) + parser.add_argument("--build", type=Path) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + try: + # PKGBUILD authenticates the verifier and this helper before execution. + spec = importlib.util.spec_from_file_location("upstream_profile", args.kit / "profile_verify.py") + upstream = importlib.util.module_from_spec(spec) + spec.loader.exec_module(upstream) + profile, kit = upstream.verify_kit(args.kit, args.kit_sha256) + base = upstream.verify_archive(args.archive, profile) + require(upstream.verify_source(args.pristine, profile) == base, "upstream source identity mismatch") + manifest = upstream.read_json(args.manifest.read_bytes()) + verify_inputs(args.pristine, args.patch, manifest) + if args.mode == "prepare": + prepare(args.pristine, args.source, args.patch, manifest) + else: + verify_tree(args.source, manifest) + if args.mode == "build": + require(args.build is not None and args.output is not None, "build evidence requires output") + native = upstream.verify_native(args.cxx, profile) + native["module_sha256"] = upstream.verify_build(args.build, args.source, args.cxx, profile) + native["module_runtime_sha256"] = profile["runtime"]["sha256"] + args.output.write_bytes(upstream.json_bytes(dict(native, source=base, profile=profile, + kit=kit, downstream=manifest))) + except (ValueError, KeyError, TypeError, OSError, subprocess.CalledProcessError) as error: + parser.exit(1, f"error: {error}\n") + + +if __name__ == "__main__": + main() diff --git a/pkgbuilds/cua-hyprland-plugin/downstream_test.py b/pkgbuilds/cua-hyprland-plugin/downstream_test.py new file mode 100644 index 0000000..60586d2 --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/downstream_test.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Exercise downstream integrity with real patch application and tampering.""" + +import hashlib +from pathlib import Path +import tempfile +import unittest + +import downstream + + +class DownstreamTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.pristine = self.root / "pristine" + self.pristine.mkdir() + (self.pristine / "SOURCE-PROVENANCE.json").write_text("upstream\n") + (self.pristine / "input.cpp").write_text("old\n") + self.patch = self.root / "change.patch" + self.patch.write_text("--- a/input.cpp\n+++ b/input.cpp\n@@ -1 +1 @@\n-old\n+new\n") + self.source = self.root / "patched" + self.manifest = { + "schema": 1, + "patch_sha256": downstream.digest(self.patch), + "upstream_manifest_sha256": downstream.digest(self.pristine / "SOURCE-PROVENANCE.json"), + "files": {"SOURCE-PROVENANCE.json": downstream.digest(self.pristine / "SOURCE-PROVENANCE.json"), + "input.cpp": hashlib.sha256(b"new\n").hexdigest()}, + } + + def test_applies_patch_without_changing_upstream(self): + downstream.verify_inputs(self.pristine, self.patch, self.manifest) + downstream.prepare(self.pristine, self.source, self.patch, self.manifest) + self.assertEqual((self.pristine / "input.cpp").read_text(), "old\n") + self.assertEqual((self.source / "input.cpp").read_text(), "new\n") + + def test_changed_patch_refuses(self): + self.patch.write_text(self.patch.read_text().replace("+new", "+bad")) + with self.assertRaisesRegex(ValueError, "patch checksum"): + downstream.verify_inputs(self.pristine, self.patch, self.manifest) + + def test_changed_base_manifest_refuses(self): + (self.pristine / "SOURCE-PROVENANCE.json").write_text("different\n") + with self.assertRaisesRegex(ValueError, "base manifest"): + downstream.verify_inputs(self.pristine, self.patch, self.manifest) + + def test_tampered_missing_and_extra_files_refuse(self): + downstream.prepare(self.pristine, self.source, self.patch, self.manifest) + file = self.source / "input.cpp" + for content in ("tampered\n", None): + if content is None: + file.unlink() + else: + file.write_text(content) + with self.assertRaisesRegex(ValueError, "inventory/checksum"): + downstream.verify_tree(self.source, self.manifest) + file.write_text("new\n") + (self.source / "unexpected.cpp").write_text("extra\n") + with self.assertRaisesRegex(ValueError, "inventory/checksum"): + downstream.verify_tree(self.source, self.manifest) + + def test_symlink_and_reused_destination_refuse(self): + downstream.prepare(self.pristine, self.source, self.patch, self.manifest) + with self.assertRaisesRegex(ValueError, "fresh destination"): + downstream.prepare(self.pristine, self.source, self.patch, self.manifest) + file = self.source / "input.cpp" + file.unlink() + file.symlink_to(self.pristine / "input.cpp") + with self.assertRaisesRegex(ValueError, "nonregular"): + downstream.verify_tree(self.source, self.manifest) + + +if __name__ == "__main__": + unittest.main() diff --git a/pkgbuilds/cua-hyprland-plugin/independent-keymaps.patch b/pkgbuilds/cua-hyprland-plugin/independent-keymaps.patch new file mode 100644 index 0000000..245ba60 --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/independent-keymaps.patch @@ -0,0 +1,1127 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 8a80de2..2d48237 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -39,8 +39,28 @@ target_compile_options(cua_hyprland_protocol PRIVATE -Wall -Wextra -Wpedantic -W + cua_hyprland_harden(cua_hyprland_protocol) + set_target_properties(cua_hyprland_protocol PROPERTIES POSITION_INDEPENDENT_CODE ON) + ++if(BUILD_TESTING OR CUA_HYPRLAND_INPUT OR CUA_HYPRLAND_TEST_INPUT) ++ find_package(PkgConfig REQUIRED) ++ pkg_check_modules(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) ++endif() ++ + if(BUILD_TESTING) ++ add_executable(cua_hyprland_keyboard_layout_test tests/keyboard_layout_test.cpp) ++ target_compile_features(cua_hyprland_keyboard_layout_test PRIVATE cxx_std_26) ++ target_include_directories(cua_hyprland_keyboard_layout_test PRIVATE src) ++ target_link_libraries(cua_hyprland_keyboard_layout_test PRIVATE PkgConfig::XKBCOMMON) ++ target_compile_options(cua_hyprland_keyboard_layout_test PRIVATE -Wall -Wextra -Wpedantic -Werror) ++ add_test(NAME cua_hyprland_keyboard_layout_test COMMAND cua_hyprland_keyboard_layout_test) ++ + find_package(Python3 REQUIRED COMPONENTS Interpreter) ++ add_test(NAME cua_hyprland_agent_keymap_test ++ COMMAND ${Python3_EXECUTABLE} -B ${CMAKE_CURRENT_SOURCE_DIR}/tests/agent_keymap_test.py) ++ set_tests_properties(cua_hyprland_agent_keymap_test PROPERTIES ++ ENVIRONMENT "CXX=${CMAKE_CXX_COMPILER}") ++ add_test(NAME cua_hyprland_foreground_modifiers_test ++ COMMAND ${Python3_EXECUTABLE} -B ${CMAKE_CURRENT_SOURCE_DIR}/tests/foreground_modifiers_test.py) ++ set_tests_properties(cua_hyprland_foreground_modifiers_test PROPERTIES ++ ENVIRONMENT "CXX=${CMAKE_CXX_COMPILER}") + add_test(NAME cua_hyprland_desktop_fault_policy_test + COMMAND ${Python3_EXECUTABLE} -B + ${CMAKE_CURRENT_SOURCE_DIR}/tests/desktop_fault_policy_test.py) +@@ -218,6 +238,7 @@ if(CUA_HYPRLAND_BUILD_PLUGIN) + message(FATAL_ERROR "Input is pinned to Hyprland 0.56.2") + endif() + target_sources(cua_hyprland_plugin PRIVATE src/input_experiment.cpp) ++ target_link_libraries(cua_hyprland_plugin PRIVATE PkgConfig::XKBCOMMON) + endif() + if(CUA_HYPRLAND_INPUT) + target_compile_definitions(cua_hyprland_plugin PRIVATE CUA_HYPRLAND_INPUT=1) +diff --git a/src/foreground_route.hpp b/src/foreground_route.hpp +index 0b675d3..c3af461 100644 +--- a/src/foreground_route.hpp ++++ b/src/foreground_route.hpp +@@ -68,11 +68,13 @@ struct ForegroundSeatBindings { + bool unique() const { return primary_candidates == 1; } + }; + +-inline ForegroundFailureReason foreground_key_modifier_failure(const std::array& modifiers) { +- // The KEY mapping assumes a neutral US state, including layout group zero. ++inline ForegroundFailureReason foreground_key_modifier_failure(const std::array& modifiers, ++ std::uint32_t allowed_locked = 0) { ++ // The caller may admit a keymap-resolved ambient Num Lock mask. All other ++ // human modifiers and nonzero layout groups remain unsupported. + if (modifiers[0]) return ForegroundFailureReason::keyboard_depressed; + if (modifiers[1]) return ForegroundFailureReason::keyboard_latched; +- if (modifiers[2]) return ForegroundFailureReason::keyboard_locked; ++ if (modifiers[2] & ~allowed_locked) return ForegroundFailureReason::keyboard_locked; + if (modifiers[3]) return ForegroundFailureReason::keyboard_group; + return ForegroundFailureReason::none; + } +diff --git a/src/input_experiment.cpp b/src/input_experiment.cpp +index fc7e740..5d80d3e 100644 +--- a/src/input_experiment.cpp ++++ b/src/input_experiment.cpp +@@ -9,6 +9,8 @@ + #include "seat_lifetime.hpp" + #include "owned_socket_path.hpp" + #include "foreground_route.hpp" ++#include "keyboard_layout.hpp" ++#include + + #include + #include +@@ -214,7 +216,11 @@ struct InputExperiment::Impl { + xkb_keymap* keymap = nullptr; + xkb_state* keyboard_state = nullptr; + int keymap_fd = -1; +- bool retired = false, suspended = true, us_keymap = false, physical_keymap_present = false; ++ // Foreground state uses the actual primary map; never share it with agent seats. ++ xkb_keymap* physical_keymap = nullptr; ++ xkb_state* physical_state = nullptr; ++ std::string physical_keymap_text; ++ bool retired = false, suspended = true, physical_keymap_present = false; + WP physical_keyboard; + CHyprSignalListener keymap_listener; + unsigned lane; +@@ -243,23 +249,37 @@ struct InputExperiment::Impl { + // No private key or input-enabled default exists in this component. + } + +- static bool canonical_us_keymap(xkb_context* context, xkb_keymap* map) { +- // Compare canonical compiled content, not a layout display name. This +- // deliberately excludes variants, options, remaps, and multiple groups. +- const xkb_rule_names names{"evdev", "pc105", "us", "", ""}; +- auto* reference = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); +- if (!reference) return false; +- char* actual = xkb_keymap_get_as_string(map, XKB_KEYMAP_FORMAT_TEXT_V1); +- char* expected = xkb_keymap_get_as_string(reference, XKB_KEYMAP_FORMAT_TEXT_V1); +- const bool matches = actual && expected && std::strcmp(actual, expected) == 0; +- std::free(actual); std::free(expected); xkb_keymap_unref(reference); +- return matches; +- } +- bool layout_qualified() const { +- if (!kProduction) return true; ++ bool physical_layout_ready() const { + const auto keyboard = g_pSeatManager->m_keyboard.lock(); +- return physical_keymap_present && us_keymap && keyboard_state && keyboard && +- keyboard->m_xkbKeymapV1FD.get() >= 0 && keyboard->m_xkbKeymapV1String == keymap_text; ++ return physical_keymap_present && physical_state && keyboard && ++ keyboard->m_xkbKeymapV1FD.get() >= 0 && keyboard->m_xkbKeymapV1String == physical_keymap_text; ++ } ++ bool layout_qualified(InputRoute route, std::uint64_t capability) const { ++ return keyboard_layout_ready(route, capability, keyboard_state && keymap_fd >= 0, physical_layout_ready()); ++ } ++ void initialize_agent_keymap() { ++ if (keymap) return; ++ xkb_context_ = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ keymap = xkb_context_ ? agent_keymap(xkb_context_) : nullptr; ++ keyboard_state = keymap ? xkb_state_new(keymap) : nullptr; ++ char* text = keymap ? xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1) : nullptr; ++ if (!keyboard_state || !text) { ++ std::free(text); ++ throw std::runtime_error("agent XKB keymap unavailable"); ++ } ++ keymap_text = text; ++ std::free(text); ++ keymap_fd = memfd_create("cua-agent-keymap", MFD_CLOEXEC | MFD_ALLOW_SEALING); ++ if (keymap_fd < 0) throw std::runtime_error("agent keymap fd unavailable"); ++ std::size_t offset = 0; ++ while (offset < keymap_text.size() + 1) { ++ const auto count = write(keymap_fd, keymap_text.c_str() + offset, keymap_text.size() + 1 - offset); ++ if (count < 0 && errno == EINTR) continue; ++ if (count <= 0) throw std::runtime_error("agent keymap write failed"); ++ offset += static_cast(count); ++ } ++ if (fcntl(keymap_fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) < 0) ++ throw std::runtime_error("agent keymap sealing failed"); + } + void sync_keymap() { + const auto keyboard = g_pSeatManager->m_keyboard.lock(); +@@ -278,38 +298,20 @@ struct InputExperiment::Impl { + return; + } + physical_keymap_present = true; +- if (keyboard_state && keymap_text == keyboard->m_xkbKeymapV1String) return; +- // Prepare a complete replacement before retiring the old independent +- // state. Keep our own fd: primary keyboard replacement must not leave +- // later seat bindings referring to a closed compositor fd. +- auto* context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); +- auto* map = context ? xkb_keymap_new_from_string(context, keyboard->m_xkbKeymapV1String.c_str(), +- XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS) : nullptr; +- auto* state = map ? xkb_state_new(map) : nullptr; +- const auto fd = fcntl(keyboard->m_xkbKeymapV1FD.get(), F_DUPFD_CLOEXEC, 0); +- if (!state || fd < 0) { +- if (fd >= 0) close(fd); +- if (state) xkb_state_unref(state); +- if (map) xkb_keymap_unref(map); +- if (context) xkb_context_unref(context); +- throw std::runtime_error("independent XKB state unavailable"); +- } ++ if (physical_keymap_text == keyboard->m_xkbKeymapV1String) return; ++ // Physical changes still revoke authority, but never replace the map ++ // advertised by either background keyboard. + desktop_transition(); +- if (keyboard_state) xkb_state_unref(keyboard_state); +- if (keymap) xkb_keymap_unref(keymap); +- if (xkb_context_) xkb_context_unref(xkb_context_); +- if (keymap_fd >= 0) close(keymap_fd); +- keyboard_state = state; keymap = map; xkb_context_ = context; keymap_fd = fd; +- us_keymap = !kProduction || canonical_us_keymap(context, map); +- keymap_text = keyboard->m_xkbKeymapV1String; +- for (auto& k : keyboards) +- if (!k->dead && k->wl->resource()) +- k->wl->sendKeymap(WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd, keymap_text.size() + 1); +- for (auto& seat : seats) +- if (!seat->dead && seat->wl->resource()) +- seat->wl->sendCapabilities(static_cast(WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD)); ++ auto* map = xkb_keymap_new_from_string(xkb_context_, keyboard->m_xkbKeymapV1String.c_str(), ++ XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); ++ auto* state = map ? xkb_state_new(map) : nullptr; ++ if (physical_state) xkb_state_unref(physical_state); ++ if (physical_keymap) xkb_keymap_unref(physical_keymap); ++ physical_state = state; physical_keymap = map; ++ physical_keymap_text = keyboard->m_xkbKeymapV1String; + } + void start() { ++ initialize_agent_keymap(); + sync_keymap(); + timer = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, tick, this); + if (!timer) throw std::runtime_error("input timer registration failed"); +@@ -389,6 +391,8 @@ struct InputExperiment::Impl { + cleanup_socket(); + if (keyboard_state) xkb_state_unref(keyboard_state); + if (keymap) xkb_keymap_unref(keymap); ++ if (physical_state) xkb_state_unref(physical_state); ++ if (physical_keymap) xkb_keymap_unref(physical_keymap); + if (xkb_context_) xkb_context_unref(xkb_context_); + if (keymap_fd >= 0) close(keymap_fd); + } +@@ -782,12 +786,40 @@ struct InputExperiment::Impl { + .exact_pointer_focus = root && g_pSeatManager->m_state.pointerFocus == root, + }; + } ++ std::array capture_foreground_modifiers(bool needs_keyboard) const { ++ const auto physical = g_pSeatManager->m_keyboard.lock(); ++ if (!physical) throw ForegroundFailure{ForegroundFailureReason::physical_keyboard}; ++ std::array result{}; ++ const auto observe = [&](const auto& kb, bool primary) { ++ const std::array state{kb->m_modifiersState.depressed, kb->m_modifiersState.latched, ++ kb->m_modifiersState.locked, kb->m_modifiersState.group}; ++ if (needs_keyboard) { ++ const auto failure = foreground_key_modifier_failure(state, foreground_numlock_mask(kb->m_xkbKeymap)); ++ if (failure != ForegroundFailureReason::none) throw ForegroundFailure{failure}; ++ // Hyprland combines shared raw masks. A lock from a different ++ // encoding must not be reinterpreted as the primary Num Lock. ++ if (state[2] && state[2] != foreground_numlock_mask(physical_keymap)) ++ throw ForegroundFailure{ForegroundFailureReason::keyboard_locked}; ++ } ++ for (unsigned i = 0; i < 3; ++i) result[i] |= state[i]; ++ if (primary) result[3] = state[3]; ++ }; ++ observe(physical, true); ++ for (const auto& kb : g_pInputManager->m_keyboards) { ++ if (kb == physical || !kb->m_enabled || !kb->shareStates() || ++ (kb->isVirtual() && g_pInputManager->shouldIgnoreVirtualKeyboard(kb))) continue; ++ observe(kb, false); ++ } ++ return result; ++ } + void require_foreground(Client& c) { + if (lease != &c) throw ForegroundFailure{ForegroundFailureReason::lease}; + if (c.dead) throw ForegroundFailure{ForegroundFailureReason::client_dead}; + if (!available()) throw ForegroundFailure{ForegroundFailureReason::session_unavailable}; +- if (!layout_qualified()) throw ForegroundFailure{ForegroundFailureReason::unsupported_layout}; ++ if (foreground_keyboard_used && !physical_layout_ready()) throw ForegroundFailure{ForegroundFailureReason::unsupported_layout}; + if (Clock::now() >= expires) throw ForegroundFailure{ForegroundFailureReason::lease_expired}; ++ if (foreground_keyboard_used && capture_foreground_modifiers(true) != foreground_modifiers) ++ throw ForegroundFailure{ForegroundFailureReason::keyboard_state}; + const auto failure = foreground_guard(c).dispatch_failure(foreground_needs_pointer); + if (failure != ForegroundFailureReason::none) throw ForegroundFailure{failure}; + } +@@ -803,24 +835,31 @@ struct InputExperiment::Impl { + p->sendButton(event_ms(), held_button, WL_POINTER_BUTTON_STATE_RELEASED); + p->sendFrame(); + } ++ // Synthetic events only change our private state. If human input ++ // cancelled the action, restore the current real state, not stale locks. ++ const auto restore_modifiers = g_pSeatManager->m_keyboard.lock() ? ++ capture_foreground_modifiers(false) : foreground_modifiers; + if (foreground_keyboard_used && root && root->good() && g_pSeatManager->m_state.keyboardFocus == root) + for (const auto& weak : foreground_keyboards) + if (const auto k = weak.lock(); k && k->good()) { + for (auto code : held_keys) k->sendKey(event_ms(), code, WL_KEYBOARD_KEY_STATE_RELEASED); +- k->sendMods(foreground_modifiers[0], foreground_modifiers[1], foreground_modifiers[2], foreground_modifiers[3]); ++ k->sendMods(restore_modifiers[0], restore_modifiers[1], restore_modifiers[2], restore_modifiers[3]); + } + held_button = 0; held_keys.clear(); + foreground_pointers.clear(); foreground_keyboards.clear(); foreground_surface.reset(); foreground_seat.reset(); + foreground_started = false; + foreground_keyboard_used = false; ++ if (physical_state) xkb_state_unref(physical_state); ++ physical_state = physical_keymap ? xkb_state_new(physical_keymap) : nullptr; + } +- void start_foreground(Client& c, double x, double y, bool needs_pointer, bool needs_keyboard) { ++ void start_foreground(Client& c, double x, double y, bool needs_pointer, bool needs_keyboard, ++ const std::array& modifiers) { + const auto root = c.surface.lock(); + const auto physical = g_pSeatManager->m_keyboard.lock(); + const auto failure = foreground_guard(c).activation_failure(); + if (failure != ForegroundFailureReason::none) throw ForegroundFailure{failure}; + if (!physical) throw ForegroundFailure{ForegroundFailureReason::physical_keyboard}; +- if (!keyboard_state) throw ForegroundFailure{ForegroundFailureReason::keyboard_state}; ++ if (needs_keyboard && !physical_layout_ready()) throw ForegroundFailure{ForegroundFailureReason::keyboard_state}; + if (needs_pointer && !g_pSeatManager->m_mouse) throw ForegroundFailure{ForegroundFailureReason::physical_pointer}; + const Vector2D local{x + c.geometry[0] - c.geometry[4], y + c.geometry[1] - c.geometry[5]}; + if (needs_pointer && (!point(c, x, y) || root->at(local, true).first != root)) throw ForegroundFailure{ForegroundFailureReason::pointer_target}; +@@ -831,24 +870,16 @@ struct InputExperiment::Impl { + for (const auto& k : seat->m_keyboards) if (k && k->good()) foreground_keyboards.push_back(k); + if (needs_pointer && foreground_pointers.empty()) throw ForegroundFailure{ForegroundFailureReason::pointer_resources}; + if (foreground_keyboards.empty()) throw ForegroundFailure{ForegroundFailureReason::keyboard_resources}; +- foreground_modifiers = {physical->m_modifiersState.depressed, physical->m_modifiersState.latched, +- physical->m_modifiersState.locked, physical->m_modifiersState.group}; +- for (const auto& kb : g_pInputManager->m_keyboards) { +- if (!kb->m_enabled || !kb->shareStates() || (kb->isVirtual() && g_pInputManager->shouldIgnoreVirtualKeyboard(kb))) continue; +- foreground_modifiers[0] |= kb->m_modifiersState.depressed; +- foreground_modifiers[1] |= kb->m_modifiersState.latched; +- foreground_modifiers[2] |= kb->m_modifiersState.locked; +- } +- if (needs_keyboard) { +- const auto modifier_failure = foreground_key_modifier_failure(foreground_modifiers); +- if (modifier_failure != ForegroundFailureReason::none) throw ForegroundFailure{modifier_failure}; +- } +- xkb_state_update_mask(keyboard_state, foreground_modifiers[0], foreground_modifiers[1], foreground_modifiers[2], 0, 0, foreground_modifiers[3]); ++ foreground_modifiers = modifiers; ++ if (needs_keyboard && capture_foreground_modifiers(true) != foreground_modifiers) ++ throw ForegroundFailure{ForegroundFailureReason::keyboard_state}; ++ if (needs_keyboard) xkb_state_update_mask(physical_state, foreground_modifiers[0], foreground_modifiers[1], foreground_modifiers[2], 0, 0, foreground_modifiers[3]); + foreground_surface = root; + foreground_seat = seat; + foreground_needs_pointer = needs_pointer; + c.foreground_attempted = true; + foreground_started = true; ++ foreground_keyboard_used = needs_keyboard; + foreground_activating = true; + // Activation intentionally persists. Never save, borrow, or restore focus. + if (g_pSeatManager->m_state.keyboardFocus != root || Desktop::focusState()->window() != c.window.lock() || +@@ -858,7 +889,16 @@ struct InputExperiment::Impl { + if (lease != &c) throw ForegroundFailure{ForegroundFailureReason::lease}; + const auto focus_failure = foreground_guard(c).dispatch_failure(false); + if (focus_failure != ForegroundFailureReason::none) throw ForegroundFailure{focus_failure}; +- if (!needs_pointer) { require_foreground(c); return; } ++ if (!needs_pointer) { ++ require_foreground(c); ++ if (needs_keyboard) ++ for (const auto& weak : foreground_keyboards) { ++ const auto k = weak.lock(); ++ if (!k || !k->good()) throw ForegroundFailure{ForegroundFailureReason::keyboard_resources}; ++ k->sendMods(foreground_modifiers[0], foreground_modifiers[1], foreground_modifiers[2], foreground_modifiers[3]); ++ } ++ return; ++ } + foreground_activating = true; + ::Pointer::mgr()->warpTo({x + c.geometry[0], y + c.geometry[1]}); + if (lease != &c) throw ForegroundFailure{ForegroundFailureReason::lease}; +@@ -893,16 +933,17 @@ struct InputExperiment::Impl { + } + void foreground_key(Client& c, std::uint32_t code, bool pressed) { + require_foreground(c); ++ if (!physical_layout_ready()) throw ForegroundFailure{ForegroundFailureReason::unsupported_layout}; + foreground_keyboard_used = true; +- xkb_state_update_key(keyboard_state, code + 8, pressed ? XKB_KEY_DOWN : XKB_KEY_UP); ++ xkb_state_update_key(physical_state, code + 8, pressed ? XKB_KEY_DOWN : XKB_KEY_UP); + if (pressed) held_keys.push_back(code); else std::erase(held_keys, code); + for (const auto& weak : foreground_keyboards) { + const auto k = weak.lock(); if (!k || !k->good()) throw ForegroundFailure{ForegroundFailureReason::keyboard_resources}; + k->sendKey(event_ms(), code, pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED); +- k->sendMods(xkb_state_serialize_mods(keyboard_state, XKB_STATE_MODS_DEPRESSED), +- xkb_state_serialize_mods(keyboard_state, XKB_STATE_MODS_LATCHED), +- xkb_state_serialize_mods(keyboard_state, XKB_STATE_MODS_LOCKED), +- xkb_state_serialize_layout(keyboard_state, XKB_STATE_LAYOUT_EFFECTIVE)); ++ k->sendMods(xkb_state_serialize_mods(physical_state, XKB_STATE_MODS_DEPRESSED), ++ xkb_state_serialize_mods(physical_state, XKB_STATE_MODS_LATCHED), ++ xkb_state_serialize_mods(physical_state, XKB_STATE_MODS_LOCKED), ++ xkb_state_serialize_layout(physical_state, XKB_STATE_LAYOUT_EFFECTIVE)); + } + } + bool pointer_enter(Client& c, double x, double y) { +@@ -942,8 +983,8 @@ struct InputExperiment::Impl { + held_button = pressed ? value : 0; + } + bool keyboard_enter(Client& c) { +- const auto root = c.surface.lock(); const auto physical = g_pSeatManager->m_keyboard.lock(); +- if (!root || !physical || physical->m_xkbKeymapV1String != keymap_text || !keyboard_state) return false; ++ const auto root = c.surface.lock(); ++ if (!root || !keyboard_state || keymap_fd < 0) return false; + unsigned count = 0; + for (auto& k : keyboards) { + if (k->dead || !k->wl->resource() || k->wl->client() != root->client()) continue; +@@ -1030,7 +1071,7 @@ struct InputExperiment::Impl { + if (kProduction && (!InputGrant::single_operation(requested_cap) || + (requested_cap == 16 && route != InputRoute::primary_foreground))) { invalidate(c); send(c, refusal("unsupported")); return; } + if (kProduction && !available()) { invalidate(c, false); send(c, refusal("session_unavailable")); return; } +- if (!layout_qualified()) { invalidate(c, false); send(c, refusal("unsupported_layout")); return; } ++ if (!layout_qualified(route, requested_cap)) { invalidate(c, false); send(c, refusal("unsupported_layout")); return; } + const auto pid = number(f[1]); const auto address = number(f[2], 16); + PHLWINDOW window; + for (const auto& w : Desktop::windowState()->windows()) +@@ -1090,7 +1131,7 @@ struct InputExperiment::Impl { + if (c.token.empty() || f[2] != c.token || !refresh(c)) { send(c, refusal("stale_target")); return; } + if (number(f[3]) != c.revision) { if (kProduction) revoke("stale_geometry"); send(c, refusal("stale_geometry")); return; } + if (!available()) { revoke("session_unavailable", true); send(c, refusal("session_unavailable")); return; } +- if (!layout_qualified()) { revoke("unsupported_layout", true); send(c, refusal("unsupported_layout")); return; } ++ if (!layout_qualified(c.route, cap)) { revoke("unsupported_layout", true); send(c, refusal("unsupported_layout")); return; } + if (lease && Clock::now() >= expires) revoke("lease_expired"); + if (drag) { send(c, refusal("lease_busy")); return; } + if (lease != &c || !(capabilities & cap) || (kProduction && !grant.permits(cap, Clock::now()))) { +@@ -1184,8 +1225,13 @@ struct InputExperiment::Impl { + if (Clock::now() + std::chrono::milliseconds(duration + 50) >= expires) { send(c, refusal("lease_expired")); return; } + } + } ++ const auto modifiers = capture_foreground_modifiers(command == "KEY"); ++ if (command == "KEY" && !foreground_chord_compatible(physical_keymap, keymap, code, mods, modifiers)) { ++ revoke("unsupported_layout", true); ++ send(c, refusal("unsupported_layout")); return; ++ } + if (!consume_grant(c, cap)) return; +- start_foreground(c, x, y, command != "KEY" && command != "ACTIVATE", command == "KEY"); ++ start_foreground(c, x, y, command != "KEY" && command != "ACTIVATE", command == "KEY", modifiers); + if (command == "KEY") { + const std::array keys{42, 29, 56, 125}; + for (unsigned i = 0; i < 4; ++i) if ((mods & (1u << i)) && keys[i] != code) foreground_key(c, keys[i], true); +@@ -1227,7 +1273,7 @@ struct InputExperiment::Impl { + if (lease) { + if (lease->dead) revoke("disconnected", true); + else if (Clock::now() >= expires) revoke("lease_expired"); +- else if (!available() || !layout_qualified()) revoke("cancelled", true); ++ else if (!available() || !layout_qualified(lease->route, capabilities)) revoke("cancelled", true); + else if (!refresh(*lease) || primary_conflict(*lease) || agent_conflict(*lease) || + (drag && !drag->geometry.matches(lease->revision))) revoke("cancelled"); + } +diff --git a/src/keyboard_layout.hpp b/src/keyboard_layout.hpp +new file mode 100644 +index 0000000..f5f51c8 +--- /dev/null ++++ b/src/keyboard_layout.hpp +@@ -0,0 +1,127 @@ ++#pragma once ++ ++#include "foreground_route.hpp" ++#include ++#include ++#include ++#include ++#include ++ ++namespace cua::hyprland { ++// Wire v3 carries evdev key positions plus fixed Shift/Ctrl/Alt/Super bits. ++// Background seats advertise this map, independently of the human keyboard. ++inline xkb_keymap* agent_keymap(xkb_context* context) { ++ const xkb_rule_names names{"evdev", "pc105", "us", "", ""}; ++ return xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); ++} ++ ++inline bool keyboard_layout_ready(InputRoute route, std::uint64_t capability, ++ bool agent_ready, bool physical_ready) { ++ if (!(capability & 2)) return true; // Pointer/activation needs no key mapping. ++ return route == InputRoute::primary_foreground ? physical_ready : agent_ready; ++} ++ ++inline bool same_key_symbols(xkb_state* actual, xkb_state* expected, xkb_keycode_t key) { ++ const xkb_keysym_t *a = nullptr, *b = nullptr; ++ const int na = xkb_state_key_get_syms(actual, key, &a); ++ const int nb = xkb_state_key_get_syms(expected, key, &b); ++ if (na != nb) return false; ++ for (int i = 0; i < na; ++i) if (a[i] != b[i]) return false; ++ return true; ++} ++ ++inline bool same_consumed_modifiers(xkb_state* actual, xkb_state* expected, xkb_keycode_t key) { ++ // Toolkits subtract consumed modifiers when matching shortcuts. Matching ++ // symbols and effective state alone does not preserve shortcut meaning. ++ for (auto* state : {actual, expected}) { ++ auto* map = xkb_state_get_keymap(state); ++ for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(map); ++i) { ++ const char* name = xkb_keymap_mod_get_name(map, i); ++ if (xkb_state_mod_name_is_active(state, name, XKB_STATE_MODS_EFFECTIVE) <= 0) continue; ++ const auto ai = xkb_keymap_mod_get_index(xkb_state_get_keymap(actual), name); ++ const auto bi = xkb_keymap_mod_get_index(xkb_state_get_keymap(expected), name); ++ for (auto mode : {XKB_CONSUMED_MODE_XKB, XKB_CONSUMED_MODE_GTK}) ++ if ((xkb_state_mod_index_is_consumed2(actual, key, ai, mode) > 0) != ++ (xkb_state_mod_index_is_consumed2(expected, key, bi, mode) > 0)) return false; ++ } ++ } ++ return true; ++} ++ ++inline bool same_modifier_state(xkb_state* actual, xkb_state* expected) { ++ // Modifier indices can differ between maps. Compare names, in both directions, ++ // including nonstandard modifiers and lock/latch changes on key release. ++ for (auto* state : {actual, expected}) { ++ auto* map = xkb_state_get_keymap(state); ++ for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(map); ++i) { ++ const char* name = xkb_keymap_mod_get_name(map, i); ++ for (auto component : {XKB_STATE_MODS_DEPRESSED, XKB_STATE_MODS_LATCHED, ++ XKB_STATE_MODS_LOCKED, XKB_STATE_MODS_EFFECTIVE}) { ++ const bool a = xkb_state_mod_name_is_active(actual, name, component) > 0; ++ const bool b = xkb_state_mod_name_is_active(expected, name, component) > 0; ++ if (a != b) return false; ++ } ++ } ++ } ++ for (auto component : {XKB_STATE_LAYOUT_DEPRESSED, XKB_STATE_LAYOUT_LATCHED, ++ XKB_STATE_LAYOUT_LOCKED, XKB_STATE_LAYOUT_EFFECTIVE}) ++ if (xkb_state_serialize_layout(actual, component) != xkb_state_serialize_layout(expected, component)) ++ return false; ++ return true; ++} ++ ++// Resolve the virtual modifier through this map; Num Lock is not necessarily ++// encoded as Mod2. Ambiguous encodings are deliberately not admitted. ++inline xkb_mod_mask_t foreground_numlock_mask(xkb_keymap* map) { ++ const auto mask = map ? xkb_keymap_mod_get_mask(map, XKB_VMOD_NAME_NUM) : 0; ++ return mask && !(mask & (mask - 1)) && ++ !(mask & xkb_keymap_mod_get_mask(map, XKB_MOD_NAME_CAPS)) ? mask : 0; ++} ++ ++// Simulate the complete chord before delivering any events or changing focus. ++// This is compatibility checking, not layout translation: physical key positions ++// remain unchanged. Unrelated remaps are harmless, requested remaps fail closed. ++inline bool foreground_chord_compatible(xkb_keymap* physical, xkb_keymap* canonical, ++ std::uint32_t code, std::uint32_t mods, ++ const std::array& modifiers) { ++ if (!physical || !canonical) return false; ++ if (foreground_key_modifier_failure(modifiers, foreground_numlock_mask(physical)) != ++ ForegroundFailureReason::none) return false; ++ using State = std::unique_ptr; ++ State actual{xkb_state_new(physical), xkb_state_unref}; ++ State expected{xkb_state_new(canonical), xkb_state_unref}; ++ // The client retains Num Lock, but wire keys still mean the neutral US ++ // chord. A third state rejects keypad/navigation changes caused by the lock. ++ State intended{xkb_state_new(canonical), xkb_state_unref}; ++ if (!actual || !expected || !intended) return false; ++ if (modifiers[2]) { ++ const auto canonical_lock = foreground_numlock_mask(canonical); ++ if (!canonical_lock) return false; ++ xkb_state_update_mask(actual.get(), 0, 0, modifiers[2], 0, 0, 0); ++ xkb_state_update_mask(expected.get(), 0, 0, canonical_lock, 0, 0, 0); ++ if (!same_modifier_state(actual.get(), expected.get())) return false; ++ } ++ const auto event = [&](std::uint32_t key, xkb_key_direction direction) { ++ // A client interprets the press using the preceding modifiers event. ++ // Stock both_capslock_cancel gives Shift a Caps_Lock symbol at its ++ // shifted level; that level is not another press. Releases pair by ++ // keycode, but their lock/latch/group effects still must agree. ++ if (direction == XKB_KEY_DOWN && ++ (!same_key_symbols(actual.get(), expected.get(), key + 8) || ++ !same_consumed_modifiers(actual.get(), expected.get(), key + 8) || ++ !same_key_symbols(expected.get(), intended.get(), key + 8) || ++ !same_consumed_modifiers(expected.get(), intended.get(), key + 8))) return false; ++ xkb_state_update_key(actual.get(), key + 8, direction); ++ xkb_state_update_key(expected.get(), key + 8, direction); ++ xkb_state_update_key(intended.get(), key + 8, direction); ++ return same_modifier_state(actual.get(), expected.get()); ++ }; ++ constexpr std::array keys{42, 29, 56, 125}; ++ for (unsigned i = 0; i < keys.size(); ++i) ++ if ((mods & (1u << i)) && keys[i] != code && !event(keys[i], XKB_KEY_DOWN)) return false; ++ if (!event(code, XKB_KEY_DOWN) || !event(code, XKB_KEY_UP)) return false; ++ for (int i = 3; i >= 0; --i) ++ if ((mods & (1u << i)) && keys[i] != code && !event(keys[i], XKB_KEY_UP)) return false; ++ return true; ++} ++} // namespace cua::hyprland +diff --git a/src/plugin.cpp b/src/plugin.cpp +index 88b9900..ae7fc7c 100644 +--- a/src/plugin.cpp ++++ b/src/plugin.cpp +@@ -218,6 +218,11 @@ std::string status_output(bool json) { + + if (json) { + auto result = cua::hyprland::render_status_json(report); ++#ifdef CUA_HYPRLAND_INPUT ++ // Installation checks compiled support before enabling input seats. ++ result.pop_back(); ++ result += ",\"keyboard_layout_independent\":true,\"foreground_numlock_compatible\":true}"; ++#endif + #if defined(CUA_HYPRLAND_TEST_INPUT) || defined(CUA_HYPRLAND_INPUT) + if (g_experiment) { + #ifdef CUA_HYPRLAND_INPUT +diff --git a/tests/agent_keymap_test.py b/tests/agent_keymap_test.py +new file mode 100644 +index 0000000..78a3603 +--- /dev/null ++++ b/tests/agent_keymap_test.py +@@ -0,0 +1,86 @@ ++"""Exercise the production map initializer without a compositor or desktop changes.""" ++import os ++from pathlib import Path ++import re ++import shlex ++import subprocess ++import tempfile ++import unittest ++ ++ROOT = Path(__file__).resolve().parents[1] ++ ++ ++class AgentKeymapTest(unittest.TestCase): ++ def test_production_keymap_and_fd(self): ++ source = (ROOT / 'src/input_experiment.cpp').read_text() ++ body = re.search(r'^ void initialize_agent_keymap\(\).*?^ }', source, re.M | re.S) ++ self.assertIsNotNone(body) ++ fixture = r''' ++#include "keyboard_layout.hpp" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++using namespace cua::hyprland; ++void check(bool v, const char* m) { if (!v) { std::cerr << m; std::exit(1); } } ++struct Lane { ++ xkb_context* xkb_context_ = nullptr; ++ xkb_keymap* keymap = nullptr; ++ xkb_state* keyboard_state = nullptr; ++ int keymap_fd = -1; ++ std::string keymap_text; ++ // INITIALIZER ++ ~Lane() { ++ if (keyboard_state) xkb_state_unref(keyboard_state); ++ if (keymap) xkb_keymap_unref(keymap); ++ if (xkb_context_) xkb_context_unref(xkb_context_); ++ if (keymap_fd >= 0) close(keymap_fd); ++ } ++}; ++int main() { ++ Lane a, b; ++ a.initialize_agent_keymap(); b.initialize_agent_keymap(); ++ check(a.keymap_fd != b.keymap_fd && a.keymap != b.keymap && a.keyboard_state != b.keyboard_state, ++ "lanes share owned map resources"); ++ check(a.keymap_text == b.keymap_text, "lanes advertise different layouts"); ++ for (Lane* lane : {&a, &b}) { ++ struct stat status{}; ++ check(fstat(lane->keymap_fd, &status) == 0 && status.st_size == (off_t)lane->keymap_text.size() + 1, ++ "keymap fd is not terminated serialized map"); ++ std::string text(status.st_size, '\0'); ++ check(pread(lane->keymap_fd, text.data(), text.size(), 0) == status.st_size, "keymap fd cannot be read"); ++ check(text == lane->keymap_text + '\0', "keymap fd differs from advertised map"); ++ check(fcntl(lane->keymap_fd, F_GETFD) & FD_CLOEXEC, "keymap fd inherited by exec"); ++ const int seals = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL; ++ check((fcntl(lane->keymap_fd, F_GET_SEALS) & seals) == seals, "keymap fd not immutable"); ++ const int fd = lane->keymap_fd; ++ lane->initialize_agent_keymap(); ++ check(lane->keymap_fd == fd, "initialization replaced advertised map"); ++ check(xkb_state_key_get_one_sym(lane->keyboard_state, 21 + 8) == XKB_KEY_y, ++ "background lane did not initialize US map"); ++ } ++ xkb_state_update_key(a.keyboard_state, 42 + 8, XKB_KEY_DOWN); ++ check(xkb_state_key_get_one_sym(a.keyboard_state, 30 + 8) == XKB_KEY_A, "lane modifier not applied"); ++ check(xkb_state_key_get_one_sym(b.keyboard_state, 30 + 8) == XKB_KEY_a, "lane modifier leaked"); ++} ++'''.replace('// INITIALIZER', body.group()) ++ compiler = shlex.split(os.environ.get('CXX', 'c++')) ++ flags = shlex.split(subprocess.check_output(['pkg-config', '--cflags', '--libs', 'xkbcommon'], text=True)) ++ with tempfile.TemporaryDirectory(prefix='cua-agent-keymap-') as directory: ++ cpp, binary = Path(directory) / 'fixture.cpp', Path(directory) / 'fixture' ++ cpp.write_text(fixture) ++ build = subprocess.run([*compiler, '-std=c++20', '-Wall', '-Wextra', '-Wpedantic', '-Werror', ++ '-I', str(ROOT / 'src'), str(cpp), '-o', str(binary), *flags], ++ capture_output=True, text=True, timeout=60) ++ self.assertEqual(build.returncode, 0, build.stdout + build.stderr) ++ result = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10) ++ self.assertEqual(result.returncode, 0, result.stdout + result.stderr) ++ ++ ++if __name__ == '__main__': ++ unittest.main() +diff --git a/tests/desktop_fault_policy_fixture.cpp b/tests/desktop_fault_policy_fixture.cpp +index 45c48ef..36be139 100644 +--- a/tests/desktop_fault_policy_fixture.cpp ++++ b/tests/desktop_fault_policy_fixture.cpp +@@ -1,6 +1,7 @@ + // Used by desktop_fault_policy_test.py, which inserts actual production bodies. + // Transport effects are counted here; native protocol/app behavior is separate. + #include "drag_geometry.hpp" ++#include "keyboard_layout.hpp" + #include "input_grant.hpp" + #include "passive_pointer_target.hpp" + +@@ -18,6 +19,7 @@ struct Window {}; + struct Surface { int client() const { return 1; } }; + using Target = PassivePointerTarget, std::weak_ptr>; + struct Client { ++ InputRoute route = InputRoute::independent; + bool dead = false; + void* source = nullptr; + int fd = -1; +@@ -34,8 +36,6 @@ struct Pointer { + }; + struct Drag { Client* client; DragGeometry geometry{1}; }; + struct Trace { void mark(const char*, unsigned) {} }; +-enum class ForegroundFailureReason { none }; +-struct ForegroundFailure { ForegroundFailureReason reason; }; + void wl_event_source_remove(void*) {} + int close(int) { return 0; } + std::string refusal(std::string_view reason) { return std::string(reason); } +@@ -79,7 +79,7 @@ struct Lane { + drag.emplace(lease); + } + bool available() const { return !suspended && session; } +- bool layout_qualified() const { return layout; } ++ bool layout_qualified(InputRoute route, uint64_t cap) const { return keyboard_layout_ready(route, cap, layout, layout); } + bool refresh(Client&) const { return refresh_ok; } + template bool primary_conflict(const T&) const { return primary_busy; } + template bool agent_conflict(const T&) const { return peer_busy; } +@@ -122,7 +122,7 @@ int main() { + for (bool session_fault : {true, false}) { + for (int path = 0; path < 3; ++path) { + Lane l; +- if (session_fault) l.session = false; else l.layout = false; ++ if (session_fault) l.session = false; else { l.layout = false; l.capabilities = 2; } + if (path == 0) l.guard_targets(); + if (path == 1) l.target_refusal(*l.lease); + if (path == 2) l.action_refusal(*l.lease); +@@ -133,6 +133,19 @@ int main() { + l.session = true; l.layout = true; l.guard_targets(); l.check_inert(); + } + } ++ // Pointer admission, dispatch and ongoing drag ignore keyboard layout readiness. ++ for (auto route : {InputRoute::independent, InputRoute::primary_foreground}) { ++ for (uint64_t cap : {1, 4, 8, 16}) { ++ Lane l; l.layout = false; l.lease->route = route; l.capabilities = cap; ++ l.target_refusal(*l.lease, route, cap); ++ check(l.lease && l.responses.empty(), "pointer admission gated on layout"); ++ l.action_refusal(*l.lease, cap); ++ check(l.lease && l.responses.empty(), "pointer dispatch gated on layout"); ++ l.guard_targets(); ++ check(l.lease && l.drag && l.held_button && l.responses.empty(), ++ "pointer operation was gated by keyboard layout"); ++ } ++ } + // Every passive safety guard still applies during either desktop fault. + for (bool session_fault : {true, false}) { + for (int bad = 0; bad < 8; ++bad) { +diff --git a/tests/desktop_fault_policy_test.py b/tests/desktop_fault_policy_test.py +index 68ca6c8..7a4d1b4 100644 +--- a/tests/desktop_fault_policy_test.py ++++ b/tests/desktop_fault_policy_test.py +@@ -35,10 +35,11 @@ def fixture(source): + ): + block = source.split(start, 1)[1].split(end, 1)[0] + refusals = [line.strip() for line in block.splitlines() +- if 'if (' in line and ('!available()' in line or '!layout_qualified()' in line)] ++ if 'if (' in line and ('!available()' in line or '!layout_qualified(' in line)] + if len(refusals) != 2: + raise AssertionError(f'production refusal branches not found: {label}') +- methods += '\nvoid ' + label + '(Client& c) {\n' + '\n'.join(refusals) + '\n}' ++ params = ', InputRoute route = InputRoute::independent, uint64_t requested_cap = 2' if label == 'target_refusal' else ', uint64_t cap = 2' ++ methods += '\nvoid ' + label + '(Client& c' + params + ') {\n' + '\n'.join(refusals) + '\n}' + return (ROOT / 'tests/desktop_fault_policy_fixture.cpp').read_text().replace( + '// PRODUCTION_METHODS', methods) + +diff --git a/tests/foreground_modifiers_test.py b/tests/foreground_modifiers_test.py +new file mode 100644 +index 0000000..013c9bf +--- /dev/null ++++ b/tests/foreground_modifiers_test.py +@@ -0,0 +1,225 @@ ++"""Exercise production foreground state admission, dispatch and unwind with XKB.""" ++import os ++from pathlib import Path ++import re ++import shlex ++import subprocess ++import tempfile ++import unittest ++ ++ROOT = Path(__file__).resolve().parents[1] ++ ++ ++class ForegroundModifiersTest(unittest.TestCase): ++ def test_production_modifiers(self): ++ source = (ROOT / 'src/input_experiment.cpp').read_text() ++ methods = [] ++ for name in ('capture_foreground_modifiers', 'require_foreground', 'finish_foreground', ++ 'start_foreground', 'foreground_key'): ++ body = re.search(r'^ \S[^\n]*\b' + name + r'\(.*?^ }', source, re.M | re.S) ++ self.assertIsNotNone(body, name) ++ methods.append(body.group()) ++ preflight = re.search(r' const auto modifiers = capture_foreground_modifiers\(command == "KEY"\);.*?' ++ r' start_foreground\([^\n]*;', source, re.S) ++ self.assertIsNotNone(preflight) ++ fixture = r''' ++#include "keyboard_layout.hpp" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++using namespace cua::hyprland; ++using Clock = std::chrono::steady_clock; ++void check(bool ok, const char* why) { if (!ok) { std::cerr << why; std::exit(1); } } ++struct Vector2D { double x, y; }; ++constexpr int WL_KEYBOARD_KEY_STATE_PRESSED=1, WL_KEYBOARD_KEY_STATE_RELEASED=0; ++constexpr int WL_POINTER_BUTTON_STATE_RELEASED=0; ++struct Root : std::enable_shared_from_this { ++ bool good() const { return true; } ++ int client() const { return 0; } ++ auto at(Vector2D, bool) { return std::pair{shared_from_this(), 0}; } ++}; ++struct Resource { ++ xkb_state* client = nullptr; ++ std::vector symbols; ++ std::array last{}; ++ std::vector> history; ++ bool good() const { return true; } ++ void sendKey(unsigned, unsigned code, unsigned down) { ++ if (down) symbols.push_back(xkb_state_key_get_one_sym(client, code + 8)); ++ } ++ void sendMods(unsigned d, unsigned l, unsigned k, unsigned g) { ++ last={d,l,k,g}; history.push_back(last); xkb_state_update_mask(client,d,l,k,0,0,g); ++ } ++ void sendButton(unsigned, unsigned, unsigned) {} ++ void sendFrame() {} ++}; ++struct Keyboard { ++ struct { unsigned depressed=0,latched=0,locked=0,group=0; } m_modifiersState; ++ xkb_keymap* m_xkbKeymap = nullptr; ++ bool m_enabled=true, shared=true, virt=false; ++ bool shareStates() const { return shared; } ++ bool isVirtual() const { return virt; } ++}; ++struct Seat { ++ std::vector> m_keyboards, m_pointers; ++ bool good() const { return true; } ++}; ++struct SeatManager { ++ std::weak_ptr m_keyboard; ++ bool m_mouse=true; ++ struct { std::shared_ptr keyboardFocus, pointerFocus; } m_state; ++ std::shared_ptr seat; ++ auto seatResourceForClient(int) { return seat; } ++ void setPointerFocus(std::shared_ptr root, Vector2D) { m_state.pointerFocus=root; } ++} manager, *g_pSeatManager=&manager; ++struct InputManager { ++ std::vector> m_keyboards; ++ bool shouldIgnoreVirtualKeyboard(const std::shared_ptr&) { return false; } ++} input, *g_pInputManager=&input; ++namespace Desktop { ++constexpr int FOCUS_REASON_OTHER=0; ++struct Focus { ++ std::shared_ptr root; ++ std::shared_ptr change_on_focus; ++ auto window() { return root; } ++ auto surface() { return root; } ++ void fullWindowFocus(std::shared_ptr, int, std::shared_ptr r) { ++ root=r; manager.m_state.keyboardFocus=r; ++ if (change_on_focus) change_on_focus->m_modifiersState.locked=0; ++ } ++} focus; ++auto focusState() { return &focus; } ++} ++namespace Pointer { struct Manager { void warpTo(Vector2D) {} } pointer; auto mgr(){ return &pointer; } } ++struct Client { ++ bool dead=false, foreground_attempted=false; ++ std::weak_ptr surface, window; ++ std::array geometry{}; ++}; ++struct Lane { ++ xkb_keymap* physical_keymap=nullptr; ++ xkb_keymap* keymap=nullptr; ++ xkb_state* physical_state=nullptr; ++ std::array foreground_modifiers{}; ++ std::vector> foreground_keyboards, foreground_pointers; ++ std::weak_ptr foreground_surface; ++ std::weak_ptr foreground_seat; ++ std::vector held_keys; ++ unsigned held_button=0; ++ bool foreground_started=false,foreground_activating=false,foreground_keyboard_used=false; ++ bool foreground_needs_pointer=false, layout_ready=true, physical_held=false; ++ Client* lease=nullptr; ++ Clock::time_point expires=Clock::now()+std::chrono::hours(1); ++ unsigned consumed=0, refused=0; ++ bool available() { return true; } ++ bool physical_layout_ready() { return layout_ready; } ++ bool point(Client&, double,double) { return true; } ++ unsigned event_ms() { return 0; } ++ void foreground_motion(Client&,double,double) {} ++ ForegroundGuard foreground_guard(Client&) { ++ return {.exact_root=true,.physical_keys=physical_held,.exact_keyboard_focus=true,.exact_pointer_focus=true}; ++ } ++ void revoke(const char*, bool) {} ++ auto refusal(const char*) { return std::string{}; } ++ void send(Client&, const std::string&) { ++refused; } ++ bool consume_grant(Client&, unsigned) { ++consumed; return true; } ++ // METHODS ++ void preflight(Client& c, unsigned code, unsigned mods) { ++ const std::string command="KEY"; ++ const unsigned cap=2; ++ const double x=0,y=0; ++ // PREFLIGHT ++ } ++ ~Lane() { if (physical_state) xkb_state_unref(physical_state); } ++}; ++int main() { ++ auto context=xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ const xkb_rule_names names{"evdev","pc105","us","","ctrl:nocaps"}; ++ auto physical=xkb_keymap_new_from_names(context,&names,XKB_KEYMAP_COMPILE_NO_FLAGS); ++ auto canonical=agent_keymap(context); ++ auto keyboard=std::make_shared(); keyboard->m_xkbKeymap=physical; ++ manager.m_keyboard=keyboard; input.m_keyboards={keyboard}; ++ auto root=std::make_shared(); ++ manager.m_state.keyboardFocus=root; manager.m_state.pointerFocus=root; Desktop::focus.root=root; ++ manager.seat=std::make_shared(); auto resource=std::make_shared(); ++ resource->client=xkb_state_new(physical); manager.seat->m_keyboards={resource}; ++ Client client; client.surface=root; client.window=root; ++ Lane lane; lane.physical_keymap=physical; lane.keymap=canonical; ++ lane.physical_state=xkb_state_new(physical); lane.lease=&client; ++ const auto num=foreground_numlock_mask(physical); ++ keyboard->m_modifiersState.locked=num; ++ lane.preflight(client,30,1); ++ check(lane.foreground_started && lane.foreground_modifiers[2]==num && resource->last[2]==num, ++ "actual Num Lock state was not captured and published before input"); ++ lane.foreground_key(client,42,true); lane.foreground_key(client,30,true); ++ lane.foreground_key(client,30,false); lane.foreground_key(client,42,false); ++ check(resource->symbols.back()==XKB_KEY_A,"client did not receive intended shifted text"); ++ for (const auto& state : resource->history) ++ check(state[2]==num,"dispatch transiently cleared Num Lock"); ++ lane.finish_foreground(); ++ check(resource->last==std::array{0,0,num,0} && keyboard->m_modifiersState.locked==num, ++ "completion changed real or client Num Lock state"); ++ const auto consumed=lane.consumed; ++ lane.preflight(client,79,0); ++ check(lane.refused==1 && lane.consumed==consumed && !lane.foreground_started, ++ "caller checked neutral state and admitted Num Lock keypad semantics"); ++ keyboard->m_modifiersState.locked=0; ++ lane.preflight(client,79,0); lane.foreground_key(client,79,true); lane.foreground_key(client,79,false); ++ check(resource->symbols.back()==XKB_KEY_KP_End,"neutral keypad contract changed"); lane.finish_foreground(); ++ keyboard->m_modifiersState.locked=num; lane.preflight(client,30,1); lane.foreground_key(client,42,true); ++ keyboard->m_modifiersState.locked=0; ++ try { lane.foreground_key(client,30,true); check(false,"ambient state change accepted"); } ++ catch (ForegroundFailure f) { check(f.reason==ForegroundFailureReason::keyboard_state,"wrong state change refusal"); } ++ lane.finish_foreground(); check(resource->last==std::array{},"cancellation restored stale Num Lock"); ++ keyboard->m_modifiersState.locked=num; lane.preflight(client,30,1); lane.foreground_key(client,42,true); ++ keyboard->m_modifiersState.locked=xkb_keymap_mod_get_mask(physical,XKB_MOD_NAME_CAPS); ++ keyboard->m_modifiersState.depressed=xkb_keymap_mod_get_mask(physical,XKB_MOD_NAME_CTRL); ++ lane.finish_foreground(); ++ check(resource->last[0]==keyboard->m_modifiersState.depressed && resource->last[2]==keyboard->m_modifiersState.locked, ++ "cancellation failed to restore current human Caps and held Control"); ++ keyboard->m_modifiersState.depressed=0; keyboard->m_modifiersState.locked=num; ++ lane.physical_held=true; ++ try { lane.preflight(client,30,0); check(false,"held physical key admitted"); } ++ catch (ForegroundFailure f) { check(f.reason==ForegroundFailureReason::physical_keys,"wrong held-key refusal"); } ++ lane.physical_held=false; ++ auto shared=std::make_shared(); shared->m_xkbKeymap=physical; ++ shared->m_modifiersState.locked=num; ++ keyboard->m_modifiersState.locked=0; ++ input.m_keyboards.push_back(shared); ++ lane.preflight(client,30,0); ++ check(lane.foreground_modifiers[2]==num && resource->last[2]==num,"shared Num Lock was ignored"); ++ lane.foreground_key(client,30,true); lane.foreground_key(client,30,false); lane.finish_foreground(); ++ check(resource->last[2]==num,"shared Num Lock not restored"); ++ shared->m_modifiersState.locked=xkb_keymap_mod_get_mask(physical,XKB_MOD_NAME_CAPS); ++ try { lane.preflight(client,30,0); check(false,"shared Caps state admitted"); } ++ catch (ForegroundFailure f) { check(f.reason==ForegroundFailureReason::keyboard_locked,"wrong shared lock refusal"); } ++ shared->shared=false; lane.preflight(client,30,0); lane.finish_foreground(); ++ // Focus callbacks can change real state between preflight and first event. ++ keyboard->m_modifiersState.locked=num; ++ Desktop::focus.root.reset(); Desktop::focus.change_on_focus=keyboard; ++ try { lane.preflight(client,30,0); check(false,"focus-time state change accepted"); } ++ catch (ForegroundFailure f) { check(f.reason==ForegroundFailureReason::keyboard_state,"wrong focus change refusal"); } ++ lane.finish_foreground(); ++ check(resource->last[2]==0,"focus-time cancellation restored stale lock"); ++ xkb_state_unref(resource->client); xkb_keymap_unref(physical); xkb_keymap_unref(canonical); xkb_context_unref(context); ++} ++'''.replace('// METHODS', '\n'.join(methods)).replace('// PREFLIGHT', preflight.group()) ++ compiler = shlex.split(os.environ.get('CXX', 'c++')) ++ flags = shlex.split(subprocess.check_output(['pkg-config', '--cflags', '--libs', 'xkbcommon'], text=True)) ++ with tempfile.TemporaryDirectory(prefix='cua-foreground-modifiers-') as directory: ++ cpp, binary = Path(directory) / 'fixture.cpp', Path(directory) / 'fixture' ++ cpp.write_text(fixture) ++ build = subprocess.run([*compiler, '-std=c++20', '-Wall', '-Wextra', '-Wpedantic', '-Werror', ++ '-I', str(ROOT / 'src'), str(cpp), '-o', str(binary), *flags], ++ capture_output=True, text=True, timeout=60) ++ self.assertEqual(build.returncode, 0, build.stdout + build.stderr) ++ result = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10) ++ self.assertEqual(result.returncode, 0, result.stdout + result.stderr) ++ ++ ++if __name__ == '__main__': ++ unittest.main() +diff --git a/tests/keyboard_layout_test.cpp b/tests/keyboard_layout_test.cpp +new file mode 100644 +index 0000000..6d57616 +--- /dev/null ++++ b/tests/keyboard_layout_test.cpp +@@ -0,0 +1,130 @@ ++#include "keyboard_layout.hpp" ++#include ++#include ++#include ++ ++using namespace cua::hyprland; ++void check(bool condition, const char* message) { ++ if (!condition) { std::cerr << message << '\n'; std::exit(1); } ++} ++int main() { ++ using Context = std::unique_ptr; ++ using Map = std::unique_ptr; ++ using State = std::unique_ptr; ++ Context context{xkb_context_new(XKB_CONTEXT_NO_FLAGS), xkb_context_unref}; ++ Map agent{agent_keymap(context.get()), xkb_keymap_unref}; ++ Map second{agent_keymap(context.get()), xkb_keymap_unref}; ++ check(bool(agent) && bool(second), "canonical maps unavailable"); ++ const auto map = [&](const char* layout, const char* options) { ++ const xkb_rule_names names{"evdev", "pc105", layout, "", options}; ++ return Map{xkb_keymap_new_from_names(context.get(), &names, XKB_KEYMAP_COMPILE_NO_FLAGS), xkb_keymap_unref}; ++ }; ++ auto stock = map("us", "compose:caps,shift:both_capslock_cancel"); ++ auto nocaps = map("us", "ctrl:nocaps"); ++ auto swapctrl = map("us", "ctrl:swapcaps"); ++ auto swapalt = map("us", "altwin:swap_alt_win"); ++ auto german = map("de", ""); ++ check(stock && nocaps && swapctrl && swapalt && german, "test keymaps unavailable"); ++ for (auto* physical : {stock.get(), nocaps.get(), swapctrl.get(), swapalt.get(), german.get()}) { ++ check(foreground_chord_compatible(physical, agent.get(), 30, 0, {}), "unrelated remap blocked A"); ++ check(foreground_chord_compatible(physical, agent.get(), 30, 1, {}), "unrelated remap blocked Shift+A"); ++ check(foreground_chord_compatible(physical, agent.get(), 28, 0, {}), "unrelated remap blocked Return"); ++ } ++ check(foreground_chord_compatible(stock.get(), agent.get(), 30, 2, {}), "stock Omarchy blocked Ctrl+A"); ++ check(foreground_chord_compatible(nocaps.get(), agent.get(), 30, 2, {}), "Caps to Ctrl blocked Ctrl+A"); ++ check(!foreground_chord_compatible(swapctrl.get(), agent.get(), 30, 2, {}), "Ctrl/Caps swap sent wrong Ctrl+A"); ++ check(!foreground_chord_compatible(swapctrl.get(), agent.get(), 29, 0, {}), "remapped modifier key accepted"); ++ check(!foreground_chord_compatible(swapalt.get(), agent.get(), 30, 4, {}), "Alt/Super swap sent wrong Alt+A"); ++ check(!foreground_chord_compatible(swapalt.get(), agent.get(), 30, 8, {}), "Alt/Super swap sent wrong Super+A"); ++ check(!foreground_chord_compatible(german.get(), agent.get(), 21, 0, {}), "German Z accepted as US Y"); ++ check(!foreground_chord_compatible(german.get(), agent.get(), 3, 1, {}), "German shifted punctuation accepted"); ++ check(!foreground_chord_compatible(nullptr, agent.get(), 30, 0, {}), "missing physical map accepted"); ++ for (auto* physical : {agent.get(), stock.get(), nocaps.get()}) { ++ const auto numlock = foreground_numlock_mask(physical); ++ check(numlock != 0, "Num Lock encoding missing"); ++ for (const auto locked : {0u, numlock}) { ++ const std::array ambient{0, 0, locked, 0}; ++ for (const auto key : {30u, 48u, 44u, 2u, 11u, 28u, 57u}) ++ for (const auto mods : {0u, 1u, 2u, 3u}) ++ check(foreground_chord_compatible(physical, agent.get(), key, mods, ambient), ++ "Num Lock blocked ordinary text or shortcut"); ++ for (const auto key : {71u, 72u, 75u, 79u, 82u, 83u}) ++ check(foreground_chord_compatible(physical, agent.get(), key, 0, ambient) == !locked, ++ "Num Lock keypad semantic change was ignored"); ++ } ++ const auto caps = xkb_keymap_mod_get_mask(physical, XKB_MOD_NAME_CAPS); ++ for (const auto locked : {caps, caps | numlock, 0x80000000u}) ++ check(!foreground_chord_compatible(physical, agent.get(), 30, 0, {0, 0, locked, 0}), ++ "unsupported lock accepted"); ++ check(!foreground_chord_compatible(physical, agent.get(), 30, 0, {numlock, 0, numlock, 0}), ++ "held modifier accepted with Num Lock"); ++ check(!foreground_chord_compatible(physical, agent.get(), 30, 0, {0, numlock, numlock, 0}), ++ "latched modifier accepted with Num Lock"); ++ check(!foreground_chord_compatible(physical, agent.get(), 30, 0, {0, 0, numlock, 1}), ++ "nonzero group accepted with Num Lock"); ++ } ++ const auto numlock = foreground_numlock_mask(agent.get()); ++ check(!foreground_chord_compatible(swapctrl.get(), agent.get(), 30, 2, {0, 0, numlock, 0}), ++ "Num Lock hid Ctrl remap"); ++ check(!foreground_chord_compatible(german.get(), agent.get(), 21, 0, {0, 0, numlock, 0}), ++ "Num Lock hid layout mismatch"); ++ // All supported wire chords remain compatible on the independent map. ++ for (unsigned code = 1; code <= 247; ++code) ++ if (code != 58 && code != 69 && code != 70) ++ for (unsigned mods = 0; mods < 16; ++mods) ++ check(foreground_chord_compatible(agent.get(), agent.get(), code, mods, {}), "canonical chord rejected"); ++ // Two lanes never share modifier state and never mutate the human state. ++ State first{xkb_state_new(agent.get()), xkb_state_unref}; ++ State other{xkb_state_new(second.get()), xkb_state_unref}; ++ State human{xkb_state_new(german.get()), xkb_state_unref}; ++ xkb_state_update_key(first.get(), 42 + 8, XKB_KEY_DOWN); ++ check(xkb_state_key_get_one_sym(first.get(), 30 + 8) == XKB_KEY_A, "agent shift not active"); ++ check(xkb_state_key_get_one_sym(other.get(), 30 + 8) == XKB_KEY_a, "agent shift leaked across lanes"); ++ check(xkb_state_key_get_one_sym(human.get(), 21 + 8) == XKB_KEY_z, "human layout changed"); ++ // Same symbols can hide a changed XKB action. Simulate an A that sets Mod3. ++ char* serialized = xkb_keymap_get_as_string(agent.get(), XKB_KEYMAP_FORMAT_TEXT_V1); ++ std::string changed = serialized; ++ std::free(serialized); ++ const auto at = changed.find("key "); ++ const auto end = changed.find(';', at); ++ check(at != std::string::npos && end != std::string::npos, "test action fixture unavailable"); ++ changed.replace(at, end - at + 1, ++ "key { type=\"ALPHABETIC\", symbols[Group1]=[a,A], actions[Group1]=[LockMods(modifiers=Mod3),LockMods(modifiers=Mod3)] };"); ++ Map lock{xkb_keymap_new_from_string(context.get(), changed.c_str(), XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS), xkb_keymap_unref}; ++ check(bool(lock), "action fixture failed to compile"); ++ check(!foreground_chord_compatible(lock.get(), agent.get(), 30, 0, {}), "hidden lock action accepted"); ++ check(!foreground_chord_compatible(lock.get(), agent.get(), 30, 0, {0, 0, numlock, 0}), ++ "Num Lock hid an unexpected lock action"); ++ // Equal symbols and modifier state can still alter toolkit shortcut matching. ++ serialized = xkb_keymap_get_as_string(agent.get(), XKB_KEYMAP_FORMAT_TEXT_V1); ++ changed = serialized; ++ std::free(serialized); ++ const auto type_at = changed.find("type \"ALPHABETIC\""); ++ const auto type_end = changed.find("};", type_at); ++ check(type_at != std::string::npos && type_end != std::string::npos, "key type fixture unavailable"); ++ changed.insert(type_end, "preserve[Shift] = Shift;\n"); ++ Map preserved{xkb_keymap_new_from_string(context.get(), changed.c_str(), XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS), xkb_keymap_unref}; ++ check(bool(preserved), "preserved modifier fixture failed to compile"); ++ check(!foreground_chord_compatible(preserved.get(), agent.get(), 30, 1, {}), "consumed modifier mismatch accepted"); ++ check(!foreground_chord_compatible(preserved.get(), agent.get(), 30, 1, {0, 0, numlock, 0}), ++ "Num Lock hid changed shortcut consumption"); ++ // A modifier that preserves Shift state but emits another symbol must fail. ++ serialized = xkb_keymap_get_as_string(agent.get(), XKB_KEYMAP_FORMAT_TEXT_V1); ++ changed = serialized; ++ std::free(serialized); ++ const auto shift_at = changed.find("key "); ++ const auto shift_end = changed.find(';', shift_at); ++ check(shift_at != std::string::npos && shift_end != std::string::npos, "shift fixture unavailable"); ++ changed.replace(shift_at, shift_end - shift_at + 1, ++ "key { symbols[Group1]=[Delete], actions[Group1]=[SetMods(modifiers=Shift)] };"); ++ Map badshift{xkb_keymap_new_from_string(context.get(), changed.c_str(), XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS), xkb_keymap_unref}; ++ check(bool(badshift), "shift fixture failed to compile"); ++ check(!foreground_chord_compatible(badshift.get(), agent.get(), 30, 1, {}), "modifier press symbols not checked"); ++ for (auto route : {InputRoute::independent, InputRoute::primary_foreground}) { ++ for (uint64_t cap : {1, 4, 8, 16}) check(keyboard_layout_ready(route, cap, false, false), "pointer gated on layout"); ++ check(!keyboard_layout_ready(route, 2, false, false), "key accepted without map"); ++ } ++ check(keyboard_layout_ready(InputRoute::independent, 2, true, false), "background depends on human map"); ++ check(!keyboard_layout_ready(InputRoute::primary_foreground, 2, true, false), "foreground uses agent readiness"); ++ std::cout << "keyboard layout tests passed\n"; ++} +diff --git a/tests/plugin_input_lifetime_test.cpp b/tests/plugin_input_lifetime_test.cpp +index ff09451..456da44 100644 +--- a/tests/plugin_input_lifetime_test.cpp ++++ b/tests/plugin_input_lifetime_test.cpp +@@ -51,12 +51,17 @@ int main() { + check(setenv("XDG_RUNTIME_DIR", directory, 1) == 0 && + setenv("HYPRLAND_INSTANCE_SIGNATURE", "mock", 1) == 0, + "select runtime"); +- Config::Values::configured_bool_override = true; ++ Config::Values::configured_bool_override = false; + static_cast(pluginInit(nullptr)); + const auto toggle = [](bool enabled) { + HyprlandAPI::registered_bool->set_mock_value(enabled); + Event::bus()->m_events.config.reloaded.emit(); + }; ++#ifdef CUA_HYPRLAND_INPUT ++ const auto initial_status = HyprlandAPI::registered_legacy_command->fn(FORMAT_JSON, {}); ++ check(created == 0 && initial_status.find("\"configured\":false") != std::string::npos && initial_status.find("\"keyboard_layout_independent\":true") != std::string::npos && initial_status.find("\"foreground_numlock_compatible\":true") != std::string::npos, ++ "disabled production module advertises compiled keyboard support before enable"); ++#endif + for (unsigned i = 0; i < 20; ++i) { + toggle(true); + #ifdef CUA_HYPRLAND_INPUT +@@ -64,6 +69,8 @@ int main() { + check(status.find("\"state\":\"input_v3_candidate\"") != std::string::npos && + status.find("trusted_local_per_action") != std::string::npos && + status.find("\"input\":{}") != std::string::npos && ++ status.find("\"keyboard_layout_independent\":true") != std::string::npos && ++ status.find("\"foreground_numlock_compatible\":true") != std::string::npos && + status.find("operator") == std::string::npos, + "v3 status advertises its actual admission mode"); + #endif diff --git a/pkgbuilds/cursor-bin/.omarchy/package.json b/pkgbuilds/cursor-bin/.omarchy/package.json index 8461d51..7c63fb1 100644 --- a/pkgbuilds/cursor-bin/.omarchy/package.json +++ b/pkgbuilds/cursor-bin/.omarchy/package.json @@ -1,5 +1,20 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "c5073d62f60f3973a14340eb1b40fc5b5a4ea9f0" + "upstream": { + "watch": { + "json": "https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable", + "path": "version", + "fields": { + "commit": "commitSha" + }, + "variables": { + "_commit": "{commit}" + } + } + }, + "origin": { + "aur": "cursor-bin", + "commit": "a87a0de17b0d8176ee4f046499b1e6e4b37cc422" + } } diff --git a/pkgbuilds/cursor-bin/PKGBUILD b/pkgbuilds/cursor-bin/PKGBUILD index 14325f6..d0a3e32 100644 --- a/pkgbuilds/cursor-bin/PKGBUILD +++ b/pkgbuilds/cursor-bin/PKGBUILD @@ -1,8 +1,8 @@ # Maintainer: Gunther Schulz pkgname=cursor-bin -pkgver=3.19.13 -pkgrel=1 +pkgver=3.20.21 +pkgrel=2 pkgdesc='AI-first coding environment' arch=('x86_64') url="https://www.cursor.com" @@ -12,15 +12,11 @@ _electron=electron42 depends=(xdg-utils ripgrep $_electron nodejs 'gcc-libs' 'hicolor-icon-theme' 'libxkbfile') options=(!strip !debug) # Don't break ext of VSCode -_commit=dd066f332fcea7382764400fde902f61920648d5 +_commit=f09fca384ceca23f7bf21f9c23655b162641d747 source=("https://downloads.cursor.com/production/${_commit}/linux/x64/deb/amd64/deb/cursor_${pkgver}_amd64.deb" "https://gitlab.archlinux.org/archlinux/packaging/packages/code/-/raw/main/code."{sh,mjs} rg.sh) -sha512sums=('SKIP' - '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' - '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' - 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a') -sha512sums[0]=3c952035fa1809caded7059b7addbb01a9143cd248ecff27cdb3cb3af47d4bc824ed60dfd9757e105a342e53280e075d81f775790295a3272818bab5a994b305 +sha512sums=('8329138d207309d16410f1cb58da18eea1a034a35f2bdd022ef9b373bb8f1b3d80e489e65256b7283c40e10da77962d158bfa3a64e742337a942633810d80dbe' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a') noextract=(cursor_${pkgver}_amd64.deb) # avoid double tarball _app=usr/share/cursor/resources/app package() { diff --git a/pkgbuilds/cursor-cli/.omarchy/package.json b/pkgbuilds/cursor-cli/.omarchy/package.json index 3d6de68..bc8c3d2 100644 --- a/pkgbuilds/cursor-cli/.omarchy/package.json +++ b/pkgbuilds/cursor-cli/.omarchy/package.json @@ -1,5 +1,16 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "954e5556aa88f2309b86992b231c8b76f273cfb0" + "upstream": { + "watch": { + "regex": "https://cursor.com/install", + "pattern": "https://downloads\\.cursor\\.com/lab/(?P[0-9]{4}\\.[0-9]{2}\\.[0-9]{2})-(?P[a-f0-9]+)/", + "version": "{version}.1.{hash}", + "sequence": true + } + }, + "origin": { + "aur": "cursor-cli", + "commit": "954e5556aa88f2309b86992b231c8b76f273cfb0" + } } diff --git a/pkgbuilds/cursor-cli/PKGBUILD b/pkgbuilds/cursor-cli/PKGBUILD index aaa1270..14869ab 100644 --- a/pkgbuilds/cursor-cli/PKGBUILD +++ b/pkgbuilds/cursor-cli/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Ismet Togay # Contributor: Christopher Cooper pkgname=cursor-cli -pkgver=2026.08.25.1.3e8eec8 +pkgver=2026.09.10.1.fd3934a # Upstream is YYYY.MM.DD-. pkgver cannot contain hyphens, and hashes are # not monotonically ordered, so pkgver is YYYY.MM.DD..: n resets to 1 # on a new date and increments when the same date gets a new hash. @@ -25,8 +25,8 @@ source_x86_64=("cursor-cli-${_upstream_ver}-x86_64.tar.gz::https://downloads.cur source_aarch64=("cursor-cli-${_upstream_ver}-aarch64.tar.gz::https://downloads.cursor.com/lab/${_upstream_ver}/linux/arm64/agent-cli-package.tar.gz") b2sums=('d241ee9895bdb1c17514438fde8528222a8f2326568bd7a033d7a1b11432ce6b4575ff1a50625764bfe6bc6f8a9dc060f7439c3be7e95f8fd02912cdd37a011d' '1928e04c713e13911ea607f84c3e4a2fed1f76af9795503811078f43d2b53c753e28b2233e553fc17e766831800fb0dbc272aad2a80b387f95ba6071d7d4116a') -b2sums_x86_64=('cd5485f7524688e1a688daa2b64669c76bedcdd9ab87638bac78f9b42c2442bd5000559920a2f5171e00b5eb7fcf737f9111ef296f1eba40369cebf3279ee0a9') -b2sums_aarch64=('191ff1c538f294134d93d501e9ca68cc6d4f8101cb1cee449753dfefcd9039daecd7bf9f9f2baf9ee9262f40eea19aaf15f834c9abb56d967fb48a3a3f23b8ea') +b2sums_x86_64=('121c0128fd630565c7000b86ae53bf76e73fd72c1ab8ba2509fae7739b1c7f4bed0587a82137340303ac4704f3344cf63a10eab0e8bea262c8e48bbb21bcb742') +b2sums_aarch64=('bfc0f540190214396df3cbb93c411ab8055677bc1dd0b0e76d1b914d6c6d90af12519434227ba8b38a38249f1bfe0a8848f47e16dc048b4fa005d366815307be') prepare() { # Block cursor-agent auto-updates by making its versions directory diff --git a/pkgbuilds/dbxcli-bin/.omarchy/package.json b/pkgbuilds/dbxcli-bin/.omarchy/package.json index e8d76bc..53217cb 100644 --- a/pkgbuilds/dbxcli-bin/.omarchy/package.json +++ b/pkgbuilds/dbxcli-bin/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "80626b9efefc215d55eede4330d56c017a522682" + "source": "local", + "upstream": { + "watch": { + "github": "dropbox/dbxcli", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "dbxcli-bin", + "commit": "80626b9efefc215d55eede4330d56c017a522682" + } } diff --git a/pkgbuilds/dbxcli-bin/PKGBUILD b/pkgbuilds/dbxcli-bin/PKGBUILD index f9665c1..6fc0489 100644 --- a/pkgbuilds/dbxcli-bin/PKGBUILD +++ b/pkgbuilds/dbxcli-bin/PKGBUILD @@ -2,7 +2,7 @@ _pkgname="dbxcli" pkgname="${_pkgname}-bin" -pkgver=3.7.2 +pkgver=3.7.3 pkgrel=1 pkgdesc="A command line client for Dropbox built using the Go SDK" arch=( @@ -33,9 +33,9 @@ source_armv7h=( source_x86_64=( "${url}/releases/download/v${pkgver}/${_pkgname}_${pkgver}_linux_amd64.tar.gz" ) -sha256sums_aarch64=('fc451469c87ad0e4f2d3201f6e36f2b57c1119e5c0adb5ebaec6182b3eb378ad') -sha256sums_armv7h=('22f21d8b40dd23b5777ffb0ec07696d135d2910daa84f46679231a573aeb9899') -sha256sums_x86_64=('1b1fa67fb3d3f6e2940566afdb84f072a21804ddfdb4f0dfade385ec0683ee63') +sha256sums_aarch64=('9d654da62a1ac10c9e32ee8f66fa6cc8d88ed29bc95555435a5ce4255eb4b96a') +sha256sums_armv7h=('8067cee274dc2f062a06ceda26200c44d9251336ec235f7d7a3826743c9a379e') +sha256sums_x86_64=('fee977ce4144174356cd7d1bae0b546aecad2570f14666bd44417e96af943484') prepare() { local source_array="source_${CARCH}[0]" diff --git a/pkgbuilds/dotnet-core-bin/.omarchy/package.json b/pkgbuilds/dotnet-core-bin/.omarchy/package.json new file mode 100644 index 0000000..db663bc --- /dev/null +++ b/pkgbuilds/dotnet-core-bin/.omarchy/package.json @@ -0,0 +1,19 @@ +{ + "source": "local", + "origin": { + "aur": "dotnet-core-bin", + "commit": "2c499d7ce634efb8e93eee4c4239490b02e98e09" + }, + "upstream": { + "watch": { + "json": "https://builds.dotnet.microsoft.com/dotnet/release-metadata/10.0/releases.json", + "path": "latest-sdk", + "fields": { + "runtime": "latest-runtime" + }, + "variables": { + "_runtimever": "{runtime}" + } + } + } +} diff --git a/pkgbuilds/dotnet-core-bin/PKGBUILD b/pkgbuilds/dotnet-core-bin/PKGBUILD new file mode 100644 index 0000000..040344b --- /dev/null +++ b/pkgbuilds/dotnet-core-bin/PKGBUILD @@ -0,0 +1,131 @@ +# Maintainer: Attila Greguss +# Co-Maintainer: Nate Plumm + +pkgbase=dotnet-core-bin +pkgname=( + 'dotnet-host-bin' + 'aspnet-runtime-bin' + 'dotnet-runtime-bin' + 'dotnet-sdk-bin' + 'dotnet-targeting-pack-bin' + 'aspnet-targeting-pack-bin' + ) +# Version the split family by SDK release; dependencies expose runtime versions. +pkgver=10.0.401 +_runtimever=10.0.12 +_sdkver=$pkgver +_short_ver=10.0 +pkgrel=1 +arch=('x86_64' 'armv7h' 'aarch64') +url='https://www.microsoft.com/net/core' +license=('MIT') +options=('staticlibs') +source=('dotnet.sh') +source_armv7h=("https://builds.dotnet.microsoft.com/dotnet/Sdk/${_sdkver}/dotnet-sdk-${_sdkver}-linux-arm.tar.gz") +source_aarch64=("https://builds.dotnet.microsoft.com/dotnet/Sdk/${_sdkver}/dotnet-sdk-${_sdkver}-linux-arm64.tar.gz") +source_x86_64=("https://builds.dotnet.microsoft.com/dotnet/Sdk/${_sdkver}/dotnet-sdk-${_sdkver}-linux-x64.tar.gz") +sha512sums=('768151c7179fb6a126b3de9cae01e363e8894f6fab384b1e2c5066c2adca4578638983b1b62aea10dd18045e6d6e8f8ea13280481134de94f004a118919b2c06') +sha512sums_armv7h=('94a8a52862ca9f0de1075a468d6e4e307a1d4463098a9e8266e66939709a812b0126e212cce7e8c6feae6b078d86c8b77e088814a0e32531edb0da0a1ce90c11') +sha512sums_aarch64=('58ace73ced6b4360754689a686bdfb8a317f4da6cb8bb416dbc7d0ba9f47e43e3c09f5eb1f1a1cfaacbd10df9558da4882bf2a5e195d6ab56a02c1f9f76102ed') +sha512sums_x86_64=('51c8b999af9e8dd9998c9edc5944e19a90788862068acd38694e098889054ce8c23d4f0c5cccfa16bf187d044562359e5ee69a9f8ad0bbe913ba90311fbce25b') + +# Keep each split package's notices usable when installed independently. +_install_license() { + install -Dm644 LICENSE.txt "$pkgdir/usr/share/licenses/$pkgname/LICENSE.txt" + install -Dm644 ThirdPartyNotices.txt "$pkgdir/usr/share/licenses/$pkgname/ThirdPartyNotices.txt" +} + +package_dotnet-host-bin() { + pkgdesc='A generic driver for the .NET Core Command Line Interface (binary)' + provides=("dotnet-host" "dotnet-host=${_runtimever}") + conflicts=('dotnet-host') + depends=( + 'libgcc' + 'libstdc++' + 'glibc' + ) + + install -dm 755 "${pkgdir}"/usr/{bin,lib,share/{dotnet,dnx}} + cp -dr --no-preserve='ownership' dotnet host dnx "${pkgdir}"/usr/share/dotnet/ + _install_license + ln -sf /usr/share/dotnet/dotnet "${pkgdir}"/usr/bin/dotnet + ln -sf /usr/share/dotnet/dnx "${pkgdir}"/usr/bin/dnx + ln -sf /usr/share/dotnet/host/fxr/"${_runtimever}"/libhostfxr.so "${pkgdir}"/usr/lib/libhostfxr.so + install -Dm 644 "${srcdir}"/dotnet.sh -t "${pkgdir}"/etc/profile.d/ +} + +package_dotnet-runtime-bin() { + pkgdesc='The .NET Core runtime (binary)' + depends=( + "dotnet-host>=${_runtimever}" + 'libgcc' + 'libstdc++' + 'glibc' + 'icu' + 'libunwind' + 'zlib' + 'openssl' + ) + optdepends=('lttng-ust2.12: CoreCLR tracing') + provides=("dotnet-runtime=${_runtimever}" "dotnet-runtime-${_short_ver}") + conflicts=("dotnet-runtime=${_runtimever}" "dotnet-runtime-${_short_ver}") + + install -dm 755 "${pkgdir}"/usr/share/{dotnet/shared,licenses} + cp -dr --no-preserve='ownership' shared/Microsoft.NETCore.App "${pkgdir}"/usr/share/dotnet/shared/ + _install_license +} + +package_aspnet-runtime-bin() { + pkgdesc='The ASP.NET Core runtime (binary)' + depends=('dotnet-runtime-bin') + provides=("aspnet-runtime=${_runtimever}" "aspnet-runtime-${_short_ver}") + conflicts=("aspnet-runtime=${_runtimever}" "aspnet-runtime-${_short_ver}") + + install -dm 755 "${pkgdir}"/usr/share/{dotnet/shared,licenses} + cp -dr --no-preserve='ownership' shared/Microsoft.AspNetCore.App "${pkgdir}"/usr/share/dotnet/shared/ + _install_license +} + +package_dotnet-sdk-bin() { + pkgdesc='The .NET Core SDK (binary)' + depends=( + 'glibc' + 'libgcc' + 'libstdc++' + 'dotnet-runtime-bin' + 'dotnet-targeting-pack-bin' + 'aspnet-runtime-bin' + 'aspnet-targeting-pack-bin' + ) + provides=("dotnet-sdk-bin" "dotnet-sdk=${pkgver}" "dotnet-sdk-${_short_ver}=${pkgver}") + conflicts=("dotnet-sdk-bin" "dotnet-sdk=${pkgver}" "dotnet-sdk-${_short_ver}") + + install -dm 755 "${pkgdir}"/usr/share/{dotnet,licenses} + cp -dr --no-preserve='ownership' sdk sdk-manifests templates "${pkgdir}"/usr/share/dotnet/ + _install_license +} + +package_dotnet-targeting-pack-bin() { + pkgdesc='The .NET Core targeting pack (binary)' + provides=(dotnet-targeting-pack=${_runtimever} dotnet-targeting-pack-${_short_ver}) + conflicts=(dotnet-targeting-pack=${_runtimever} dotnet-targeting-pack-${_short_ver}) + + if [ $CARCH = 'x86_64' ]; then msarch=x64; + elif [ $CARCH = 'armv7h' ]; then msarch=arm; + elif [ $CARCH = 'aarch64' ]; then msarch=arm64; fi + + install -dm 755 "${pkgdir}"/usr/share/{dotnet,dotnet/packs,licenses} + cp -dr --no-preserve='ownership' packs/Microsoft.NETCore.App.{Host.linux-${msarch},Ref} "${pkgdir}"/usr/share/dotnet/packs/ + _install_license +} + +package_aspnet-targeting-pack-bin() { + pkgdesc='The ASP.NET Core targeting pack (binary)' + depends=(dotnet-targeting-pack-bin) + provides=(aspnet-targeting-pack=${_runtimever} aspnet-targeting-pack-${_short_ver}) + conflicts=(aspnet-targeting-pack=${_runtimever} aspnet-targeting-pack-${_short_ver}) + + install -dm 755 "${pkgdir}"/usr/share/{dotnet,dotnet/packs,licenses} + cp -dr --no-preserve='ownership' packs/Microsoft.AspNetCore.App.Ref "${pkgdir}"/usr/share/dotnet/packs/ + _install_license +} diff --git a/pkgbuilds/dotnet-core-bin/dotnet.sh b/pkgbuilds/dotnet-core-bin/dotnet.sh new file mode 100755 index 0000000..48b6ee6 --- /dev/null +++ b/pkgbuilds/dotnet-core-bin/dotnet.sh @@ -0,0 +1,19 @@ +# Set location for AppHost lookup +[ -z "$DOTNET_ROOT" ] && export DOTNET_ROOT=/usr/share/dotnet + +# Add dotnet directory to PATH, according to docs it must be added, plus VSCode C# Dev Kit doesn't work without this. +# See https://learn.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual#set-environment-variables-system-wide +case "$PATH" in + *"$DOTNET_ROOT"* ) true ;; + * ) PATH="$PATH:$DOTNET_ROOT" ;; +esac + +# Add dotnet tools directory to PATH +[ -z "$DOTNET_TOOLS_PATH" ] && export DOTNET_TOOLS_PATH="$HOME/.dotnet/tools" +case "$PATH" in + *"$DOTNET_TOOLS_PATH"* ) true ;; + * ) PATH="$PATH:$DOTNET_TOOLS_PATH" ;; +esac + +# Extract self-contained executables under HOME to avoid multi-user issues from using the default '/var/tmp' +[ -z "$DOTNET_BUNDLE_EXTRACT_BASE_DIR" ] && export DOTNET_BUNDLE_EXTRACT_BASE_DIR="${XDG_CACHE_HOME:-"$HOME"/.cache}/dotnet_bundle_extract" diff --git a/pkgbuilds/dropbox-cli/.omarchy/package.json b/pkgbuilds/dropbox-cli/.omarchy/package.json index 407efd8..aaeee66 100644 --- a/pkgbuilds/dropbox-cli/.omarchy/package.json +++ b/pkgbuilds/dropbox-cli/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "b67e475d16f4e061a16af53dae212a46d9bc3ea9" + "upstream": { + "watch": { + "regex": "https://linux.dropbox.com/packages/", + "pattern": "nautilus-dropbox-(?P[0-9]{4}\\.[0-9]{2}\\.[0-9]{2})\\.tar\\.bz2" + } + }, + "origin": { + "aur": "dropbox-cli", + "commit": "b67e475d16f4e061a16af53dae212a46d9bc3ea9" + } } diff --git a/pkgbuilds/dropbox-cli/.omarchy/patches/x86-only.patch b/pkgbuilds/dropbox-cli/.omarchy/patches/x86-only.patch deleted file mode 100644 index 2448ae4..0000000 --- a/pkgbuilds/dropbox-cli/.omarchy/patches/x86-only.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -11,7 +11,7 @@ pkgname=dropbox-cli - pkgver=2024.04.17 - pkgrel=2 - pkgdesc="Command line interface for Dropbox" --arch=("any") -+arch=("x86_64") - url="https://www.dropbox.com" - license=("GPL-3.0-or-later") - makedepends=("gdk-pixbuf2") diff --git a/pkgbuilds/dropbox-cli/PKGBUILD b/pkgbuilds/dropbox-cli/PKGBUILD index 1372a2c..ddd7504 100644 --- a/pkgbuilds/dropbox-cli/PKGBUILD +++ b/pkgbuilds/dropbox-cli/PKGBUILD @@ -6,8 +6,8 @@ # Contributor: carstene1ns pkgname=dropbox-cli -pkgver=2024.04.17 -pkgrel=2.1 +pkgver=2026.05.06 +pkgrel=1 pkgdesc="Command line interface for Dropbox" arch=("x86_64") url="https://www.dropbox.com" @@ -18,8 +18,7 @@ optdepends=("gtk3: Dropbox update GUI" "python-gpgme: verify binary signature") source=("https://linux.dropbox.com/packages/nautilus-dropbox-${pkgver}.tar.bz2" "dropboxd-fallback.patch") -sha256sums=('a6a098cf16aa4747f40816ac793d59e37e8ae3b7080d0b30611d6c2b8663f2c1' - '711ed63c6dfccfd05c6e9abaa291be9ac3c3909e84f788f568cb44dee2d48229') +sha256sums=('c9d7ef418ccb0f34adc7cdda1952110be4fbbc788a79cdeb2cfd63e070bb3961' '711ed63c6dfccfd05c6e9abaa291be9ac3c3909e84f788f568cb44dee2d48229') prepare() { cd "nautilus-dropbox-${pkgver}" diff --git a/pkgbuilds/dropbox/.omarchy/package.json b/pkgbuilds/dropbox/.omarchy/package.json index fba5030..9250abe 100644 --- a/pkgbuilds/dropbox/.omarchy/package.json +++ b/pkgbuilds/dropbox/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "6379feda6f36bbbd496c97f040e4f71c5a1dcec7" + "upstream": { + "watch": { + "redirect": "https://www.dropbox.com/download?plat=lnx.x86_64", + "pattern": "dropbox-lnx\\.x86_64-(?P[0-9]+\\.4\\.[0-9]+)\\.tar\\.gz" + } + }, + "origin": { + "aur": "dropbox", + "commit": "6379feda6f36bbbd496c97f040e4f71c5a1dcec7" + } } diff --git a/pkgbuilds/dropbox/PKGBUILD b/pkgbuilds/dropbox/PKGBUILD index 80f6d9e..5e052aa 100644 --- a/pkgbuilds/dropbox/PKGBUILD +++ b/pkgbuilds/dropbox/PKGBUILD @@ -4,7 +4,7 @@ # Contributor: David Manouchehri pkgname=dropbox -pkgver=264.4.3421 +pkgver=270.4.3312 pkgrel=1 pkgdesc="A free service that lets you bring your photos, docs, and videos anywhere and share them easily." arch=("x86_64") @@ -27,12 +27,7 @@ source=("DropboxGlyph_Blue.svg" "dropbox@.service" "https://edge.dropboxstatic.com/dbx-releng/client/dropbox-lnx.x86_64-$pkgver.tar.gz"{,.asc}) -sha256sums=('9ba76205ec5838db85d822f23cfd7e2112fd2757e8031d8374709f102143c548' - '1610ff57e8b20ee7a37682c3cc505da4ddc9cec2bd7234c90c0f2073657521d2' - '6c67a9c8c95c08fafafd2f1d828074b13e3347b05d2e4f4bf4e62746115d7477' - '98581e65a91ae1f19ed42edcdaaa52e102298b5da0d71b50089393d364474d3d' - '4aa06821de43b5e1cf4f27f83cb5f0bca82d01107c758091d7895f0d723f5411' - 'SKIP') +sha256sums=('9ba76205ec5838db85d822f23cfd7e2112fd2757e8031d8374709f102143c548' '1610ff57e8b20ee7a37682c3cc505da4ddc9cec2bd7234c90c0f2073657521d2' '6c67a9c8c95c08fafafd2f1d828074b13e3347b05d2e4f4bf4e62746115d7477' '98581e65a91ae1f19ed42edcdaaa52e102298b5da0d71b50089393d364474d3d' '35404957d2a15dcac998d53cbec692d5236e197493f6c009accd91ea9aa8f34c' 'SKIP') # The PGP key fingerprint should match the one on https://www.dropbox.com/help/desktop-web/linux-commands validpgpkeys=( '1C61A2656FB57B7E4DE0F4C1FC918B335044912E' # Dropbox Automatic Signing Key diff --git a/pkgbuilds/elephant-all/.omarchy/package.json b/pkgbuilds/elephant-all/.omarchy/package.json index b45dcf3..0b3c0a1 100644 --- a/pkgbuilds/elephant-all/.omarchy/package.json +++ b/pkgbuilds/elephant-all/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "5614a0a61616643e448fb7c68d58237715ce2739" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-all", + "commit": "5614a0a61616643e448fb7c68d58237715ce2739" + } } diff --git a/pkgbuilds/elephant-archlinuxpkgs/.omarchy/package.json b/pkgbuilds/elephant-archlinuxpkgs/.omarchy/package.json index f886656..a020358 100644 --- a/pkgbuilds/elephant-archlinuxpkgs/.omarchy/package.json +++ b/pkgbuilds/elephant-archlinuxpkgs/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "659b81f1aa74a13fd2ebec222e19da2046d8e977" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-archlinuxpkgs", + "commit": "659b81f1aa74a13fd2ebec222e19da2046d8e977" + } } diff --git a/pkgbuilds/elephant-bluetooth/.omarchy/package.json b/pkgbuilds/elephant-bluetooth/.omarchy/package.json index e3a0f17..d08f83f 100644 --- a/pkgbuilds/elephant-bluetooth/.omarchy/package.json +++ b/pkgbuilds/elephant-bluetooth/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "42d9dd5424884c51b8fe6bf7692caf0a31007f05" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-bluetooth", + "commit": "42d9dd5424884c51b8fe6bf7692caf0a31007f05" + } } diff --git a/pkgbuilds/elephant-calc/.omarchy/package.json b/pkgbuilds/elephant-calc/.omarchy/package.json index b5f3ca0..94f4a46 100644 --- a/pkgbuilds/elephant-calc/.omarchy/package.json +++ b/pkgbuilds/elephant-calc/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "84aba9e90bbd65af45f83168cd6c7bce6ec4322e" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-calc", + "commit": "84aba9e90bbd65af45f83168cd6c7bce6ec4322e" + } } diff --git a/pkgbuilds/elephant-clipboard/.omarchy/package.json b/pkgbuilds/elephant-clipboard/.omarchy/package.json index f7e54fa..1634055 100644 --- a/pkgbuilds/elephant-clipboard/.omarchy/package.json +++ b/pkgbuilds/elephant-clipboard/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "3176f9de1445e7115009383f9cdac116729fc7be" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-clipboard", + "commit": "3176f9de1445e7115009383f9cdac116729fc7be" + } } diff --git a/pkgbuilds/elephant-desktopapplications/.omarchy/package.json b/pkgbuilds/elephant-desktopapplications/.omarchy/package.json index 3800fd9..e83c756 100644 --- a/pkgbuilds/elephant-desktopapplications/.omarchy/package.json +++ b/pkgbuilds/elephant-desktopapplications/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "c30e33db5fef912dec9aa157773b10ffab1e0307" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-desktopapplications", + "commit": "c30e33db5fef912dec9aa157773b10ffab1e0307" + } } diff --git a/pkgbuilds/elephant-files/.omarchy/package.json b/pkgbuilds/elephant-files/.omarchy/package.json index 851441c..b7f4bb9 100644 --- a/pkgbuilds/elephant-files/.omarchy/package.json +++ b/pkgbuilds/elephant-files/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "2d16502b7a905e7d7a03e0026c5519c3b3c4abf2" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-files", + "commit": "2d16502b7a905e7d7a03e0026c5519c3b3c4abf2" + } } diff --git a/pkgbuilds/elephant-menus/.omarchy/package.json b/pkgbuilds/elephant-menus/.omarchy/package.json index f33dba7..81b6353 100644 --- a/pkgbuilds/elephant-menus/.omarchy/package.json +++ b/pkgbuilds/elephant-menus/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "5ab583fee6ba3e387d49ffe4e409bb84bc60ea24" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-menus", + "commit": "5ab583fee6ba3e387d49ffe4e409bb84bc60ea24" + } } diff --git a/pkgbuilds/elephant-providerlist/.omarchy/package.json b/pkgbuilds/elephant-providerlist/.omarchy/package.json index eb32c10..5646aba 100644 --- a/pkgbuilds/elephant-providerlist/.omarchy/package.json +++ b/pkgbuilds/elephant-providerlist/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "1d756a138e926a81b9d33265f0197b8661168845" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-providerlist", + "commit": "1d756a138e926a81b9d33265f0197b8661168845" + } } diff --git a/pkgbuilds/elephant-runner/.omarchy/package.json b/pkgbuilds/elephant-runner/.omarchy/package.json index 9d14cb3..f8d45c4 100644 --- a/pkgbuilds/elephant-runner/.omarchy/package.json +++ b/pkgbuilds/elephant-runner/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "e47641530d912199372204cf8780ef37f99f0b37" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-runner", + "commit": "e47641530d912199372204cf8780ef37f99f0b37" + } } diff --git a/pkgbuilds/elephant-symbols/.omarchy/package.json b/pkgbuilds/elephant-symbols/.omarchy/package.json index c0525b3..2b2eeec 100644 --- a/pkgbuilds/elephant-symbols/.omarchy/package.json +++ b/pkgbuilds/elephant-symbols/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "32263e8b71390ab38b8aa1fd82fc2595b41a5157" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-symbols", + "commit": "32263e8b71390ab38b8aa1fd82fc2595b41a5157" + } } diff --git a/pkgbuilds/elephant-todo/.omarchy/package.json b/pkgbuilds/elephant-todo/.omarchy/package.json index 134635f..4a63e04 100644 --- a/pkgbuilds/elephant-todo/.omarchy/package.json +++ b/pkgbuilds/elephant-todo/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "447978df5ea3afd1e10959d7973c0b35367724c3" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-todo", + "commit": "447978df5ea3afd1e10959d7973c0b35367724c3" + } } diff --git a/pkgbuilds/elephant-unicode/.omarchy/package.json b/pkgbuilds/elephant-unicode/.omarchy/package.json index ed0012a..8b9efa1 100644 --- a/pkgbuilds/elephant-unicode/.omarchy/package.json +++ b/pkgbuilds/elephant-unicode/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "23222b0d304a234c74f630c5b9176d58c6c6e0b3" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-unicode", + "commit": "23222b0d304a234c74f630c5b9176d58c6c6e0b3" + } } diff --git a/pkgbuilds/elephant-websearch/.omarchy/package.json b/pkgbuilds/elephant-websearch/.omarchy/package.json index 2d9ea6f..e5f3fe6 100644 --- a/pkgbuilds/elephant-websearch/.omarchy/package.json +++ b/pkgbuilds/elephant-websearch/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "6b5da831da33e3533593823826a8ffb1a008a299" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant-websearch", + "commit": "6b5da831da33e3533593823826a8ffb1a008a299" + } } diff --git a/pkgbuilds/elephant/.omarchy/package.json b/pkgbuilds/elephant/.omarchy/package.json index 4f1debd..13506dc 100644 --- a/pkgbuilds/elephant/.omarchy/package.json +++ b/pkgbuilds/elephant/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "6dd02e6987a7a91be6a33e9600147523efa7fa5a" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/elephant", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "elephant", + "commit": "6dd02e6987a7a91be6a33e9600147523efa7fa5a" + } } diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD index 0fbc8d4..a525033 100644 --- a/pkgbuilds/flea/PKGBUILD +++ b/pkgbuilds/flea/PKGBUILD @@ -1,8 +1,8 @@ # Maintainer: GM pkgname=flea -pkgver=0.1.5 -pkgrel=1 +pkgver=0.2.1 +pkgrel=3 pkgdesc='Fast, keyboard-first file manager for Omarchy' arch=('x86_64' 'aarch64') url='https://github.com/thisisgm/flea' @@ -19,6 +19,8 @@ depends=( 'gvfs-nfs' 'gvfs-smb' 'hicolor-icon-theme' + 'kimageformats' + 'libheif' 'omarchy' 'python' 'python-gobject' @@ -46,7 +48,7 @@ options=('!debug') source=( "$url/releases/download/v$pkgver/$pkgname-v$pkgver.tar.gz" ) -sha256sums=('5f9b6591e2a912625055c25d44d20bd5a6ebb94f4cf5d3c03fddd4b114beb480') +sha256sums=('75f9ac0274a09a0d55cf7d9187943983c1b78a9e443465738b3ac8af8f2a77e9') build() { cd "$pkgname-$pkgver" @@ -69,6 +71,8 @@ check() { --proc /proc --dev /dev --tmpfs /tmp /usr/bin/true >/dev/null 2>&1; then printf 'Skipping sandbox integration tests: this builder cannot create an unprivileged namespace\n' test_args+=( + --skip backend::archiveops::tests::a_silent_failure_names_the_operation_that_was_running + --skip tui::job::tests::preview_worker_reads_held_source_in_readonly_sandbox --skip backend::archiveops::tests::a_tool_that_exits_zero_writing_nothing_is_caught_by_the_predicate --skip backend::archiveops::tests::an_empty_archive_extracts_to_an_empty_directory_and_that_is_success --skip backend::archiveops::tests::only_the_archive_root_extracts_to_nothing_while_nested_directories_do_not @@ -90,7 +94,45 @@ check() { ) fi - cargo test --frozen --release -- "${test_args[@]}" + # Recovery-lock tests are unreliable when run concurrently with other suites. + # These fixtures require O_TMPFILE, which the builder's /tmp filesystem may + # not provide. Keep executable fixtures in the remaining suites on the normal + # temp root (/dev/shm is noexec). Note that /dev/shm does NOT buy finer + # timestamps -- see the skip below. + local -a filesystem_tests=( + backend::menu_actions::tests:: + backend::menudelete::tests:: + backend::redo::tests:: + backend::trashbrowse::tests:: + backend::trashdelete:: + backend::trashmanifest::tests:: + ) + # redo_refuses_changed_sources_and_destination_collisions writes a file and + # then immediately asks redo to notice the edit. flea decides "changed" from + # ctime alone -- src/backend/undo.rs records (ctime, ctime_nsec) as the whole + # identity -- and this kernel stamps ctime from the coarse clock, so two + # writes microseconds apart share a timestamp and the guard sees no change. + # Measured on this builder: 196/200 back-to-back write pairs on /dev/shm and + # 192/200 on the root filesystem produced an identical ctime. The assertion + # therefore fails on any builder fast enough to stay inside one granule, and + # moving the suite to tmpfs does not help. Both halves are upstream bugs -- + # the test assumes a resolution the kernel never promised, and the identity it + # exercises cannot see a same-granule edit -- so skip it rather than paper + # over it by disabling the whole package. + local -a racy_ctime_tests=( + --skip backend::redo::tests::redo_refuses_changed_sources_and_destination_collisions + ) + + local test_tmp test_status=0 suite + test_tmp=$(mktemp -d /dev/shm/flea-tests.XXXXXXXX) || return 1 + TMPDIR="$test_tmp" cargo test --frozen --release -- \ + --test-threads=1 "${filesystem_tests[@]}" "${racy_ctime_tests[@]}" || test_status=$? + rm -rf -- "$test_tmp" + (( test_status == 0 )) || return "$test_status" + for suite in "${filesystem_tests[@]}"; do + test_args+=(--skip "$suite") + done + cargo test --frozen --release -- --test-threads=1 "${test_args[@]}" ./tests/js.sh ./tests/keymap-gen.sh } diff --git a/pkgbuilds/ghostty/.omarchy/package.json b/pkgbuilds/ghostty/.omarchy/package.json new file mode 100644 index 0000000..f9c4cf1 --- /dev/null +++ b/pkgbuilds/ghostty/.omarchy/package.json @@ -0,0 +1,10 @@ +{ + "source": "local", + "release_ring": "fast", + "upstream": { + "watch": { + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)", + "git_tags": "https://github.com/ghostty-org/ghostty.git" + } + } +} diff --git a/pkgbuilds/ghostty/PKGBUILD b/pkgbuilds/ghostty/PKGBUILD new file mode 100644 index 0000000..d0f0ed0 --- /dev/null +++ b/pkgbuilds/ghostty/PKGBUILD @@ -0,0 +1,123 @@ +# Ghostty for x86_64 and aarch64, built from the upstream release source tarball. +# +# Ghostty 1.3.x requires Zig 0.15.2 exactly. Distribution toolchains can move +# ahead, so use the verified upstream toolchain for each architecture only at +# package-build time rather than publishing a second Zig package. + +pkgbase=ghostty +pkgname=(ghostty ghostty-shell-integration ghostty-terminfo ghostty-nautilus) +pkgver=1.3.1 +pkgrel=3 +pkgdesc='Fast, native, feature-rich terminal emulator pushing modern features' +arch=(x86_64 aarch64) +url='https://github.com/ghostty-org/ghostty' +license=(MIT) +depends=( + bzip2 + fontconfig + freetype2 + glib2 + glibc + gtk4 + gtk4-layer-shell + harfbuzz + libadwaita + libpng + oniguruma + pixman + wayland + zlib +) +makedepends=( + blueprint-compiler + curl + gettext + pkgconf +) +_zigver=0.15.2 +_archive="$pkgbase-$pkgver" +source=( + "https://release.files.ghostty.org/$pkgver/$_archive.tar.gz" + 'build-data-llvm.patch' +) +sha256sums=( + '3349d25600ffbda281197a18314f7d18791969cffe9474f0ff16a45a9ebfccdb' + 'd9f5781b748651fa1ff7b919f4a79cd8570118faefe2848f64f02ea4220257ba' +) +source_x86_64=("https://ziglang.org/download/$_zigver/zig-x86_64-linux-$_zigver.tar.xz") +sha256sums_x86_64=('02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239') +source_aarch64=("https://ziglang.org/download/$_zigver/zig-aarch64-linux-$_zigver.tar.xz") +sha256sums_aarch64=('958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f') + +prepare() { + cd "$_archive" + # Zig's native x86 linker cannot read .sframe relocations in Arch's crt1.o. + # Use bundled LLVM for the build-data helper, as the main executable does. + if [[ "$CARCH" == x86_64 ]]; then + patch -Np1 -i "$srcdir/build-data-llvm.patch" + fi + PATH="$srcdir/zig-$CARCH-linux-$_zigver:$PATH" \ + ZIG_GLOBAL_CACHE_DIR="$srcdir/zig-global-cache" \ + ./nix/build-support/fetch-zig-cache.sh +} + +build() { + cd "$_archive" + # A '-' suffix is a SemVer prerelease and selects Ghostty's tip channel. + # Keep the package revision as build metadata on the stable release. + PATH="$srcdir/zig-$CARCH-linux-$_zigver:$PATH" \ + DESTDIR=build \ + zig build \ + --prefix /usr \ + --system "$srcdir/zig-global-cache/p" \ + -Doptimize=ReleaseFast \ + -Dgtk-x11=true \ + -Dcpu=baseline \ + -Dpie=true \ + -Demit-docs=false \ + -Dversion-string="$pkgver+omarchy.$pkgrel" \ + --build-id=sha1 +} + +package_ghostty() { + depends+=(ghostty-shell-integration ghostty-terminfo) + optdepends=('ghostty-nautilus: Open in Ghostty context menu in GNOME Files') + + cd "$_archive" + cp -a build/* "$pkgdir/" + install -Dm0644 LICENSE "$pkgdir/usr/share/licenses/ghostty/LICENSE" + rm -r "$pkgdir/usr/share/terminfo" \ + "$pkgdir/usr/share/ghostty/shell-integration" \ + "$pkgdir/usr/share/nautilus-python" +} + +package_ghostty-shell-integration() { + pkgdesc='Shell integration scripts for Ghostty' + depends=() + + cd "$_archive" + install -d "$pkgdir/usr/share/ghostty/shell-integration" + cp -a build/usr/share/ghostty/shell-integration/. \ + "$pkgdir/usr/share/ghostty/shell-integration/" + install -Dm0644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} + +package_ghostty-terminfo() { + pkgdesc='Terminfo for Ghostty' + depends=() + + cd "$_archive" + install -d "$pkgdir/usr/share/terminfo" + cp -a build/usr/share/terminfo/x "$pkgdir/usr/share/terminfo/" + install -Dm0644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} + +package_ghostty-nautilus() { + pkgdesc='Open in Ghostty for GNOME Files' + depends=(ghostty nautilus-python) + license=(GPL-2.0-or-later) + + cd "$_archive" + install -d "$pkgdir/usr/share/nautilus-python" + cp -a build/usr/share/nautilus-python/. "$pkgdir/usr/share/nautilus-python/" +} diff --git a/pkgbuilds/ghostty/build-data-llvm.patch b/pkgbuilds/ghostty/build-data-llvm.patch new file mode 100644 index 0000000..629f3d6 --- /dev/null +++ b/pkgbuilds/ghostty/build-data-llvm.patch @@ -0,0 +1,10 @@ +--- a/src/build/GhosttyResources.zig ++++ b/src/build/GhosttyResources.zig +@@ -15,6 +15,7 @@ + // This is the exe used to generate some build data. + const build_data_exe = b.addExecutable(.{ + .name = "ghostty-build-data", ++ .use_llvm = true, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main_build_data.zig"), + .target = b.graph.host, diff --git a/pkgbuilds/github-copilot-cli/PKGBUILD b/pkgbuilds/github-copilot-cli/PKGBUILD index 917dca5..97bc32b 100644 --- a/pkgbuilds/github-copilot-cli/PKGBUILD +++ b/pkgbuilds/github-copilot-cli/PKGBUILD @@ -6,7 +6,7 @@ _npmmodule=@github/copilot pkgname=github-copilot-cli _pkgexec=copilot -pkgver=1.0.83 +pkgver=1.0.85 pkgrel=1 pkgdesc="GitHub Copilot CLI brings the power of Copilot coding agent directly to your terminal." @@ -31,8 +31,8 @@ source=("https://registry.npmjs.org/${_npmmodule}/-/copilot-${pkgver}.tgz" noextract=("copilot-${pkgver}.tgz") sha256sums=( - '135506fc2b13163ab55dbf76a06e2fbcbad04ecac76b4e9c6659f0ba309e6a86' - '0c0064a10effac8adf9ad97338bafaa0d7d7d5bf191cc1c0384e05ff4366d36c' + 'd26e3c15310bdcdcc910ed223285bed8d68aaebce0d344f97224b5cfdaeeee36' + '1b1a8fbd5562df73684b6e94865837ceffd6609d61822c39e6c8fcbd32d8ac41' ) # Document: https://wiki.archlinux.org/title/Node.js_package_guidelines diff --git a/pkgbuilds/grok-bot/.omarchy/package.json b/pkgbuilds/grok-bot/.omarchy/package.json index a2a26fe..2e8b089 100644 --- a/pkgbuilds/grok-bot/.omarchy/package.json +++ b/pkgbuilds/grok-bot/.omarchy/package.json @@ -1,5 +1,8 @@ { - "source": "aur", + "source": "local", "sync": false, - "upstream_commit": "05eb78fca06b482affda28b26223cbf249d4bbcd" + "origin": { + "aur": "grok-bot", + "commit": "05eb78fca06b482affda28b26223cbf249d4bbcd" + } } diff --git a/pkgbuilds/grok-bot/PKGBUILD b/pkgbuilds/grok-bot/PKGBUILD index 1109333..bcc5797 100644 --- a/pkgbuilds/grok-bot/PKGBUILD +++ b/pkgbuilds/grok-bot/PKGBUILD @@ -2,14 +2,15 @@ # Contributor: Omarchy pkgname=grok-bot -pkgver=0.29.0 +pkgver=0.47.0 pkgrel=1 -_commit=f0e5bfcee649ea84c0c61369cf896cd146d72136 +_commit=c1e7d7a46549956d25f53e9c0b9f59666e03aa3a pkgdesc='Grok Bot desktop agent' -arch=('x86_64') +arch=('x86_64' 'aarch64') url='https://x.ai/bot' license=('custom') depends=( + 'alsa-lib' 'at-spi2-core' 'gtk3' 'hicolor-icon-theme' @@ -25,32 +26,36 @@ optdepends=('libappindicator-gtk3: tray support') provides=('sand') conflicts=('sand') options=('!strip' '!debug') +install=grok-bot.install + +_deb_x86_64="grok-bot_${pkgver}_amd64.deb" +_deb_aarch64="grok-bot_${pkgver}_arm64.deb" source=( - "${pkgname}_${pkgver}.deb::https://downloads.cursor.com/grokbot/stable/${_commit}/linux/x64/Grok_Bot_${pkgver}.deb" 'grok-bot.sh' 'grok-bot.desktop' ) -sha256sums=('d223b5830282aef11d5c46d8f4d1edd239bf992e336405cbd288d4476b9233d4' - '6dfa6c305941afa6cbaefbeaae06d05ab5a88f31630005d25a819a160c20c7a3' - '856056c9ca63dda5d01158ce8fb6a9a7cbb3f67c13a92b573cd196d3e50f26e7') -noextract=("${pkgname}_${pkgver}.deb") +source_x86_64=( + "${_deb_x86_64}::https://downloads.cursor.com/grokbot/stable/${_commit}/linux/x64/${_deb_x86_64}" +) +source_aarch64=( + "${_deb_aarch64}::https://downloads.cursor.com/grokbot/stable/${_commit}/linux/arm64/${_deb_aarch64}" +) +sha256sums=('6dfa6c305941afa6cbaefbeaae06d05ab5a88f31630005d25a819a160c20c7a3' + '3e2a2461ea58d17ac1777616be9ba660f7cb9ceefa9292016e36c55758bf78dd') +sha256sums_x86_64=('11ca0f51a535b97af51a352adf9c0f9ecd2e1b0430a69ae9451b688a7a065808') +sha256sums_aarch64=('836f8d19d3826c6573c31ac45c7a9b797abc73381ae0d2b1e7a8dae5410e7e46') +noextract=("${_deb_x86_64}" "${_deb_aarch64}") package() { - bsdtar -xOf "${srcdir}/${pkgname}_${pkgver}.deb" data.tar.xz | + local deb_var="_deb_${CARCH}" + local deb="${!deb_var}" + + bsdtar -xOf "${srcdir}/${deb}" data.tar.xz | bsdtar -x -C "${pkgdir}" -f - rm -rf "${pkgdir}/usr/share/doc" \ - "${pkgdir}/usr/share/applications/sand.desktop" - - local icon1024="${pkgdir}/usr/share/icons/hicolor/1024x1024/apps" - if [[ -f "${icon1024}/sand.png" && ! -f "${icon1024}/grok-bot.png" ]]; then - install -Dm644 "${icon1024}/sand.png" "${icon1024}/grok-bot.png" - fi - rm -f "${icon1024}/sand.png" - if [[ -f "${icon1024}/grok-bot.png" ]]; then - install -Dm644 "${icon1024}/grok-bot.png" \ - "${pkgdir}/usr/share/icons/hicolor/512x512/apps/grok-bot.png" - fi + "${pkgdir}/usr/share/applications/sand.desktop" \ + "${pkgdir}/usr/share/applications/grok-bot.desktop" # Always install our Wayland wrapper; do not keep any /usr/bin from the .deb. rm -f "${pkgdir}/usr/bin/grok-bot" "${pkgdir}/usr/bin/sand" @@ -64,9 +69,10 @@ package() { install -Dm644 "${pkgdir}/opt/Grok Bot/LICENSES.chromium.html" \ "${pkgdir}/usr/share/licenses/${pkgname}/LICENSES.chromium.html" - if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then - chmod 4755 "${pkgdir}/opt/Grok Bot/chrome-sandbox" - else - chmod 0755 "${pkgdir}/opt/Grok Bot/chrome-sandbox" - fi + # Ship chrome-sandbox without setuid. Upstream's build-time userns probe + # would measure the CI container, not the user's machine, and a setuid + # helper could not exec from "/opt/Grok Bot/" anyway (electron#44414). + # grok-bot.install tells users on kernels without unprivileged user + # namespaces how to run without the sandbox. + chmod 0755 "${pkgdir}/opt/Grok Bot/chrome-sandbox" } diff --git a/pkgbuilds/grok-bot/grok-bot.desktop b/pkgbuilds/grok-bot/grok-bot.desktop index 992c36c..ddda47a 100644 --- a/pkgbuilds/grok-bot/grok-bot.desktop +++ b/pkgbuilds/grok-bot/grok-bot.desktop @@ -8,6 +8,6 @@ Terminal=false Type=Application Categories=Development; MimeType=x-scheme-handler/grokbot;x-scheme-handler/sand; -StartupWMClass=Grok Bot +StartupWMClass=grok-bot StartupNotify=true Keywords=Grok;AI;Agent; diff --git a/pkgbuilds/grok-bot/grok-bot.install b/pkgbuilds/grok-bot/grok-bot.install new file mode 100644 index 0000000..11a7683 --- /dev/null +++ b/pkgbuilds/grok-bot/grok-bot.install @@ -0,0 +1,41 @@ +# Electron's renderer sandbox needs unprivileged user namespaces, or else a +# setuid-root chrome-sandbox. Upstream omarchy-pkgs probes for user namespaces +# inside package() and sets 4755 when they are missing. That is wrong twice +# for this repo: the build runs in a CI container where the probe fails, so +# every user would get the setuid helper; and the helper lives under +# "/opt/Grok Bot/", and Electron cannot exec a setuid chrome-sandbox from a +# path with a space (electron/electron#44414), so 4755 would not even work. +# +# The package therefore always ships chrome-sandbox as 0755. This hook only +# tells the user what to do on a host that lacks unprivileged user namespaces. +# The probe drops to nobody first: pacman runs hooks as root, and root can +# unshare a user namespace even where unprivileged users cannot. +_userns_available() { + [[ -L /proc/self/ns/user ]] || return 1 + if (( EUID == 0 )) && command -v setpriv >/dev/null; then + setpriv --reuid=65534 --regid=65534 --clear-groups -- unshare --user true 2>/dev/null + else + # Already unprivileged (or no setpriv): the direct probe is the real answer. + unshare --user true 2>/dev/null + fi +} + +_advise() { + _userns_available && return 0 + cat <<'MSG' +==> Unprivileged user namespaces are unavailable on this kernel, so Grok Bot's + renderer sandbox cannot start. A setuid chrome-sandbox is not an option + here: Electron cannot exec it from "/opt/Grok Bot/" (electron#44414). + To run without the sandbox, add this line to ~/.config/grok-bot-flags.conf: + + --no-sandbox +MSG +} + +post_install() { + _advise +} + +post_upgrade() { + _advise +} diff --git a/pkgbuilds/herdr/PKGBUILD b/pkgbuilds/herdr/PKGBUILD index 259df78..02cc4d3 100644 --- a/pkgbuilds/herdr/PKGBUILD +++ b/pkgbuilds/herdr/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: David Heinemeier Hansson pkgname=herdr -pkgver=0.8.2 +pkgver=0.9.0 pkgrel=1 pkgdesc="Herdr terminal workspace manager for AI coding agents" arch=('x86_64' 'aarch64') @@ -17,7 +17,7 @@ _zigver=0.15.2 source=("herdr-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") source_x86_64=("zig-x86_64-linux-$_zigver.tar.xz::https://ziglang.org/download/$_zigver/zig-x86_64-linux-$_zigver.tar.xz") source_aarch64=("zig-aarch64-linux-$_zigver.tar.xz::https://ziglang.org/download/$_zigver/zig-aarch64-linux-$_zigver.tar.xz") -sha256sums=('60453051025ee44ebf055d26cdaf665a0accd99a992cddd22c166a26c49cd161') +sha256sums=('1e83bff4b05834ed8281e16f1680e8f3e58375a94b2e3f2b3d021e28e293ef9a') sha256sums_x86_64=('02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239') sha256sums_aarch64=('958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f') diff --git a/pkgbuilds/hermes-desktop/.omarchy/package.json b/pkgbuilds/hermes-desktop/.omarchy/package.json index db153c3..2a9719d 100644 --- a/pkgbuilds/hermes-desktop/.omarchy/package.json +++ b/pkgbuilds/hermes-desktop/.omarchy/package.json @@ -1,4 +1,3 @@ { - "source": "local", - "release_ring": "fast" + "source": "local" } diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index 1f48dd9..bd41ea2 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -4,8 +4,8 @@ # so the upstream updater can rebuild and relaunch it in place. pkgname=hermes-desktop -pkgver=2026.8.31 -pkgrel=3 +pkgver=2026.9.7 +pkgrel=1 pkgdesc='Native desktop shell for Hermes Agent' arch=('x86_64') url='https://github.com/NousResearch/hermes-agent' @@ -65,7 +65,7 @@ options=('!strip' '!debug') # before falling back to `git rev-parse`. That fallback is wrong here: makepkg # builds inside this repository, so git ascends out of srcdir and stamps the # app with an omarchy-pkgs commit that means nothing upstream. -_commit=29112bef099274229cadff79cdff7bf7b99c4b77 +_commit=2237be355906fbe6065ce1815711eee52b2d646e _srcdir="hermes-agent-${pkgver}" source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz" @@ -74,12 +74,12 @@ source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz 'hermes-desktop.png' 'runtime.patch' 'runtime-test.py') -sha256sums=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217' +sha256sums=('907c2a72db1c5dd637ea8eeae97f4cb5b32cef615c17258f6b190924ec5bf688' '094d5f3191109a80eea9f23053b78a2e00dbecf90d62d1ca04c8e48866251469' '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52' - '03b67e26c234c797a6b1d4c9f34a57502dba37f462540e47ce6c88f6ea79302a' - '461e1120e7e6779f531c114d9926479e47fab79113d1b4646efea36bc771b3c5') + '9d5015d1be762a901f8f64319981ae862e9852fa5cb9a22a2ba1e691f90430a2' + '7337a12c71e8091ad5fc2e879e922c9cb1706c65f81b59d6dd70b12123dc7c00') build() { cd "${srcdir}/${_srcdir}" @@ -109,10 +109,10 @@ package() { install -Dm644 "${srcdir}/${_srcdir}/scripts/install.sh" \ "${pkgdir}/usr/share/${pkgname}/install.sh" - # Let the release's first updater relaunch with the user-namespace sandbox. + # Omarchy's installer still requires this patch. The release includes the + # fix, so the installer recognizes it through its reverse-apply check. install -Dm644 "${srcdir}/runtime.patch" "${pkgdir}/usr/share/${pkgname}/runtime.patch" - install -Dm644 "${srcdir}/hermes-desktop.desktop" \ "${pkgdir}/usr/share/applications/${pkgname}.desktop" diff --git a/pkgbuilds/hermes-desktop/runtime-test.py b/pkgbuilds/hermes-desktop/runtime-test.py index 79f0a43..cc08e07 100644 --- a/pkgbuilds/hermes-desktop/runtime-test.py +++ b/pkgbuilds/hermes-desktop/runtime-test.py @@ -14,7 +14,9 @@ with tempfile.TemporaryDirectory(prefix="hermes-runtime-check-") as temporary: destination = root / "scripts/desktop-update/posix.sh" destination.parent.mkdir(parents=True) shutil.copyfile(source / "scripts/desktop-update/posix.sh", destination) - subprocess.run(["git", "apply", str(patch_file.resolve())], cwd=root, check=True) + # Omarchy's installer requires the patch and accepts an upstreamed fix + # through its reverse check. Verify that path without changing the release. + subprocess.run(["git", "apply", "--reverse", "--check", str(patch_file.resolve())], cwd=root, check=True) home = root / "home with spaces" runtime = home / ".hermes/hermes-agent" @@ -42,13 +44,14 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({ module = runtime / "hermes_cli" module.mkdir() (module / "__init__.py").touch() - upstream = ast.parse((source / "hermes_cli/main.py").read_text()) + upstream = ast.parse((source / "hermes_cli/main_desktop.py").read_text()) option_parser = next(node for node in upstream.body if isinstance(node, ast.FunctionDef) and node.name == "_desktop_launch_options") - stores = next(node for node in upstream.body if isinstance(node, ast.Assign) - and any(isinstance(target, ast.Name) and target.id == "_LINUX_PASSWORD_STORES" - for target in node.targets)) - helper = "import os, shlex\n" + ast.unparse(stores) + "\n" + ast.unparse(option_parser) + "\n" + constants = [node for node in upstream.body if isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) + and target.id in ("_LINUX_PASSWORD_STORES", "_GPU_FLAG_WORDS") + for target in node.targets)] + helper = "import os, shlex\n" + "\n".join(map(ast.unparse, constants)) + "\n" + ast.unparse(option_parser) + "\n" (module / "main.py").write_text(helper) (module / "config.py").write_text('''import json, os from pathlib import Path diff --git a/pkgbuilds/hermes-desktop/runtime.patch b/pkgbuilds/hermes-desktop/runtime.patch index 8b7ed51..edb7d13 100644 --- a/pkgbuilds/hermes-desktop/runtime.patch +++ b/pkgbuilds/hermes-desktop/runtime.patch @@ -1,11 +1,13 @@ --- a/scripts/desktop-update/posix.sh +++ b/scripts/desktop-update/posix.sh -@@ -317,6 +317,8 @@ +@@ -327,6 +327,10 @@ + sb="$unpacked/chrome-sandbox" if [ ! -e "$sb" ]; then GATE=relaunch; return; fi if [ -u "$sb" ] && [ "$(stat -c %u "$sb" 2>/dev/null)" = "0" ]; then GATE=relaunch; return; fi - ++ # Namespace sandbox usable => Electron never consults the setuid helper, ++ # so a non-root chrome-sandbox does not block relaunch (mirrors the ++ # _desktop_linux_userns_sandbox_available() probe in hermes_cli/main.py). + if unshare --user --map-root-user true 2>/dev/null; then GATE=relaunch; return; fi -+ + case "${ELECTRON_DISABLE_SANDBOX:-}" in 1|true|TRUE|True) GATE=relaunch; return ;; esac [ "$SANDBOX_FALLBACK" -eq 1 ] && { GATE=relaunch; return; } - for arg in ${RELAUNCH_ARGS[@]+"${RELAUNCH_ARGS[@]}"}; do diff --git a/pkgbuilds/heroic-games-launcher-bin/.omarchy/package.json b/pkgbuilds/heroic-games-launcher-bin/.omarchy/package.json index 119be00..e245991 100644 --- a/pkgbuilds/heroic-games-launcher-bin/.omarchy/package.json +++ b/pkgbuilds/heroic-games-launcher-bin/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "44b4e963c69541700088a5719855d34f5cf16c85" + "source": "local", + "upstream": { + "watch": { + "github": "Heroic-Games-Launcher/HeroicGamesLauncher", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "heroic-games-launcher-bin", + "commit": "44b4e963c69541700088a5719855d34f5cf16c85" + } } diff --git a/pkgbuilds/heroic-games-launcher-bin/PKGBUILD b/pkgbuilds/heroic-games-launcher-bin/PKGBUILD index 18d37bb..ead38ab 100755 --- a/pkgbuilds/heroic-games-launcher-bin/PKGBUILD +++ b/pkgbuilds/heroic-games-launcher-bin/PKGBUILD @@ -2,16 +2,16 @@ # Maintainer: CommandMC pkgname=heroic-games-launcher-bin -pkgver=2.22.1 +pkgver=2.22.2 pkgrel=1 pkgdesc="An Open source Launcher for Epic, Amazon and GOG Games" arch=('x86_64') url="https://heroicgameslauncher.com/" license=('GPL-3.0-only') -_filename=Heroic-2.22.1-linux-x64.pacman -source=("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher/releases/download/v2.22.1/Heroic-2.22.1-linux-x64.pacman") -noextract=("Heroic-2.22.1-linux-x64.pacman") -sha256sums=(66ed041a93ac2817b744d3d0985194adfa408c8d35f64d9a8967fa5e2a58f2c1) +_filename=Heroic-${pkgver}-linux-x64.pacman +source=("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher/releases/download/v${pkgver}/${_filename}") +noextract=("${_filename}") +sha256sums=('4e4033ac70b8c407eaf70ce072e4e4d017200f07a3e88f7cfd4d3a597f3c09b8') options=(!strip) depends=( which @@ -27,4 +27,3 @@ package() { } # vim:set ts=2 sw=2 et: syntax=sh - diff --git a/pkgbuilds/hyprshade/.omarchy/package.json b/pkgbuilds/hyprshade/.omarchy/package.json index 56463ec..bdd892b 100644 --- a/pkgbuilds/hyprshade/.omarchy/package.json +++ b/pkgbuilds/hyprshade/.omarchy/package.json @@ -1,5 +1,13 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "89c710019789541e4cf972ee847f266534d6a96d" + "upstream": { + "watch": { + "pypi": "hyprshade" + } + }, + "origin": { + "aur": "hyprshade", + "commit": "89c710019789541e4cf972ee847f266534d6a96d" + } } diff --git a/pkgbuilds/hyprtoolkit/PKGBUILD b/pkgbuilds/hyprtoolkit/PKGBUILD index c6f1b49..2427e27 100644 --- a/pkgbuilds/hyprtoolkit/PKGBUILD +++ b/pkgbuilds/hyprtoolkit/PKGBUILD @@ -1,8 +1,8 @@ # Maintainer: Caleb Maclennan pkgname=hyprtoolkit -pkgver=0.5.4 -pkgrel=5.1 +pkgver=0.6.0 +pkgrel=1 pkgdesc='A modern C++ Wayland-native GUI toolkit' arch=(aarch64) url="https://github.com/hyprwm/$pkgname" @@ -31,7 +31,7 @@ makedepends=(cmake provides=(libhyprtoolkit.so) _archive="$pkgname-$pkgver" source=("$url/archive/v$pkgver/$_archive.tar.gz") -sha256sums=('2fb59789f231c1c4e9154ceffc1e7524c0cae154807c0d57e6166806255b570f') +sha256sums=('53c41be72af97d9ede274a63c9c1034c58726d862905763ed6e5a564ae42ba6b') build() { cd "$_archive" diff --git a/pkgbuilds/intel-ipu7-camera/0005-camhal-MediaControl-route-through-Intel-CVS-bridge.patch b/pkgbuilds/intel-ipu7-camera/0005-camhal-MediaControl-route-through-Intel-CVS-bridge.patch new file mode 100644 index 0000000..3438987 --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0005-camhal-MediaControl-route-through-Intel-CVS-bridge.patch @@ -0,0 +1,77 @@ +From f167239b3ecf242ae081767892cb0a4c54bad496 Mon Sep 17 00:00:00 2001 +From: Arun T +Date: Thu, 2 Jul 2026 06:19:35 +0530 +Subject: [PATCH] Enable ov08x40 CVS for support upstream cvs driver + +Linux 7.2 wires the Intel CVS (Computer Vision Sensing) controller into +the IPU media graph as a bridge entity named "Intel CVS" that sits between +the sensor and the IPU CSI2 receiver (ipu-bridge commit c6b1b34b5090, +in-tree driver drivers/media/i2c/cvs). Teach MediaControl to route the +sensor -> CSI2 link through that entity when it is present, and to find +the sensor's I2C bus through it. Kernels without the entity are unaffected. + +Upstream: https://github.com/intel/ipu7-camera-hal/commit/f167239b3ecf242ae081767892cb0a4c54bad496 +(only the src/ part; the ipu8 config changes are not needed for ipu75xa) +--- + src/v4l2/MediaControl.cpp | 31 +++++++++++++++++++++++++++++++ + 1 file changed, 31 insertions(+) + +diff --git a/src/v4l2/MediaControl.cpp b/src/v4l2/MediaControl.cpp +index 0b2cc33..807d4f4 100644 +--- a/src/v4l2/MediaControl.cpp ++++ b/src/v4l2/MediaControl.cpp +@@ -63,6 +63,7 @@ struct MediaEntity { + char devname[32]; + }; + ++static const string icvsName = "Intel CVS"; + MediaControl* MediaControl::sInstance = nullptr; + Mutex MediaControl::sLock; + +@@ -983,6 +984,33 @@ int MediaControl::mediaCtlSetup(int cameraId, MediaCtlConf* mc, int width, int h + } + } + ++ MediaEntity* icvs = getEntityByName(icvsName.c_str()); ++ if (icvs) { ++ for (uint32_t i = 0; i < icvs->numLinks; ++i) { ++ if (icvs->links[i].sink->entity == icvs) { ++ MediaEntity* sensor = icvs->links[i].source->entity; ++ int sensor_entity_id = sensor->info.id; ++ LOG1("@%s, found %s -> %s", __func__, sensor->info.name, icvsName.c_str()); ++ for (McLink& link : mc->links) { ++ if (link.srcEntity == sensor_entity_id && link.sinkEntity != static_cast(icvs->info.id)) { ++ LOG1("@%s, skip %s, link %s -> %s", __func__, link.srcEntityName.c_str(), ++ icvsName.c_str(), link.sinkEntityName.c_str()); ++ link.srcEntity = icvs->info.id; ++ link.srcEntityName = icvsName; ++ for (uint32_t j = 0; j < icvs->info.pads; ++j) { ++ if (icvs->pads[j].flags & MEDIA_PAD_FL_SOURCE) { ++ link.srcPad = j; ++ break; ++ } ++ } ++ break; ++ } ++ } ++ break; ++ } ++ } ++ } ++ + /* Set link in format Configuration */ + ret = setMediaMcLink(mc->links); + CheckAndLogError(ret != OK, ret, "set MediaCtlConf McLink failed: ret = %d", ret); +@@ -1127,6 +1155,9 @@ int MediaControl::getI2CBusAddress(const string& sensorEntityName, const string& + for (int i = 0; i < linksCount; i++) { + if (strcmp(links[i].sink->entity->info.name, sinkEntityName.c_str()) == 0) { + entityName = entity.info.name; ++ if (strcmp(entityName, icvsName.c_str()) == 0) { ++ return getI2CBusAddress(sensorEntityName, icvsName, i2cBus); ++ } + break; + } + } +-- +2.55.0 + diff --git a/pkgbuilds/intel-ipu7-camera/0006-camhal-ipu75xa-ov08x40-Intel-CVS-formats.patch b/pkgbuilds/intel-ipu7-camera/0006-camhal-ipu75xa-ov08x40-Intel-CVS-formats.patch new file mode 100644 index 0000000..1f5705a --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0006-camhal-ipu75xa-ov08x40-Intel-CVS-formats.patch @@ -0,0 +1,32 @@ +From: Spencer Bull +Subject: [PATCH] ipu75xa: set Intel CVS pad formats for ov08x40 + +On Linux 7.2 the "Intel CVS" bridge entity sits between ov08x40 and the +IPU7 CSI2 receiver. Its sink pad format must be set to the sensor format +(the source pad mirrors the sink) or link validation fails at stream-on. + +Mirrors the upstream ipu8 change in ipu7-camera-hal commit f167239b3ecf. +Only formats are added; links stay sensor -> CSI2 in the config because +MediaControl rewrites them through the bridge at runtime, so this config +also keeps working on kernels without the CVS entity (a format entry for +a missing entity is logged and skipped). +--- +diff --git i/config/linux/ipu75xa/sensors/ov08x40-uf.json w/config/linux/ipu75xa/sensors/ov08x40-uf.json +index ff31df5..483bf1d 100644 +--- i/config/linux/ipu75xa/sensors/ov08x40-uf.json ++++ w/config/linux/ipu75xa/sensors/ov08x40-uf.json +@@ -31,6 +31,14 @@ + "name": "ov08x40 $I2CBUS", "pad": 0, "width": 3856, "height": 2176, + "format": "V4L2_MBUS_FMT_SGRBG10_1X10" + }, ++ { ++ "name": "Intel CVS", "pad": 0, "width": 3856, "height": 2176, ++ "format": "V4L2_MBUS_FMT_SGRBG10_1X10" ++ }, ++ { ++ "name": "Intel CVS", "pad": 1, "width": 3856, "height": 2176, ++ "format": "V4L2_MBUS_FMT_SGRBG10_1X10" ++ }, + { + "name": "Intel IPU7 CSI2 $CSI_PORT", "pad": 0, "width": 3856, "height": 2176, + "format": "V4L2_MBUS_FMT_SGRBG10_1X10" diff --git a/pkgbuilds/intel-ipu7-camera/0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch b/pkgbuilds/intel-ipu7-camera/0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch new file mode 100644 index 0000000..3fd68bd --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Junjie Cao +Date: Tue, 08 Sep 2026 18:57:17 +0800 +Subject: [PATCH] media: i2c: cvs: Get the wake IRQ without claiming the GPIO +Message-ID: <20260908105717.496232-1-junjie.cao@intel.com> +Link: https://patchwork.linuxtv.org/project/linux-media/patch/20260908105717.496232-1-junjie.cao@intel.com/ + +The wake line is only used as an IRQ source, yet the driver requests +it with devm_gpiod_get() before the I2C handshake. Where ipu-bridge +does not expose the CSI endpoints, CSI init returns -EPROBE_DEFER and +every retry claims the line again for the length of the handshake. + +On the Dell XPS 14 DA14260 (Panther Lake) the four CS35L57 amplifiers +read their speaker ID from one GpioIo (DSDT decoded in the second +link): + + GpioIo (Shared, PullNone, 0, 0, IoRestrictionInputOnly, + "\_SB.GPI1", 0, ResourceConsumer,,) {20} + +A request that lands while another consumer holds the line fails, and +cs35l56 does not retry: + + cs35l56 sdw:0:2:01fa:3557:01:2: error -EBUSY: Failed to get spk-id-gpios + +All four fail on Fedora 7.1.13, the first Fedora 7.1 kernel with the +driver enabled; the same board on 7.1.12 without it creates the card. +The second link shows the same failure on openSUSE 7.2.2, whose +config also enables the driver. + +The INTC10E1 _CRS of this machine has not been decoded. The vendor +driver in intel/vision-drivers requests req, resp and rst the same +way but maps wake to an IRQ with acpi_dev_gpio_irq_get_by() without +requesting it, and on another DA14260 (board 0VRKYR, BIOS 1.8.2) a +build of it is bound while the amplifiers probe. The wake entry is +the line that differs. + +Take the IRQ from the GpioInt entry the same way, as the I2C core +does for client->irq; this also applies the trigger type from _CRS. +The driver binds as a platform device too, hence the explicit lookup. + +Fixes: 8e2b43d2c10b ("media: i2c: cvs: Add driver of Intel Computer Vision Sensing Controller(CVS)") +Cc: stable@vger.kernel.org +Link: https://bugzilla.redhat.com/show_bug.cgi?id=2529031 +Link: https://github.com/thesofproject/sof/issues/11152 +Signed-off-by: Junjie Cao +--- + drivers/media/i2c/cvs/core.c | 16 ++++++---------- + 1 file changed, 6 insertions(+), 10 deletions(-) + +diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c +index d4a3b9c3bab1e..8d857bbd8ab51 100644 +--- a/drivers/media/i2c/cvs/core.c ++++ b/drivers/media/i2c/cvs/core.c +@@ -725,8 +725,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), +@@ -734,14 +732,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/intel-ipu7-camera/PKGBUILD b/pkgbuilds/intel-ipu7-camera/PKGBUILD index c7f44e6..6683be9 100644 --- a/pkgbuilds/intel-ipu7-camera/PKGBUILD +++ b/pkgbuilds/intel-ipu7-camera/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Omarchy pkgname=intel-ipu7-camera -pkgver=1.0.5 +pkgver=1.0.6 pkgrel=2 pkgdesc="Intel IPU7 MIPI camera stack for Hurrican/Performance (OV08X40 + hardware ISP)" arch=('x86_64') @@ -9,7 +9,6 @@ url="https://github.com/TsaiGaggery/hurrican_omarchy_enabling" license=('GPL-2.0-or-later') depends=( 'dkms' - 'linux-headers' 'v4l2loopback-dkms' 'v4l2-relayd' 'gstreamer' @@ -37,16 +36,42 @@ _ipu7_camera_bins_commit="403c67db6b279dd02752f11db6a34552f31a3ac5" _ipu7_camera_hal_commit="b1f6ebef12111fb5da0133b144d69dd9b001836c" _icamerasrc_commit="4fb31db76b618aae72184c59314b839dedb42689" +# Linux 7.2 moved the Intel CVS (Computer Vision Sensing) controller into the +# IPU media graph: ipu-bridge now places it between the sensor and the IPU +# CSI2 receiver as an "Intel CVS" V4L2 bridge sub-device, provided by the +# in-tree drivers/media/i2c/cvs driver. The legacy vision-drivers misc driver +# has no such sub-device, so on 7.2 the sensor never joins the graph and the +# HAL finds no camera. We ship the in-tree driver as a DKMS module +# for 7.2+ kernels (Arch-derived kernel configs cannot enable it: the option +# is hidden behind MEDIA_HIDE_ANCILLARY_SUBDRV) and keep vision-drivers for +# older kernels. Both dkms.conf files carry matching BUILD_EXCLUSIVE_KERNEL. +# +# 0007 is the upstream fix for the audio regression on the Dell XPS 14 +# DA14260: the driver requested its "wake" GPIO, which shares a pin with the +# CS35L57 amplifiers' speaker-ID GpioIo, so every cs35l56 probe failed with +# -EBUSY and the sound card never appeared. Drop it once the stable tag +# fetched below contains the fix. +_intel_cvs_kernel_tag="v7.2.5" +_intel_cvs_url="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/media/i2c/cvs" + source=( "ipu7-drivers::git+https://github.com/intel/ipu7-drivers.git#commit=${_ipu7_drivers_commit}" "vision-drivers::git+https://github.com/intel/vision-drivers.git#commit=${_vision_drivers_commit}" "ipu7-camera-bins::git+https://github.com/intel/ipu7-camera-bins.git#commit=${_ipu7_camera_bins_commit}" "ipu7-camera-hal::git+https://github.com/intel/ipu7-camera-hal.git#commit=${_ipu7_camera_hal_commit}" "icamerasrc::git+https://github.com/intel/icamerasrc.git#commit=${_icamerasrc_commit}" + "intel-cvs-core.c::${_intel_cvs_url}/core.c?h=${_intel_cvs_kernel_tag}" + "intel-cvs-v4l2.c::${_intel_cvs_url}/v4l2.c?h=${_intel_cvs_kernel_tag}" + "intel-cvs-icvs.h::${_intel_cvs_url}/icvs.h?h=${_intel_cvs_kernel_tag}" "0004-ipu7-psys-register-device-bus.patch" "0003-icvs-set-rgbcamera_pwrup_host-0-for-Panther-Lake.patch" + "0005-camhal-MediaControl-route-through-Intel-CVS-bridge.patch" + "0006-camhal-ipu75xa-ov08x40-Intel-CVS-formats.patch" + "0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch" "dkms-ipu7-drivers.conf" "dkms-vision-drivers.conf" + "dkms-intel-cvs.conf" + "intel-cvs-Makefile" "camera-deps.conf" "v4l2loopback-modprobe.conf" "camera-init.service" @@ -62,10 +87,14 @@ source=( "v4l2-relayd-ipu7-override.conf" ) sha256sums=( + 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' + 'b4a90ad1815c02e4cadc1b5ad6c576ae0b150cb365c68d2c934cf964a995d91f' + 'e988014f54b1b5183e9ef43b602d73bbc2c991f19a8d503560a8cdba112b76c6' + '6dcd5201fb78766a440038c8eba2cc8c3892064ba829065e2269c30dc1905983' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' - 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' - 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' - 'SKIP' 'SKIP' 'SKIP' + 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' + 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' + 'SKIP' ) prepare() { @@ -79,6 +108,20 @@ prepare() { git am "${srcdir}/0003-icvs-set-rgbcamera_pwrup_host-0-for-Panther-Lake.patch" fi + # Route the media graph through the Linux 7.2 "Intel CVS" bridge entity + cd "${srcdir}/ipu7-camera-hal" + patch -Np1 -i "${srcdir}/0005-camhal-MediaControl-route-through-Intel-CVS-bridge.patch" + patch -Np1 -i "${srcdir}/0006-camhal-ipu75xa-ov08x40-Intel-CVS-formats.patch" + + # Intel CVS: take the wake IRQ without claiming the GPIO (shared with the + # speaker-ID line on the XPS 14). makepkg symlinks plain downloads into + # srcdir and patch refuses to touch a symlink, so patch a real copy. The + # patch is against a/drivers/media/i2c/cvs/core.c, hence -p5. + cp --remove-destination "$(readlink -f "${srcdir}/intel-cvs-core.c")" \ + "${srcdir}/intel-cvs-core.c" + patch -p5 -F0 --no-backup-if-mismatch "${srcdir}/intel-cvs-core.c" \ + < "${srcdir}/0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch" + # Stage proprietary libs/headers for build-time use local staging="${srcdir}/staging" rm -rf "${staging}" @@ -145,13 +188,22 @@ package() { install -Dm644 "${srcdir}/dkms-ipu7-drivers.conf" "${ipu7_dkms_dir}/dkms.conf" sed -i "s/@PKGVER@/${pkgver}/" "${ipu7_dkms_dir}/dkms.conf" - # DKMS: Vision drivers (CVS) + # DKMS: legacy Vision drivers (CVS misc driver), kernels before 7.2 local cvs_dkms_dir="${pkgdir}/usr/src/vision-drivers-${pkgver}" install -dm755 "${cvs_dkms_dir}" cp -r "${srcdir}/vision-drivers/"* "${cvs_dkms_dir}/" install -Dm644 "${srcdir}/dkms-vision-drivers.conf" "${cvs_dkms_dir}/dkms.conf" sed -i "s/@PKGVER@/${pkgver}/" "${cvs_dkms_dir}/dkms.conf" + # DKMS: in-tree Linux 7.2 Intel CVS V4L2 bridge driver, kernels 7.2+ + local icvs_dkms_dir="${pkgdir}/usr/src/intel-cvs-${pkgver}" + install -Dm644 "${srcdir}/intel-cvs-core.c" "${icvs_dkms_dir}/core.c" + install -Dm644 "${srcdir}/intel-cvs-v4l2.c" "${icvs_dkms_dir}/v4l2.c" + install -Dm644 "${srcdir}/intel-cvs-icvs.h" "${icvs_dkms_dir}/icvs.h" + install -Dm644 "${srcdir}/intel-cvs-Makefile" "${icvs_dkms_dir}/Makefile" + install -Dm644 "${srcdir}/dkms-intel-cvs.conf" "${icvs_dkms_dir}/dkms.conf" + sed -i "s/@PKGVER@/${pkgver}/" "${icvs_dkms_dir}/dkms.conf" + # Firmware install -dm755 "${pkgdir}/usr/lib/firmware/intel/ipu" install -Dm644 "${srcdir}/ipu7-camera-bins/lib/firmware/intel/ipu/"*.bin \ diff --git a/pkgbuilds/intel-ipu7-camera/dkms-intel-cvs.conf b/pkgbuilds/intel-ipu7-camera/dkms-intel-cvs.conf new file mode 100644 index 0000000..ba882ac --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/dkms-intel-cvs.conf @@ -0,0 +1,26 @@ +# Intel CVS (Computer Vision Sensing) CSI-2 bridge driver. +# +# Copy of the in-tree Linux 7.2 driver (drivers/media/i2c/cvs) plus the +# upstream wake-IRQ fix carried by the PKGBUILD, built out of tree so it +# exists on every 7.2+ kernel regardless of the kernel's Kconfig. +# Arch-derived configs cannot enable VIDEO_INTEL_CVS at all: it lives under +# the "Miscellaneous helper chips" menu, which is hidden by +# MEDIA_HIDE_ANCILLARY_SUBDRV unless CONFIG_EXPERT is set. +# +# 7.2's ipu-bridge inserts the CVS device between the sensor and the IPU +# CSI2 receiver, so the IPU only sees the sensor once a driver registers +# this V4L2 bridge sub-device. Kernels before 7.2 keep using the legacy +# misc driver from vision-drivers (see dkms-vision-drivers.conf). +PACKAGE_NAME="intel-cvs" +PACKAGE_VERSION="@PKGVER@" +AUTOINSTALL="yes" + +# 7.2 and later only (regex, so the pacman dkms hook also honours it) +BUILD_EXCLUSIVE_KERNEL="^(7\.([2-9]|[1-9][0-9]+)\.|([8-9]|[1-9][0-9]+)\.)" + +BUILT_MODULE_NAME[0]="intel_cvs" +BUILT_MODULE_LOCATION[0]="" +DEST_MODULE_LOCATION[0]="/updates" + +MAKE[0]="make KERNEL_SRC=${kernel_source_dir}" +CLEAN="make KERNEL_SRC=${kernel_source_dir} clean" diff --git a/pkgbuilds/intel-ipu7-camera/dkms-vision-drivers.conf b/pkgbuilds/intel-ipu7-camera/dkms-vision-drivers.conf index 3582d0f..ab47545 100644 --- a/pkgbuilds/intel-ipu7-camera/dkms-vision-drivers.conf +++ b/pkgbuilds/intel-ipu7-camera/dkms-vision-drivers.conf @@ -1,7 +1,15 @@ +# Legacy Intel CVS misc driver (no V4L2 bridge). Only for kernels before +# 7.2: from 7.2 on, ipu-bridge routes the sensor through a CVS V4L2 bridge +# sub-device, which this driver does not provide, and it would displace the +# in-tree intel_cvs module of the same name. 7.2+ kernels use intel-cvs +# (see dkms-intel-cvs.conf) instead. PACKAGE_NAME="vision-drivers" PACKAGE_VERSION="@PKGVER@" AUTOINSTALL="yes" +# Before 7.2 only (regex, so the pacman dkms hook also honours it) +BUILD_EXCLUSIVE_KERNEL="^([0-6]\.|7\.[01]\.)" + BUILT_MODULE_NAME[0]="intel_cvs" BUILT_MODULE_LOCATION[0]="" DEST_MODULE_LOCATION[0]="/updates" diff --git a/pkgbuilds/intel-ipu7-camera/intel-cvs-Makefile b/pkgbuilds/intel-ipu7-camera/intel-cvs-Makefile new file mode 100644 index 0000000..58b251c --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/intel-cvs-Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Out-of-tree build of the in-tree Linux 7.2 Intel CVS driver +# (drivers/media/i2c/cvs). Sources are kernel files plus the upstream +# wake-IRQ fix carried by the PKGBUILD. +KERNELRELEASE ?= $(shell uname -r) +KERNEL_SRC ?= /lib/modules/$(KERNELRELEASE)/build + +obj-m := intel_cvs.o +intel_cvs-y := core.o v4l2.o + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(CURDIR) modules + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(CURDIR) clean diff --git a/pkgbuilds/lib32-nvidia-580xx-utils/.omarchy/package.json b/pkgbuilds/lib32-nvidia-580xx-utils/.omarchy/package.json index 8fa7c05..93ef7eb 100644 --- a/pkgbuilds/lib32-nvidia-580xx-utils/.omarchy/package.json +++ b/pkgbuilds/lib32-nvidia-580xx-utils/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "bbe7b771c897f78e518ab4453b0bc9210ef2f201" + "source": "local", + "upstream": { + "watch": { + "regex": "https://download.nvidia.com/XFree86/Linux-x86_64/", + "pattern": "href=[\\'\"](?P580\\.[0-9]+\\.[0-9]+)/[\\'\"]" + } + }, + "origin": { + "aur": "lib32-nvidia-580xx-utils", + "commit": "bbe7b771c897f78e518ab4453b0bc9210ef2f201" + } } diff --git a/pkgbuilds/lib32-nvidia-580xx-utils/.omarchy/patches/max-zstd-compression.patch b/pkgbuilds/lib32-nvidia-580xx-utils/.omarchy/patches/max-zstd-compression.patch deleted file mode 100644 index 10f90eb..0000000 --- a/pkgbuilds/lib32-nvidia-580xx-utils/.omarchy/patches/max-zstd-compression.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -12,6 +12,12 @@ - #makedepends=('nvidia-libgl') # To avoid conflict during installation in the build chroot - license=('custom') - options=('!strip') -+ -+# Omarchy: the build image compresses with zstd at its default level, which -+# leaves these unstripped driver blobs far larger than they need to be. Maximum -+# zstd saves 25 MB here, most of it on the ISO's offline mirror. -+COMPRESSZST=(zstd -c -z -q --ultra -22 --threads=0 -) -+ - _pkg="NVIDIA-Linux-x86_64-${pkgver}" - source=("https://download.nvidia.com/XFree86/Linux-x86_64/${pkgver}/${_pkg}.run") - sha512sums=('ca394b1e3f6c3403dc7ffe5d5fd7301bb06607fdbf75c9ffd338d7641c06d1878790c629108162adf4289ae829568973a48b7557183dbf7588e668f6d8edf3b3') diff --git a/pkgbuilds/libfprint-git/.omarchy/package.json b/pkgbuilds/libfprint-git/.omarchy/package.json index 48065ee..eed18f7 100644 --- a/pkgbuilds/libfprint-git/.omarchy/package.json +++ b/pkgbuilds/libfprint-git/.omarchy/package.json @@ -1,5 +1,8 @@ { - "source": "aur", + "source": "local", "sync": false, - "upstream_commit": "d225acd677e4a2af8f99698ea11386d8ad3af1bf" + "origin": { + "aur": "libfprint-git", + "commit": "d225acd677e4a2af8f99698ea11386d8ad3af1bf" + } } diff --git a/pkgbuilds/libretro-cap32-git/.omarchy/package.json b/pkgbuilds/libretro-cap32-git/.omarchy/package.json index 42986be..41c7005 100644 --- a/pkgbuilds/libretro-cap32-git/.omarchy/package.json +++ b/pkgbuilds/libretro-cap32-git/.omarchy/package.json @@ -1,5 +1,8 @@ { - "source": "aur", + "source": "local", "sync": false, - "upstream_commit": "2dbe3348fb970d34155e652ce4a84fe4fbb42ecf" + "origin": { + "aur": "libretro-cap32-git", + "commit": "2dbe3348fb970d34155e652ce4a84fe4fbb42ecf" + } } diff --git a/pkgbuilds/libretro-database-git/.omarchy/package.json b/pkgbuilds/libretro-database-git/.omarchy/package.json index 9031029..7fcb920 100644 --- a/pkgbuilds/libretro-database-git/.omarchy/package.json +++ b/pkgbuilds/libretro-database-git/.omarchy/package.json @@ -1,5 +1,8 @@ { - "source": "aur", + "source": "local", "sync": false, - "upstream_commit": "11ff3d3d1fc62f44a35a813f818235b7313ff7e0" + "origin": { + "aur": "libretro-database-git", + "commit": "11ff3d3d1fc62f44a35a813f818235b7313ff7e0" + } } diff --git a/pkgbuilds/libretro-fbneo-git/.omarchy/package.json b/pkgbuilds/libretro-fbneo-git/.omarchy/package.json index 88a2b8a..07b490f 100644 --- a/pkgbuilds/libretro-fbneo-git/.omarchy/package.json +++ b/pkgbuilds/libretro-fbneo-git/.omarchy/package.json @@ -1,5 +1,8 @@ { - "source": "aur", + "source": "local", "sync": false, - "upstream_commit": "c33a5ce38a6a493fe88f092561d5197db1f97a1d" + "origin": { + "aur": "libretro-fbneo-git", + "commit": "c33a5ce38a6a493fe88f092561d5197db1f97a1d" + } } diff --git a/pkgbuilds/libretro-uae-git/.omarchy/package.json b/pkgbuilds/libretro-uae-git/.omarchy/package.json index 6a6a5db..479ac60 100644 --- a/pkgbuilds/libretro-uae-git/.omarchy/package.json +++ b/pkgbuilds/libretro-uae-git/.omarchy/package.json @@ -1,5 +1,8 @@ { - "source": "aur", + "source": "local", "sync": false, - "upstream_commit": "f1bbed73caf47bfcb07a2c15f18485b42cb58584" + "origin": { + "aur": "libretro-uae-git", + "commit": "f1bbed73caf47bfcb07a2c15f18485b42cb58584" + } } diff --git a/pkgbuilds/libretro-vice-git/.omarchy/package.json b/pkgbuilds/libretro-vice-git/.omarchy/package.json index c141b36..664ce23 100644 --- a/pkgbuilds/libretro-vice-git/.omarchy/package.json +++ b/pkgbuilds/libretro-vice-git/.omarchy/package.json @@ -1,5 +1,8 @@ { - "source": "aur", + "source": "local", "sync": false, - "upstream_commit": "6b1839b0a1154ef29ddb2b67910f6d4c90bc1edd" + "origin": { + "aur": "libretro-vice-git", + "commit": "6b1839b0a1154ef29ddb2b67910f6d4c90bc1edd" + } } diff --git a/pkgbuilds/limine-mkinitcpio-hook/.omarchy/package.json b/pkgbuilds/limine-mkinitcpio-hook/.omarchy/package.json index e8b8b64..86036c2 100644 --- a/pkgbuilds/limine-mkinitcpio-hook/.omarchy/package.json +++ b/pkgbuilds/limine-mkinitcpio-hook/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "0c6f3a88d5928907d79c58bb67c9cb018774f60d" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://gitlab.com/Zesko/limine-entry-tool.git", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "limine-mkinitcpio-hook", + "commit": "0c6f3a88d5928907d79c58bb67c9cb018774f60d" + } } diff --git a/pkgbuilds/limine-mkinitcpio-hook/.omarchy/patches/arm-gradle.patch b/pkgbuilds/limine-mkinitcpio-hook/.omarchy/patches/arm-gradle.patch deleted file mode 100644 index d7af3e8..0000000 --- a/pkgbuilds/limine-mkinitcpio-hook/.omarchy/patches/arm-gradle.patch +++ /dev/null @@ -1,48 +0,0 @@ ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -3,6 +3,7 @@ - pkgname="limine-mkinitcpio-hook" - _pkgver=1.38.0 - _extver="" -+_gradle_version=9.7.1 - pkgver="${_pkgver}${_extver}" - pkgrel=1 - pkgdesc="Install kernels for the Limine bootloader." -@@ -10,7 +11,8 @@ - url="https://gitlab.com/Zesko/limine-entry-tool" - source=("${_pkgname}::git+${url}.git#tag=${pkgver}") - source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-x64_bin.tar.gz") --source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz") -+source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz" -+ "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip") - license=("GPL3") - provides=('limine-entry-tool') - options=(!debug !strip) -@@ -27,12 +29,14 @@ - 'sbctl: Signs UEFI boot files for Secure Boot when enabled' - 'journalctl-desktop-notification: Sends desktop notifications when errors occur' - ) --makedepends=('git' 'gradle') -+makedepends=('git') -+makedepends_x86_64=('gradle') - backup=(etc/limine-entry-tool.conf) - conflicts=('limine-entry-tool') - sha256sums=('11dd8211898585f79d8361d97606965bb2ca214dccace5b133dc5121e4bb4d01') - sha256sums_x86_64=('3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e') --sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7') -+sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7' -+ 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a') - - prepare() { - [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" -@@ -48,7 +52,9 @@ - export GRAALVM_HOME="$srcdir/${_graalvm_version}" - export JAVA_HOME="${GRAALVM_HOME}" - export NATIVE_IMAGE_OPTIONS="-march=compatibility --future-defaults=all" -- /usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" -+ local gradle=/usr/bin/gradle -+ [[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle" -+ "$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" - } - - package() { diff --git a/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD b/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD index b2e63c8..76e4d9a 100644 --- a/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD +++ b/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD @@ -1,11 +1,9 @@ # Maintainer: Zesko _pkgname="limine-entry-tool" pkgname="limine-mkinitcpio-hook" -_pkgver=1.38.0 -_extver="" _gradle_version=9.7.1 -pkgver="${_pkgver}${_extver}" -pkgrel=1.1 +pkgver=1.39.0 +pkgrel=1 pkgdesc="Install kernels for the Limine bootloader." arch=('x86_64' 'aarch64') url="https://gitlab.com/Zesko/limine-entry-tool" @@ -33,7 +31,7 @@ makedepends=('git') makedepends_x86_64=('gradle') backup=(etc/limine-entry-tool.conf) conflicts=('limine-entry-tool') -sha256sums=('11dd8211898585f79d8361d97606965bb2ca214dccace5b133dc5121e4bb4d01') +sha256sums=('6c4affb6fb6367a1222f7d0c54957a3142781d7894bbf61b1a274bd153e5d869') sha256sums_x86_64=('3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e') sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7' 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a') diff --git a/pkgbuilds/limine-snapper-sync/.omarchy/package.json b/pkgbuilds/limine-snapper-sync/.omarchy/package.json index 24ae4dd..76ad41c 100644 --- a/pkgbuilds/limine-snapper-sync/.omarchy/package.json +++ b/pkgbuilds/limine-snapper-sync/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "35aff513bf1f0284380c8592d3de213f00bb6a7b" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://gitlab.com/Zesko/limine-snapper-sync.git", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "limine-snapper-sync", + "commit": "35aff513bf1f0284380c8592d3de213f00bb6a7b" + } } diff --git a/pkgbuilds/limine-snapper-sync/.omarchy/patches/arm-gradle.patch b/pkgbuilds/limine-snapper-sync/.omarchy/patches/arm-gradle.patch deleted file mode 100644 index 76ad31a..0000000 --- a/pkgbuilds/limine-snapper-sync/.omarchy/patches/arm-gradle.patch +++ /dev/null @@ -1,46 +0,0 @@ ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -2,12 +2,14 @@ - pkgname="limine-snapper-sync" - pkgver=1.31.0 - pkgrel=1 -+_gradle_version=9.7.1 - pkgdesc="Integrates Limine boot entries with Snapper snapshots." - arch=('x86_64' 'aarch64') - url="https://gitlab.com/Zesko/limine-snapper-sync" - source=("${pkgname}::git+${url}.git#tag=${pkgver}") - source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-x64_bin.tar.gz") --source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz") -+source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz" -+ "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip") - license=("GPL3") - options=(!debug !strip) - _graalvm_version=graalvm_ce_jdk25 -@@ -25,12 +27,14 @@ - 'b3sum: Fast Blake3 hash function to prevent duplication.' - 'xxhash: Fast hashing utility for deduplication with shorter hashes.' - ) --makedepends=('git' 'gradle') -+makedepends=('git') -+makedepends_x86_64=('gradle') - backup=(etc/limine-snapper-sync.conf) - conflicts=('limine-snapper-cli' 'limine-snapper-sync-git') - sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40') - sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0') --sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71') -+sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71' -+ 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a') - - prepare() { - [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" -@@ -46,7 +50,9 @@ - export GRAALVM_HOME="$srcdir/${_graalvm_version}" - export JAVA_HOME="${GRAALVM_HOME}" - export NATIVE_IMAGE_OPTIONS="-march=compatibility" -- /usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" -+ local gradle=/usr/bin/gradle -+ [[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle" -+ "$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" - } - - package() { diff --git a/pkgbuilds/linux-omarchy-bore/.omarchy/package.json b/pkgbuilds/linux-omarchy-bore/.omarchy/package.json new file mode 100644 index 0000000..c3ed389 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "skip_build": true +} diff --git a/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch new file mode 100644 index 0000000..55e9379 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch @@ -0,0 +1,56 @@ +diff --git a/kernel/fork.c b/kernel/fork.c +index f0e2e131a9a5..7b611da9a27a 100644 +--- 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 + */ +@@ -2093,6 +2099,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 +@@ -3163,6 +3174,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; + } +@@ -3398,6 +3413,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-bore/0010-archlinux-base.patch.sig b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch.sig new file mode 100644 index 0000000..a7eaa24 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch b/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch new file mode 100644 index 0000000..1d049bd --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch @@ -0,0 +1,1241 @@ +diff --git a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -823,6 +823,31 @@ struct kmap_ctrl { + #endif + }; + ++#ifdef CONFIG_SCHED_BORE ++#define BORE_BC_TIMESTAMP_SHIFT 16 ++ ++struct bore_bc { ++ union { ++ struct { ++ u64 timestamp: 48; ++ u64 penalty: 16; ++ }; ++ u64 value; ++ }; ++}; ++ ++struct bore_ctx { ++ u64 burst_time; ++ u16 prev_penalty; ++ u16 curr_penalty; ++ u16 penalty; ++ bool stop_update; ++ bool futex_waiting; ++ struct bore_bc subtree; ++ struct bore_bc group; ++}; ++#endif /* CONFIG_SCHED_BORE */ ++ + struct task_struct { + #ifdef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -884,6 +909,9 @@ struct task_struct { + #ifdef CONFIG_SCHED_CLASS_EXT + struct sched_ext_entity scx; + #endif ++#ifdef CONFIG_SCHED_BORE ++ struct bore_ctx bore; ++#endif /* CONFIG_SCHED_BORE */ + const struct sched_class *sched_class; + + #ifdef CONFIG_SCHED_CORE +diff --git a/include/linux/sched/bore.h b/include/linux/sched/bore.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/sched/bore.h +@@ -0,0 +1,49 @@ ++#ifndef _KERNEL_SCHED_BORE_H ++#define _KERNEL_SCHED_BORE_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SCHED_BORE_AUTHOR "Masahito Suzuki" ++#define SCHED_BORE_PROGNAME "BORE CPU Scheduler modification" ++ ++#define SCHED_BORE_VERSION "6.8.0" ++ ++extern u8 __read_mostly sched_bore; ++DECLARE_STATIC_KEY_TRUE(sched_bore_key); ++extern u8 __read_mostly sched_burst_inherit_type; ++extern u8 __read_mostly sched_burst_smoothness; ++extern u8 __read_mostly sched_burst_penalty_offset; ++extern uint __read_mostly sched_burst_penalty_scale; ++extern uint __read_mostly sched_burst_cache_lifetime; ++extern u8 __read_mostly sched_burst_protect_slice_lv; ++DECLARE_STATIC_KEY_TRUE(sched_burst_protect_slice_cond_key); ++DECLARE_STATIC_KEY_FALSE(sched_burst_protect_slice_prefer_key); ++ ++static inline u8 bore_score(struct task_struct *p) ++{ return p->bore.penalty >> 8; } ++ ++extern u8 effective_prio_bore(struct task_struct *p); ++extern void update_curr_bore(struct task_struct *p, u64 delta_exec); ++extern void restart_burst_bore(struct task_struct *p); ++extern void restart_burst_rescale_deadline_bore(struct task_struct *p); ++extern void task_fork_bore(struct task_struct *p, struct task_struct *parent, ++ u64 clone_flags, u64 now); ++extern void sched_init_bore(void); ++extern void reset_task_bore(struct task_struct *p); ++ ++extern int sched_bore_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos); ++extern int sched_burst_inherit_type_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos); ++extern int sched_burst_protect_slice_lv_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos); ++ ++extern void reweight_entity( ++ struct cfs_rq *cfs_rq, struct sched_entity *se, unsigned long weight); ++ ++#endif /* _KERNEL_SCHED_BORE_H */ +diff --git a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -1480,6 +1480,23 @@ config CHECKPOINT_RESTORE + + If unsure, say N here. + ++config SCHED_BORE ++ bool "Burst-Oriented Response Enhancer" ++ default y ++ help ++ In Desktop and Mobile computing, one might prefer interactive ++ tasks to keep responsive no matter what they run in the background. ++ ++ Enabling this kernel feature modifies the scheduler to discriminate ++ tasks by their burst time (runtime since it last went sleeping or ++ yielding state) and prioritize those that run less bursty. ++ Such tasks usually include window compositor, widgets backend, ++ terminal emulator, video playback, games and so on. ++ With a little impact to scheduling fairness, it may improve ++ responsiveness especially under heavy background workload. ++ ++ If unsure, say Y here. ++ + config SCHED_AUTOGROUP + bool "Automatic process group scheduling" + select CGROUPS +diff --git a/kernel/Kconfig.hz b/kernel/Kconfig.hz +--- a/kernel/Kconfig.hz ++++ b/kernel/Kconfig.hz +@@ -57,3 +57,20 @@ config HZ + + config SCHED_HRTICK + def_bool HIGH_RES_TIMERS ++ ++config MIN_BASE_SLICE_NS ++ int "Default value for min_base_slice_ns" ++ default 2000000 ++ help ++ The BORE Scheduler automatically calculates the optimal base ++ slice for the configured HZ using the following equation: ++ ++ base_slice_ns = ++ 1000000000/HZ * DIV_ROUNDUP(min_base_slice_ns, 1000000000/HZ) ++ ++ This option sets the default lower bound limit of the base slice ++ to prevent the loss of task throughput due to overscheduling. ++ ++ Setting this value too high can cause the system to boot with ++ an unnecessarily large base slice, resulting in high scheduling ++ latency and poor system responsiveness. +diff --git a/kernel/exit.c b/kernel/exit.c +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -147,7 +147,11 @@ static void __unhash_process(struct release_task_post *post, struct task_struct + detach_pid(post->pids, p, PIDTYPE_SID); + + list_del_rcu(&p->tasks); ++#ifdef CONFIG_SCHED_BORE ++ list_del_rcu(&p->sibling); ++#else /* !CONFIG_SCHED_BORE */ + list_del_init(&p->sibling); ++#endif /* CONFIG_SCHED_BORE */ + __this_cpu_dec(process_counts); + } + list_del_rcu(&p->thread_node); +diff --git a/kernel/fork.c b/kernel/fork.c +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -120,6 +120,10 @@ + /* For dup_mmap(). */ + #include "../mm/internal.h" + ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ ++ + #include + + #define CREATE_TRACE_POINTS +@@ -2441,6 +2445,11 @@ __latent_entropy struct task_struct *copy_process( + p->start_time = ktime_get_ns(); + p->start_boottime = ktime_get_boottime_ns(); + ++#ifdef CONFIG_SCHED_BORE ++ if (likely(p->pid)) ++ task_fork_bore(p, current, clone_flags, p->start_time); ++#endif /* CONFIG_SCHED_BORE */ ++ + /* + * Make it visible to the rest of the system, but dont wake it up yet. + * Need tasklist lock for parent etc handling! +@@ -2526,7 +2535,11 @@ __latent_entropy struct task_struct *copy_process( + p->real_parent->signal->is_child_subreaper; + if (clone_flags & CLONE_AUTOREAP) + p->signal->autoreap = 1; ++#ifdef CONFIG_SCHED_BORE ++ list_add_tail_rcu(&p->sibling, &p->real_parent->children); ++#else /* !CONFIG_SCHED_BORE */ + list_add_tail(&p->sibling, &p->real_parent->children); ++#endif /* CONFIG_SCHED_BORE */ + list_add_tail_rcu(&p->tasks, &init_task.tasks); + attach_pid(p, PIDTYPE_TGID); + attach_pid(p, PIDTYPE_PGID); +diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c +--- a/kernel/futex/waitwake.c ++++ b/kernel/futex/waitwake.c +@@ -4,6 +4,9 @@ + #include + #include + #include ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ + + #include "futex.h" + +@@ -384,7 +387,15 @@ void futex_do_wait(struct futex_q *q, struct hrtimer_sleeper *timeout) + * is no timeout, or if it has yet to expire. + */ + if (!timeout || timeout->task) ++#ifdef CONFIG_SCHED_BORE ++ { ++ current->bore.futex_waiting = true; ++#endif /* CONFIG_SCHED_BORE */ + schedule(); ++#ifdef CONFIG_SCHED_BORE ++ current->bore.futex_waiting = false; ++ } ++#endif /* CONFIG_SCHED_BORE */ + } + __set_current_state(TASK_RUNNING); + } +diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile +--- a/kernel/sched/Makefile ++++ b/kernel/sched/Makefile +@@ -40,3 +40,4 @@ obj-y += core.o + obj-y += fair.o + obj-y += build_policy.o + obj-y += build_utility.o ++obj-$(CONFIG_SCHED_BORE) += bore.o +diff --git a/kernel/sched/bore.c b/kernel/sched/bore.c +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/bore.c +@@ -0,0 +1,466 @@ ++/* ++ * Burst-Oriented Response Enhancer (BORE) CPU Scheduler ++ * Copyright (C) 2021-2025 Masahito Suzuki ++ */ ++#include ++#include ++#include ++#include "sched.h" ++ ++#ifdef CONFIG_SCHED_BORE ++DEFINE_STATIC_KEY_TRUE(sched_bore_key); ++u8 __read_mostly sched_bore = 1; ++u8 __read_mostly sched_burst_inherit_type = 2; ++u8 __read_mostly sched_burst_protect_slice_lv = 1; ++u8 __read_mostly sched_burst_smoothness = 1; ++u8 __read_mostly sched_burst_penalty_offset = 24; ++uint __read_mostly sched_burst_penalty_scale = 1536; ++uint __read_mostly sched_burst_cache_lifetime = 75000000; ++static int __maybe_unused maxval_prio = 39; ++static int __maybe_unused maxval_6_bits = 63; ++static int __maybe_unused maxval_8_bits = 255; ++static int __maybe_unused maxval_12_bits = 4095; ++ ++#define MAX_BURST_PENALTY ((40U << 8) - 1) ++#define BURST_CACHE_SAMPLE_LIMIT 63 ++#define BURST_CACHE_SCAN_LIMIT (BURST_CACHE_SAMPLE_LIMIT * 2) ++ ++static u32 bore_reciprocal_lut[BURST_CACHE_SAMPLE_LIMIT + 1]; ++ ++DEFINE_STATIC_KEY_TRUE(sched_burst_inherit_key); ++DEFINE_STATIC_KEY_TRUE(sched_burst_ancestor_key); ++DEFINE_STATIC_KEY_TRUE (sched_burst_protect_slice_cond_key); ++DEFINE_STATIC_KEY_FALSE(sched_burst_protect_slice_prefer_key); ++ ++static inline u32 log2p1_u64_u32fp(u64 v, u8 fp) { ++ if (unlikely(!v)) return 0; ++ int clz = __builtin_clzll(v); ++ int exponent = 64 - clz; ++ u32 mantissa = (u32)((v << clz) << 1 >> (64 - fp)); ++ return exponent << fp | mantissa; ++} ++ ++static inline u32 calc_burst_penalty(u64 burst_time) { ++ u32 greed = log2p1_u64_u32fp(burst_time, 8), ++ tolerance = sched_burst_penalty_offset << 8; ++ s32 diff = (s32)(greed - tolerance); ++ u32 penalty = diff & ~(diff >> 31); ++ u32 scaled_penalty = penalty * sched_burst_penalty_scale >> 10; ++ s32 overflow = scaled_penalty - MAX_BURST_PENALTY; ++ return scaled_penalty - (overflow & ~(overflow >> 31)); ++} ++ ++static inline u64 rescale_slice(u64 delta, u8 old_prio, u8 new_prio) { ++ u64 unscaled, rescaled; ++ unscaled = mul_u64_u32_shr(delta , sched_prio_to_weight[old_prio], 10); ++ rescaled = mul_u64_u32_shr(unscaled, sched_prio_to_wmult [new_prio], 22); ++ return rescaled; ++} ++ ++static inline u32 binary_smooth(u32 new, u32 old) { ++ u32 is_growing = (new > old); ++ u32 increment = (new - old) * is_growing; ++ u32 shift = sched_burst_smoothness; ++ u32 smoothed = old + ((increment + (1U << shift) - 1) >> shift); ++ return (new & ~(-is_growing)) | (smoothed & (-is_growing)); ++} ++ ++static void reweight_task_by_prio(struct task_struct *p, int prio) { ++ if (task_has_idle_policy(p)) return; ++ ++ struct sched_entity *se = &p->se; ++ unsigned long weight = scale_load(sched_prio_to_weight[prio]); ++ ++ if (se->on_rq) { ++ p->bore.stop_update = true; ++ reweight_entity(cfs_rq_of(se), se, weight); ++ p->bore.stop_update = false; ++ } else ++ se->load.weight = weight; ++ se->load.inv_weight = sched_prio_to_wmult[prio]; ++} ++ ++u8 effective_prio_bore(struct task_struct *p) { ++ int prio = p->static_prio - MAX_RT_PRIO; ++ if (static_branch_likely(&sched_bore_key)) ++ prio += bore_score(p); ++ prio &= ~(prio >> 31); ++ s32 diff = prio - maxval_prio; ++ prio -= (diff & ~(diff >> 31)); ++ return (u8)prio; ++} ++ ++static void update_penalty(struct task_struct *p) { ++ struct bore_ctx *ctx = &p->bore; ++ ++ u8 prev_prio = effective_prio_bore(p); ++ ++ s32 diff = (s32)ctx->curr_penalty - (s32)ctx->prev_penalty; ++ u16 max_val = ctx->curr_penalty - (diff & (diff >> 31)); ++ u32 is_kthread = !!(p->flags & PF_KTHREAD); ++ ctx->penalty = max_val & -(s32)(!is_kthread); ++ ++ u8 new_prio = effective_prio_bore(p); ++ if (new_prio != prev_prio) ++ reweight_task_by_prio(p, new_prio); ++} ++ ++void update_curr_bore(struct task_struct *p, u64 delta_exec) { ++ struct bore_ctx *ctx = &p->bore; ++ if (ctx->stop_update) return; ++ ++ ctx->burst_time += delta_exec; ++ u32 curr_penalty = ctx->curr_penalty = calc_burst_penalty(ctx->burst_time); ++ ++ if (curr_penalty <= ctx->prev_penalty) return; ++ update_penalty(p); ++} ++ ++void restart_burst_bore(struct task_struct *p) { ++ struct bore_ctx *ctx = &p->bore; ++ u32 new_penalty = binary_smooth(ctx->curr_penalty, ctx->prev_penalty); ++ ctx->prev_penalty = new_penalty; ++ ctx->curr_penalty = 0; ++ ctx->burst_time = 0; ++ update_penalty(p); ++} ++ ++void restart_burst_rescale_deadline_bore(struct task_struct *p) { ++ struct sched_entity *se = &p->se; ++ s64 vscaled, vremain = se->deadline - se->vruntime; ++ ++ u8 old_prio = effective_prio_bore(p); ++ restart_burst_bore(p); ++ u8 new_prio = effective_prio_bore(p); ++ ++ if (old_prio > new_prio) { ++ vscaled = rescale_slice(abs(vremain), old_prio, new_prio); ++ if (unlikely(vremain < 0)) ++ vscaled = -vscaled; ++ se->deadline = se->vruntime + vscaled; ++ } ++} ++ ++static inline bool task_is_bore_eligible(struct task_struct *p) ++{return p && p->sched_class == &fair_sched_class && !p->exit_state;} ++ ++#ifndef for_each_child_task ++#define for_each_child_task(p, t) \ ++ list_for_each_entry_rcu(t, &(p)->children, sibling) ++#endif ++ ++static inline u32 count_children_upto2(struct task_struct *p) { ++ struct list_head *head = &p->children; ++ struct list_head *first = READ_ONCE(head->next); ++ struct list_head *second = READ_ONCE(first->next); ++ return (first != head) + (second != head); ++} ++ ++static inline bool burst_cache_expired(struct bore_bc *bc, u64 now) { ++ struct bore_bc bc_val = { .value = READ_ONCE(bc->value) }; ++ u64 timestamp = (u64)bc_val.timestamp << BORE_BC_TIMESTAMP_SHIFT; ++ return now - timestamp > (u64)sched_burst_cache_lifetime; ++} ++ ++static void update_burst_cache(struct bore_bc *bc, ++ struct task_struct *p, u32 count, u32 total, u64 now) { ++ u32 average = (count == 1) ? total : ++ (u32)(((u64)total * bore_reciprocal_lut[count]) >> 32); ++ ++ struct bore_bc new_bc = { ++ .penalty = max(average, p->bore.penalty), ++ .timestamp = now >> BORE_BC_TIMESTAMP_SHIFT ++ }; ++ WRITE_ONCE(bc->value, new_bc.value); ++} ++ ++static u32 inherit_from_parent(struct task_struct *parent, ++ u64 clone_flags, u64 now) { ++ struct bore_bc bc_val; ++ ++ if (clone_flags & CLONE_PARENT) ++ parent = rcu_dereference(parent->real_parent); ++ ++ struct bore_bc *bc = &parent->bore.subtree; ++ ++ if (burst_cache_expired(bc, now)) { ++ struct task_struct *child; ++ u32 count = 0, total = 0, scan_count = 0; ++ for_each_child_task(parent, child) { ++ if (count >= BURST_CACHE_SAMPLE_LIMIT) break; ++ if (scan_count++ >= BURST_CACHE_SCAN_LIMIT) break; ++ ++ if (!task_is_bore_eligible(child)) continue; ++ count++; ++ total += child->bore.penalty; ++ } ++ ++ update_burst_cache(bc, parent, count, total, now); ++ } ++ ++ bc_val.value = READ_ONCE(bc->value); ++ return (u32)bc_val.penalty; ++} ++ ++static u32 inherit_from_ancestor_hub(struct task_struct *parent, ++ u64 clone_flags, u64 now) { ++ struct bore_bc bc_val; ++ struct task_struct *ancestor = parent; ++ u32 sole_child_count = 0; ++ ++ if (clone_flags & CLONE_PARENT) { ++ ancestor = rcu_dereference(ancestor->real_parent); ++ sole_child_count = 1; ++ } ++ ++ for (struct task_struct *next; ++ (next = rcu_dereference(ancestor->real_parent)) != ancestor && ++ count_children_upto2(ancestor) <= sole_child_count; ++ ancestor = next, sole_child_count = 1) {} ++ ++ struct bore_bc *bc = &ancestor->bore.subtree; ++ ++ if (burst_cache_expired(bc, now)) { ++ struct task_struct *direct_child; ++ u32 count = 0, total = 0, scan_count = 0; ++ for_each_child_task(ancestor, direct_child) { ++ if (count >= BURST_CACHE_SAMPLE_LIMIT) break; ++ if (scan_count++ >= BURST_CACHE_SCAN_LIMIT) break; ++ ++ struct task_struct *descendant = direct_child; ++ while (count_children_upto2(descendant) == 1) { ++ struct task_struct *next_descendant = ++ list_first_or_null_rcu(&descendant->children, ++ struct task_struct, sibling); ++ if (!next_descendant) break; ++ descendant = next_descendant; ++ } ++ ++ if (!task_is_bore_eligible(descendant)) continue; ++ count++; ++ total += descendant->bore.penalty; ++ } ++ ++ update_burst_cache(bc, ancestor, count, total, now); ++ } ++ ++ bc_val.value = READ_ONCE(bc->value); ++ return (u32)bc_val.penalty; ++} ++ ++static u32 inherit_from_thread_group(struct task_struct *p, u64 now) { ++ struct bore_bc bc_val; ++ struct task_struct *leader = p->group_leader; ++ struct bore_bc *bc = &leader->bore.group; ++ ++ if (burst_cache_expired(bc, now)) { ++ struct task_struct *sibling; ++ u32 count = 0, total = 0, scan_count = 0; ++ ++ for_each_thread(leader, sibling) { ++ if (count >= BURST_CACHE_SAMPLE_LIMIT) break; ++ if (scan_count++ >= BURST_CACHE_SCAN_LIMIT) break; ++ ++ if (!task_is_bore_eligible(sibling)) continue; ++ count++; ++ total += sibling->bore.penalty; ++ } ++ ++ update_burst_cache(bc, leader, count, total, now); ++ } ++ ++ bc_val.value = READ_ONCE(bc->value); ++ return (u32)bc_val.penalty; ++} ++ ++void task_fork_bore(struct task_struct *p, ++ struct task_struct *parent, u64 clone_flags, u64 now) { ++ if (!static_branch_likely(&sched_bore_key) || !task_is_bore_eligible(p)) return; ++ ++ rcu_read_lock(); ++ struct bore_ctx *ctx = &p->bore; ++ u32 inherited_penalty; ++ if (clone_flags & CLONE_THREAD) ++ inherited_penalty = inherit_from_thread_group(parent, now); ++ else if (static_branch_likely(&sched_burst_inherit_key)) ++ inherited_penalty = static_branch_likely(&sched_burst_ancestor_key)? ++ inherit_from_ancestor_hub(parent, clone_flags, now): ++ inherit_from_parent(parent, clone_flags, now); ++ else ++ inherited_penalty = 0; ++ ++ if (ctx->prev_penalty < inherited_penalty) ++ ctx->prev_penalty = inherited_penalty; ++ ctx->curr_penalty = 0; ++ ctx->burst_time = 0; ++ ctx->stop_update = false; ++ ctx->futex_waiting = false; ++ update_penalty(p); ++ rcu_read_unlock(); ++} ++ ++void reset_task_bore(struct task_struct *p) ++{ memset(&p->bore, 0, sizeof(struct bore_ctx)); } ++ ++static void update_inherit_type(void) { ++ switch(sched_burst_inherit_type) { ++ case 1: ++ static_branch_enable(&sched_burst_inherit_key); ++ static_branch_disable(&sched_burst_ancestor_key); ++ break; ++ case 2: ++ static_branch_enable(&sched_burst_inherit_key); ++ static_branch_enable(&sched_burst_ancestor_key); ++ break; ++ default: ++ static_branch_disable(&sched_burst_inherit_key); ++ break; ++ } ++} ++ ++void __init sched_init_bore(void) { ++ printk(KERN_INFO "%s %s by %s\n", ++ SCHED_BORE_PROGNAME, SCHED_BORE_VERSION, SCHED_BORE_AUTHOR); ++ ++ for (int i = 1; i <= BURST_CACHE_SAMPLE_LIMIT; i++) ++ bore_reciprocal_lut[i] = (u32)div64_u64(0xffffffffULL + i, i); ++ ++ reset_task_bore(&init_task); ++ update_inherit_type(); ++} ++ ++static void readjust_all_task_weights(void) { ++ struct task_struct *task; ++ struct rq *rq; ++ struct rq_flags rf; ++ ++ scoped_guard(write_lock_irq, &tasklist_lock) ++ for_each_process(task) { ++ if (!task_is_bore_eligible(task)) continue; ++ rq = task_rq_lock(task, &rf); ++ update_rq_clock(rq); ++ reweight_task_by_prio(task, effective_prio_bore(task)); ++ task_rq_unlock(rq, task, &rf); ++ } ++} ++ ++int sched_bore_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos) { ++ int ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos); ++ if (ret || !write) ++ return ret; ++ ++ if (sched_bore) ++ static_branch_enable(&sched_bore_key); ++ else ++ static_branch_disable(&sched_bore_key); ++ ++ readjust_all_task_weights(); ++ ++ return 0; ++} ++ ++int sched_burst_inherit_type_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos) { ++ int ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos); ++ if (ret || !write) ++ return ret; ++ ++ update_inherit_type(); ++ ++ return 0; ++} ++ ++int sched_burst_protect_slice_lv_update_handler(const struct ctl_table *table, ++ int write, void __user *buffer, size_t *lenp, loff_t *ppos) { ++ int ret = proc_dou8vec_minmax(table, write, buffer, lenp, ppos); ++ if (ret || !write) ++ return ret; ++ ++ if (sched_burst_protect_slice_lv == 1 || ++ sched_burst_protect_slice_lv == 2) ++ static_branch_enable(&sched_burst_protect_slice_cond_key); ++ else ++ static_branch_disable(&sched_burst_protect_slice_cond_key); ++ ++ if (sched_burst_protect_slice_lv >= 2) ++ static_branch_enable(&sched_burst_protect_slice_prefer_key); ++ else ++ static_branch_disable(&sched_burst_protect_slice_prefer_key); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_SYSCTL ++static struct ctl_table sched_bore_sysctls[] = { ++ { ++ .procname = "sched_bore", ++ .data = &sched_bore, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = sched_bore_update_handler, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "sched_burst_inherit_type", ++ .data = &sched_burst_inherit_type, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = sched_burst_inherit_type_update_handler, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_TWO, ++ }, ++ { ++ .procname = "sched_burst_smoothness", ++ .data = &sched_burst_smoothness, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_THREE, ++ }, ++ { ++ .procname = "sched_burst_penalty_offset", ++ .data = &sched_burst_penalty_offset, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = proc_dou8vec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &maxval_6_bits, ++ }, ++ { ++ .procname = "sched_burst_penalty_scale", ++ .data = &sched_burst_penalty_scale, ++ .maxlen = sizeof(uint), ++ .mode = 0644, ++ .proc_handler = proc_douintvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = &maxval_12_bits, ++ }, ++ { ++ .procname = "sched_burst_cache_lifetime", ++ .data = &sched_burst_cache_lifetime, ++ .maxlen = sizeof(uint), ++ .mode = 0644, ++ .proc_handler = proc_douintvec, ++ }, ++ { ++ .procname = "sched_burst_protect_slice_lv", ++ .data = &sched_burst_protect_slice_lv, ++ .maxlen = sizeof(u8), ++ .mode = 0644, ++ .proc_handler = sched_burst_protect_slice_lv_update_handler, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_THREE, ++ }, ++}; ++ ++static int __init sched_bore_sysctl_init(void) { ++ register_sysctl_init("kernel", sched_bore_sysctls); ++ return 0; ++} ++late_initcall(sched_bore_sysctl_init); ++ ++#endif // CONFIG_SYSCTL ++#endif /* CONFIG_SCHED_BORE */ +diff --git a/kernel/sched/build_utility.c b/kernel/sched/build_utility.c +--- a/kernel/sched/build_utility.c ++++ b/kernel/sched/build_utility.c +@@ -54,6 +54,10 @@ + #include "stats.h" + #include "autogroup.h" + ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ ++ + #include "clock.c" + + #ifdef CONFIG_CGROUP_CPUACCT +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -100,6 +100,10 @@ + #include "../smpboot.h" + #include "../locking/mutex.h" + ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ ++ + EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpu); + EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpumask); + +@@ -1534,7 +1538,11 @@ int tg_nop(struct task_group *tg, void *data) + + void set_load_weight(struct task_struct *p, bool update_load) + { ++#ifdef CONFIG_SCHED_BORE ++ int prio = effective_prio_bore(p); ++#else /* !CONFIG_SCHED_BORE */ + int prio = p->static_prio - MAX_RT_PRIO; ++#endif /* CONFIG_SCHED_BORE */ + struct load_weight lw; + + if (task_has_idle_policy(p)) { +@@ -8975,6 +8983,10 @@ void __init sched_init(void) + BUG_ON(!sched_class_above(&ext_sched_class, &idle_sched_class)); + #endif + ++#ifdef CONFIG_SCHED_BORE ++ sched_init_bore(); ++#endif /* CONFIG_SCHED_BORE */ ++ + wait_bit_init(); + + #ifdef CONFIG_FAIR_GROUP_SCHED +diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c +--- a/kernel/sched/debug.c ++++ b/kernel/sched/debug.c +@@ -170,6 +170,53 @@ static const struct file_operations sched_feat_fops = { + .release = single_release, + }; + ++#ifdef CONFIG_SCHED_BORE ++#define DEFINE_SYSCTL_SCHED_FUNC(name, update_func) \ ++static ssize_t sched_##name##_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) \ ++{ \ ++ char buf[16]; \ ++ unsigned int value; \ ++\ ++ if (cnt > 15) \ ++ cnt = 15; \ ++\ ++ if (copy_from_user(&buf, ubuf, cnt)) \ ++ return -EFAULT; \ ++ buf[cnt] = '\0'; \ ++\ ++ if (kstrtouint(buf, 10, &value)) \ ++ return -EINVAL; \ ++\ ++ sysctl_sched_##name = value; \ ++ sched_update_##update_func(); \ ++\ ++ *ppos += cnt; \ ++ return cnt; \ ++} \ ++\ ++static int sched_##name##_show(struct seq_file *m, void *v) \ ++{ \ ++ seq_printf(m, "%d\n", sysctl_sched_##name); \ ++ return 0; \ ++} \ ++\ ++static int sched_##name##_open(struct inode *inode, struct file *filp) \ ++{ \ ++ return single_open(filp, sched_##name##_show, NULL); \ ++} \ ++\ ++static const struct file_operations sched_##name##_fops = { \ ++ .open = sched_##name##_open, \ ++ .write = sched_##name##_write, \ ++ .read = seq_read, \ ++ .llseek = seq_lseek, \ ++ .release = single_release, \ ++}; ++ ++DEFINE_SYSCTL_SCHED_FUNC(min_base_slice, min_base_slice) ++ ++#undef DEFINE_SYSCTL_SCHED_FUNC ++#else /* !CONFIG_SCHED_BORE */ + static ssize_t sched_scaling_write(struct file *filp, const char __user *ubuf, + size_t cnt, loff_t *ppos) + { +@@ -209,6 +256,7 @@ static const struct file_operations sched_scaling_fops = { + .llseek = seq_lseek, + .release = single_release, + }; ++#endif /* CONFIG_SCHED_BORE */ + + #ifdef CONFIG_SCHED_CACHE + static ssize_t +@@ -645,12 +693,19 @@ static __init int sched_init_debug(void) + debugfs_create_file("preempt", 0644, debugfs_sched, NULL, &sched_dynamic_fops); + #endif + ++#ifdef CONFIG_SCHED_BORE ++ debugfs_create_file("min_base_slice_ns", 0644, debugfs_sched, NULL, &sched_min_base_slice_fops); ++ debugfs_create_u32("base_slice_ns", 0444, debugfs_sched, &sysctl_sched_base_slice); ++#else /* !CONFIG_SCHED_BORE */ + debugfs_create_u32("base_slice_ns", 0644, debugfs_sched, &sysctl_sched_base_slice); ++#endif /* CONFIG_SCHED_BORE */ + + debugfs_create_u32("latency_warn_ms", 0644, debugfs_sched, &sysctl_resched_latency_warn_ms); + debugfs_create_u32("latency_warn_once", 0644, debugfs_sched, &sysctl_resched_latency_warn_once); + ++#if !defined(CONFIG_SCHED_BORE) + debugfs_create_file("tunable_scaling", 0644, debugfs_sched, NULL, &sched_scaling_fops); ++#endif /* CONFIG_SCHED_BORE */ + debugfs_create_u32("migration_cost_ns", 0644, debugfs_sched, &sysctl_sched_migration_cost); + debugfs_create_u32("nr_migrate", 0644, debugfs_sched, &sysctl_sched_nr_migrate); + +@@ -911,6 +966,9 @@ print_task(struct seq_file *m, struct rq *rq, struct task_struct *p) + SPLIT_NS(schedstat_val_or_zero(p->stats.sum_sleep_runtime)), + SPLIT_NS(schedstat_val_or_zero(p->stats.sum_block_runtime))); + ++#ifdef CONFIG_SCHED_BORE ++ SEQ_printf(m, " %2d", bore_score(p)); ++#endif /* CONFIG_SCHED_BORE */ + #ifdef CONFIG_NUMA_BALANCING + SEQ_printf(m, " %d %d", task_node(p), task_numa_group_id(p)); + #endif +@@ -1401,6 +1459,9 @@ void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, + __PS("nr_involuntary_switches", p->nivcsw); + + P(se.load.weight); ++#ifdef CONFIG_SCHED_BORE ++ __PS("bore.score", bore_score(p)); ++#endif /* CONFIG_SCHED_BORE */ + P(se.avg.load_sum); + P(se.avg.runnable_sum); + P(se.avg.util_sum); +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -58,6 +58,10 @@ + #include "stats.h" + #include "autogroup.h" + ++#ifdef CONFIG_SCHED_BORE ++#include ++#endif /* CONFIG_SCHED_BORE */ ++ + /* + * The initial- and re-scaling of tunables is configurable + * +@@ -67,17 +71,30 @@ + * SCHED_TUNABLESCALING_LOG - scaled logarithmically, *1+ilog(ncpus) + * SCHED_TUNABLESCALING_LINEAR - scaled linear, *ncpus + * +- * (default SCHED_TUNABLESCALING_LOG = *(1+ilog(ncpus)) ++ * BORE : default SCHED_TUNABLESCALING_NONE = *1 constant ++ * EEVDF: default SCHED_TUNABLESCALING_LOG = *(1+ilog(ncpus)) + */ ++#ifdef CONFIG_SCHED_BORE ++unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_NONE; ++#else /* !CONFIG_SCHED_BORE */ + unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; ++#endif /* CONFIG_SCHED_BORE */ + + /* + * Minimal preemption granularity for CPU-bound tasks: + * +- * (default: 0.70 msec * (1 + ilog(ncpus)), units: nanoseconds) ++ * BORE : base_slice = minimum multiple of nsecs_per_tick >= min_base_slice ++ * (default min_base_slice = 2000000 constant, units: nanoseconds) ++ * EEVDF: default 0.70 msec * (1 + ilog(ncpus)), units: nanoseconds + */ ++#ifdef CONFIG_SCHED_BORE ++static const unsigned int nsecs_per_tick = 1000000000ULL / HZ; ++unsigned int sysctl_sched_min_base_slice = CONFIG_MIN_BASE_SLICE_NS; ++__read_mostly uint sysctl_sched_base_slice = nsecs_per_tick; ++#else /* !CONFIG_SCHED_BORE */ + unsigned int sysctl_sched_base_slice = 700000ULL; + static unsigned int normalized_sysctl_sched_base_slice = 700000ULL; ++#endif /* CONFIG_SCHED_BORE */ + + __read_mostly unsigned int sysctl_sched_migration_cost = 500000UL; + +@@ -189,6 +206,13 @@ static inline void update_load_set(struct load_weight *lw, unsigned long w) + * + * This idea comes from the SD scheduler of Con Kolivas: + */ ++#ifdef CONFIG_SCHED_BORE ++static void update_sysctl(void) { ++ sysctl_sched_base_slice = nsecs_per_tick * ++ max(1UL, DIV_ROUND_UP(sysctl_sched_min_base_slice, nsecs_per_tick)); ++} ++void sched_update_min_base_slice(void) { update_sysctl(); } ++#else /* !CONFIG_SCHED_BORE */ + static unsigned int get_update_sysctl_factor(void) + { + unsigned int cpus = min_t(unsigned int, num_online_cpus(), 8); +@@ -219,6 +243,7 @@ static void update_sysctl(void) + SET_SYSCTL(sched_base_slice); + #undef SET_SYSCTL + } ++#endif /* CONFIG_SCHED_BORE */ + + void __init sched_init_granularity(void) + { +@@ -1083,7 +1108,11 @@ struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq) + */ + static inline void set_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity *se) + { ++#ifdef CONFIG_SCHED_BORE ++ u64 slice = sysctl_sched_base_slice; ++#else /* CONFIG_SCHED_BORE */ + u64 slice = normalized_sysctl_sched_base_slice; ++#endif /* CONFIG_SCHED_BORE */ + u64 vprot = se->deadline; + + if (sched_feat(RUN_TO_PARITY)) +@@ -1160,8 +1189,17 @@ static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq, bool protect) + if (curr && (!curr->on_rq || !entity_eligible(cfs_rq, curr))) + curr = NULL; + +- if (curr && protect && protect_slice(curr)) +- return curr; ++ if (curr && protect && protect_slice(curr)) { ++#ifdef CONFIG_SCHED_BORE ++ if (static_branch_likely(&sched_bore_key)) { ++ if ((static_branch_likely(&sched_burst_protect_slice_cond_key) || ++ static_branch_unlikely(&sched_burst_protect_slice_prefer_key)) && ++ (!entity_is_task(curr) || !task_of(curr)->bore.futex_waiting)) ++ return curr; ++ } else ++#endif /* CONFIG_SCHED_BORE */ ++ return curr; ++ } + + /* Pick the leftmost entity if it's eligible */ + if (se && entity_eligible(cfs_rq, se)) { +@@ -1217,6 +1255,7 @@ struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) + /************************************************************** + * Scheduling class statistics methods: + */ ++#if !defined(CONFIG_SCHED_BORE) + int sched_update_scaling(void) + { + unsigned int factor = get_update_sysctl_factor(); +@@ -1228,6 +1267,7 @@ int sched_update_scaling(void) + + return 0; + } ++#endif /* CONFIG_SCHED_BORE */ + + static void clear_buddies(struct cfs_rq *cfs_rq, struct sched_entity *se); + +@@ -1971,6 +2011,38 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) {} + + #endif /* CONFIG_SCHED_CACHE */ + ++#ifdef CONFIG_SCHED_BORE ++static inline bool do_preempt_weight(struct cfs_rq *cfs_rq, ++ struct sched_entity *pse, struct sched_entity *se) ++{ ++ /* ++ * Companion to the PREEMPT_SHORT path: where that lets a shorter-slice ++ * waker cancel current's slice protection, this lets a heavier (under ++ * BORE: more interactive) waker do so. No RUN_TO_PARITY gate -- in 7.0 ++ * the slice protection (vprot) exists regardless of RUN_TO_PARITY, so ++ * the weight bypass must apply either way. ++ */ ++ if (!static_branch_likely(&sched_burst_protect_slice_cond_key)) ++ return false; ++ ++ if (static_branch_unlikely(&sched_burst_protect_slice_prefer_key) ++ ? (pse->load.weight <= se->load.weight) ++ : (pse->load.weight < se->load.weight)) ++ return false; ++ ++ if (!entity_eligible(cfs_rq, pse)) ++ return false; ++ ++ if (entity_before(pse, se)) ++ return true; ++ ++ if (!entity_eligible(cfs_rq, se)) ++ return true; ++ ++ return false; ++} ++#endif /* CONFIG_SCHED_BORE */ ++ + /* + * Used by other classes to account runtime. + */ +@@ -2006,6 +2078,11 @@ static void update_curr(struct cfs_rq *cfs_rq) + resched = update_deadline(cfs_rq, curr); + + if (entity_is_task(curr)) { ++#ifdef CONFIG_SCHED_BORE ++ struct task_struct *p = task_of(curr); ++ update_curr_bore(p, delta_exec); ++#endif /* CONFIG_SCHED_BORE */ ++ + /* + * If the fair_server is active, we need to account for the + * fair_server time whether or not the task is running on +@@ -4663,8 +4740,8 @@ rescale_entity(struct sched_entity *se, unsigned long weight, bool rel_vprot) + se->vprot = div64_long(se->vprot * old_weight, weight); + } + +-static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, +- unsigned long weight) ++void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, ++ unsigned long weight) + { + bool curr = cfs_rq->curr == se; + bool rel_vprot = false; +@@ -5967,13 +6044,12 @@ void __setparam_fair(struct task_struct *p, const struct sched_attr *attr) + static void + place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + { +- u64 vslice, vruntime = avg_vruntime(cfs_rq); ++ u64 vslice = 0, vruntime = avg_vruntime(cfs_rq); + bool update_zero = false; + s64 lag = 0; + + if (!se->custom_slice) + se->slice = sysctl_sched_base_slice; +- vslice = calc_delta_fair(se->slice, se); + + /* + * Due to how V is constructed as the weighted average of entities, +@@ -6085,7 +6161,18 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + se->rel_deadline = 0; + return; + } +- ++#ifdef CONFIG_SCHED_BORE ++ if (static_branch_likely(&sched_bore_key) && ++ entity_is_task(se) && ++ task_of(se)->bore.futex_waiting) ++ goto vslice_found; ++#endif /* !CONFIG_SCHED_BORE */ ++ vslice = calc_delta_fair(se->slice, se); ++#ifdef CONFIG_SCHED_BORE ++ if (static_branch_likely(&sched_bore_key)) ++ vslice >>= !!(flags & (ENQUEUE_INITIAL | ENQUEUE_WAKEUP)); ++ else ++#endif /* CONFIG_SCHED_BORE */ + /* + * When joining the competition; the existing tasks will be, + * on average, halfway through their slice, as such start tasks +@@ -6094,6 +6181,9 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + if (sched_feat(PLACE_DEADLINE_INITIAL) && (flags & ENQUEUE_INITIAL)) + vslice /= 2; + ++#ifdef CONFIG_SCHED_BORE ++vslice_found: ++#endif /* CONFIG_SCHED_BORE */ + /* + * EEVDF: vd_i = ve_i + r_i/w_i + */ +@@ -6104,7 +6194,7 @@ static void check_enqueue_throttle(struct cfs_rq *cfs_rq); + static inline int cfs_rq_throttled(struct cfs_rq *cfs_rq); + + static void +-requeue_delayed_entity(struct sched_entity *se); ++requeue_delayed_entity(struct sched_entity *se, int flags); + + static void + enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) +@@ -7767,7 +7857,7 @@ static int choose_idle_cpu(int cpu, struct task_struct *p) + } + + static void +-requeue_delayed_entity(struct sched_entity *se) ++requeue_delayed_entity(struct sched_entity *se, int flags) + { + struct cfs_rq *cfs_rq = cfs_rq_of(se); + +@@ -7785,7 +7875,13 @@ requeue_delayed_entity(struct sched_entity *se) + cfs_rq->nr_queued--; + if (se != cfs_rq->curr) + __dequeue_entity(cfs_rq, se); +- place_entity(cfs_rq, se, 0); ++#ifdef CONFIG_SCHED_BORE ++ if (static_branch_likely(&sched_bore_key)) ++ flags |= ENQUEUE_WAKEUP; ++ else ++#endif /* CONFIG_SCHED_BORE */ ++ flags = 0; ++ place_entity(cfs_rq, se, flags); + if (se != cfs_rq->curr) + __enqueue_entity(cfs_rq, se); + cfs_rq->nr_queued++; +@@ -7824,7 +7920,7 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) + util_est_enqueue(&rq->cfs, p); + + if (flags & ENQUEUE_DELAYED) { +- requeue_delayed_entity(se); ++ requeue_delayed_entity(se, flags); + return; + } + +@@ -7842,7 +7938,7 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) + for_each_sched_entity(se) { + if (se->on_rq) { + if (se->sched_delayed) +- requeue_delayed_entity(se); ++ requeue_delayed_entity(se, flags); + break; + } + cfs_rq = cfs_rq_of(se); +@@ -8045,6 +8141,15 @@ static bool dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags) + if (!p->se.sched_delayed) + util_est_dequeue(&rq->cfs, p); + ++#ifdef CONFIG_SCHED_BORE ++ struct cfs_rq *cfs_rq = cfs_rq_of(&p->se); ++ struct sched_entity *se = &p->se; ++ if ((flags & DEQUEUE_SLEEP) && entity_is_task(se)) { ++ if (cfs_rq->curr == se) ++ update_curr(cfs_rq); ++ restart_burst_bore(p); ++ } ++#endif /* CONFIG_SCHED_BORE */ + if (dequeue_entities(rq, &p->se, flags) < 0) + return false; + +@@ -9850,6 +9955,18 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f + goto pick; + } + ++#ifdef CONFIG_SCHED_BORE ++ /* ++ * Likewise, if @p is heavier than current (under BORE: more ++ * interactive), per kernel.sched_burst_protect_slice_lv, override ++ * current's slice protection to allow preemption. ++ */ ++ if (do_preempt_weight(cfs_rq, pse, se)) { ++ preempt_action = PREEMPT_WAKEUP_SHORT; ++ goto pick; ++ } ++#endif ++ + /* + * Ignore wakee preemption on WF_FORK as it is less likely that + * there is shared data as exec often follow fork. Do not +@@ -10020,16 +10137,25 @@ static void yield_task_fair(struct rq *rq) + /* + * Are we the only task in the tree? + */ ++#if !defined(CONFIG_SCHED_BORE) + if (unlikely(rq->nr_running == 1)) + return; + + clear_buddies(cfs_rq, se); ++#endif /* CONFIG_SCHED_BORE */ + + update_rq_clock(rq); + /* + * Update run-time statistics of the 'current'. + */ + update_curr(cfs_rq); ++#ifdef CONFIG_SCHED_BORE ++ restart_burst_rescale_deadline_bore(curr); ++ if (unlikely(rq->nr_running == 1)) ++ return; ++ ++ clear_buddies(cfs_rq, se); ++#endif /* CONFIG_SCHED_BORE */ + /* + * Tell update_rq_clock() that we've just updated, + * so we don't do microscopic update in schedule() +@@ -15010,6 +15136,9 @@ static void switched_to_fair(struct rq *rq, struct task_struct *p) + WARN_ON_ONCE(p->se.sched_delayed); + + attach_task_cfs_rq(p); ++#ifdef CONFIG_SCHED_BORE ++ reset_task_bore(p); ++#endif /* CONFIG_SCHED_BORE */ + + set_task_max_allowed_capacity(p); + +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -2267,7 +2267,11 @@ extern int group_balance_cpu(struct sched_group *sg); + extern void update_sched_domain_debugfs(void); + extern void dirty_sched_domain_sysctl(int cpu); + ++#ifdef CONFIG_SCHED_BORE ++extern void sched_update_min_base_slice(void); ++#else /* !CONFIG_SCHED_BORE */ + extern int sched_update_scaling(void); ++#endif /* CONFIG_SCHED_BORE */ + + static inline const struct cpumask *task_user_cpus(struct task_struct *p) + { +@@ -3112,7 +3116,12 @@ static inline void attach_one_task(struct rq *rq, struct task_struct *p) + extern __read_mostly unsigned int sysctl_sched_nr_migrate; + extern __read_mostly unsigned int sysctl_sched_migration_cost; + ++#ifdef CONFIG_SCHED_BORE ++extern unsigned int sysctl_sched_min_base_slice; ++extern __read_mostly uint sysctl_sched_base_slice; ++#else /* !CONFIG_SCHED_BORE */ + extern unsigned int sysctl_sched_base_slice; ++#endif /* CONFIG_SCHED_BORE */ + + extern int sysctl_resched_latency_warn_ms; + extern int sysctl_resched_latency_warn_once; diff --git a/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch.sig b/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch.sig new file mode 100644 index 0000000..6256900 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch new file mode 100644 index 0000000..712b9b7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch @@ -0,0 +1,763 @@ +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/pkeys.h b/arch/x86/include/asm/pkeys.h +--- a/arch/x86/include/asm/pkeys.h ++++ b/arch/x86/include/asm/pkeys.h +@@ -88,6 +88,9 @@ int mm_pkey_alloc(struct mm_struct *mm) + u16 all_pkeys_mask = ((1U << arch_max_pkey()) - 1); + int ret; + ++ if (!arch_pkeys_enabled()) ++ return -1; ++ + /* + * Are we out of pkeys? We must handle this specially + * because ffz() behavior is undefined if there are no +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 +@@ -62,18 +62,18 @@ enum cpa_warn { + static const int cpa_warn_level = CPA_PROTECT; + + /* +- * Serialize cpa() (for !DEBUG_PAGEALLOC which uses large identity mappings) +- * using cpa_lock. So that we don't allow any other cpu, with stale large tlb +- * entries change the page attribute in parallel to some other cpu +- * splitting a large page entry along with changing the attribute. ++ * Serialize cpa() using cpa_lock so that we don't allow any other cpu, with ++ * stale large tlb entries, to change the page attribute in parallel to some ++ * other cpu splitting a large page entry along with changing the attribute. + */ + static DEFINE_SPINLOCK(cpa_lock); + +-#define CPA_FLUSHTLB 1 +-#define CPA_ARRAY 2 +-#define CPA_PAGES_ARRAY 4 +-#define CPA_NO_CHECK_ALIAS 8 /* Do not search for aliases */ +-#define CPA_COLLAPSE 16 /* try to collapse large pages */ ++#define CPA_FLUSHTLB 0x01 ++#define CPA_ARRAY 0x02 ++#define CPA_PAGES_ARRAY 0x04 ++#define CPA_NO_CHECK_ALIAS 0x08 /* Do not search for aliases */ ++#define CPA_COLLAPSE 0x10 /* try to collapse large pages */ ++#define CPA_DEBUG_PAGEALLOC 0x20 + + static inline pgprot_t cachemode2pgprot(enum page_cache_mode pcm) + { +@@ -86,9 +86,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) +@@ -418,6 +417,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) + int collapsed = 0; + int i; + ++ guard(spinlock)(&cpa_lock); ++ + if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) { + for (i = 0; i < cpa->numpages; i++) + collapsed += collapse_large_pages(__cpa_addr(cpa, i), +@@ -888,24 +889,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) +@@ -1075,16 +1075,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, +@@ -1135,16 +1130,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)); + +@@ -1177,7 +1170,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + break; + + default: +- spin_unlock(&pgd_lock); + return 1; + } + +@@ -1225,7 +1217,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; + } +@@ -1235,11 +1226,9 @@ static int split_large_page(struct cpa_data *cpa, pte_t *kpte, + { + struct ptdesc *ptdesc; + +- if (!debug_pagealloc_enabled()) +- spin_unlock(&cpa_lock); ++ spin_unlock(&cpa_lock); + ptdesc = pagetable_alloc(GFP_KERNEL, 0); +- if (!debug_pagealloc_enabled()) +- spin_lock(&cpa_lock); ++ spin_lock(&cpa_lock); + if (!ptdesc) + return -ENOMEM; + +@@ -1298,11 +1287,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); +@@ -1376,7 +1365,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; +@@ -1384,26 +1373,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; + } + +@@ -2004,6 +1991,7 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + { + unsigned long numpages = cpa->numpages; + unsigned long rempages = numpages; ++ bool lock = true; + int ret = 0; + + /* +@@ -2013,6 +2001,29 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + !cpa->force_split) + return ret; + ++ /* ++ * DEBUG_PAGEALLOC is special; it is called from any context the ++ * page-allocator is, which violates the normal cpa_lock locking ++ * rules. ++ * ++ * However, since it is part of the page-allocator, things are still ++ * properly serialized by the page-allocator locking and the fact that ++ * when a page is owned by the page-allocator, it isn't owned by ++ * anybody else. That is, you *SHOULD NOT* be calling cpa() on memory ++ * that isn't allocated. ++ * ++ * Additionally, DEBUG_PAGEALLOC ensures (per probe_page_size_mask()) ++ * that the kernel mapping is 4k pages, therefore there are no large ++ * pages to split/collapse. ++ * ++ * Furthermore, the page-allocator strictly manages pages that ++ * *exist*, avoiding pgd_lock. ++ * ++ * Therefore, it is safe to not take cpa_lock. ++ */ ++ if (debug_pagealloc_enabled() && (cpa->flags & CPA_DEBUG_PAGEALLOC)) ++ lock = false; ++ + while (rempages) { + /* + * Store the remaining nr of pages for the large page +@@ -2023,11 +2034,12 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + if (cpa->flags & (CPA_ARRAY | CPA_PAGES_ARRAY)) + cpa->numpages = 1; + +- if (!debug_pagealloc_enabled()) +- spin_lock(&cpa_lock); +- ret = __change_page_attr(cpa, primary); +- if (!debug_pagealloc_enabled()) +- spin_unlock(&cpa_lock); ++ if (lock) { ++ guard(spinlock)(&cpa_lock); ++ ret = __change_page_attr(cpa, primary); ++ } else { ++ ret = __change_page_attr(cpa, primary); ++ } + if (ret) + goto out; + +@@ -2606,7 +2618,7 @@ int set_pages_rw(struct page *page, int numpages) + return set_memory_rw(addr, numpages); + } + +-static int __set_pages_p(struct page *page, int numpages) ++static int __set_pages_p(struct page *page, int numpages, unsigned int cpa_flags) + { + unsigned long tempaddr = (unsigned long) page_address(page); + struct cpa_data cpa = { .vaddr = &tempaddr, +@@ -2614,7 +2626,7 @@ static int __set_pages_p(struct page *page, int numpages) + .numpages = numpages, + .mask_set = __pgprot(_PAGE_PRESENT | _PAGE_RW), + .mask_clr = __pgprot(0), +- .flags = CPA_NO_CHECK_ALIAS }; ++ .flags = CPA_NO_CHECK_ALIAS | cpa_flags }; + + /* + * No alias checking needed for setting present flag. otherwise, +@@ -2625,7 +2637,7 @@ static int __set_pages_p(struct page *page, int numpages) + return __change_page_attr_set_clr(&cpa, 1); + } + +-static int __set_pages_np(struct page *page, int numpages) ++static int __set_pages_np(struct page *page, int numpages, unsigned int cpa_flags) + { + unsigned long tempaddr = (unsigned long) page_address(page); + struct cpa_data cpa = { .vaddr = &tempaddr, +@@ -2633,7 +2645,7 @@ static int __set_pages_np(struct page *page, int numpages) + .numpages = numpages, + .mask_set = __pgprot(0), + .mask_clr = __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY), +- .flags = CPA_NO_CHECK_ALIAS }; ++ .flags = CPA_NO_CHECK_ALIAS | cpa_flags }; + + /* + * No alias checking needed for setting not present flag. otherwise, +@@ -2646,20 +2658,20 @@ static int __set_pages_np(struct page *page, int numpages) + + int set_direct_map_invalid_noflush(struct page *page) + { +- return __set_pages_np(page, 1); ++ return __set_pages_np(page, 1, 0); + } + + int set_direct_map_default_noflush(struct page *page) + { +- return __set_pages_p(page, 1); ++ return __set_pages_p(page, 1, 0); + } + + int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid) + { + if (valid) +- return __set_pages_p(page, nr); ++ return __set_pages_p(page, nr, 0); + +- return __set_pages_np(page, nr); ++ return __set_pages_np(page, nr, 0); + } + + #ifdef CONFIG_DEBUG_PAGEALLOC +@@ -2678,15 +2690,23 @@ void __kernel_map_pages(struct page *page, int numpages, int enable) + * and hence no memory allocations during large page split. + */ + if (enable) +- __set_pages_p(page, numpages); ++ __set_pages_p(page, numpages, CPA_DEBUG_PAGEALLOC); + else +- __set_pages_np(page, numpages); ++ __set_pages_np(page, numpages, CPA_DEBUG_PAGEALLOC); + + /* +- * We should perform an IPI and flush all tlbs, +- * but that can deadlock->flush only current cpu. +- * Preemption needs to be disabled around __flush_tlb_all() due to +- * CR3 reload in __native_flush_tlb(). ++ * We should perform an IPI and flush all tlbs, but that can ++ * deadlock, settle for a local flush. ++ * ++ * Not doing a global TLB flush means that remote CPUs will retain ++ * stale TLB entries. In case of P->NP (on free) this means the remote ++ * CPUs will not take the faults, making the debug scheme less ++ * reliable. On the NP->P (on alloc) this means the remote CPUs can ++ * take a spurious fault. However spurious_kernel_fault() will observe ++ * *_present() and fix it up. ++ * ++ * Preemption needs to be disabled around __flush_tlb_all() due to CR3 ++ * reload in __native_flush_tlb(). + */ + preempt_disable(); + __flush_tlb_all(); +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-bore/0120-tlbpull.patch.sig b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch.sig new file mode 100644 index 0000000..54e6137 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0121-smp-preempt.patch b/pkgbuilds/linux-omarchy-bore/0121-smp-preempt.patch new file mode 100644 index 0000000..66bd67c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0121-smp-preempt.patch @@ -0,0 +1,514 @@ +diff --git a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -848,6 +848,17 @@ struct bore_ctx { + }; + #endif /* CONFIG_SCHED_BORE */ + ++#if defined(CONFIG_SMP) && defined(CONFIG_PREEMPTION) ++struct task_ipi_mask { ++ union { ++ cpumask_t *ipi_mask_ptr; ++ unsigned long ipi_mask_val; ++ }; ++}; ++#else ++struct task_ipi_mask { }; ++#endif ++ + struct task_struct { + #ifdef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -1387,6 +1398,7 @@ struct task_struct { + struct list_head perf_event_list; + struct perf_ctx_data __rcu *perf_ctx_data; + #endif ++ struct task_ipi_mask __private ipi_mask; + #ifdef CONFIG_DEBUG_PREEMPT + unsigned long preempt_disable_ip; + #endif +diff --git a/include/linux/smp.h b/include/linux/smp.h +--- a/include/linux/smp.h ++++ b/include/linux/smp.h +@@ -47,8 +47,7 @@ extern void __smp_call_single_queue(int cpu, struct llist_node *node); + /* total number of cpus in this system (may exceed NR_CPUS) */ + extern unsigned int total_cpus; + +-int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, +- int wait); ++int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, bool wait); + + void on_each_cpu_cond_mask(smp_cond_func_t cond_func, smp_call_func_t func, + void *info, bool wait, const struct cpumask *mask); +@@ -239,6 +238,18 @@ static inline int get_boot_cpu_id(void) + + #endif /* !SMP */ + ++#if defined(CONFIG_PREEMPTION) && defined(CONFIG_SMP) ++int smp_task_ipi_mask_alloc(struct task_struct *task); ++void smp_task_ipi_mask_free(struct task_struct *task); ++#else ++static inline int smp_task_ipi_mask_alloc(struct task_struct *task) ++{ ++ return 0; ++} ++ ++static inline void smp_task_ipi_mask_free(struct task_struct *task) { } ++#endif ++ + /* + * raw_smp_processor_id() - get the current (unstable) CPU id + * +diff --git a/kernel/fork.c b/kernel/fork.c +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -547,6 +547,7 @@ void free_task(struct task_struct *tsk) + #endif + release_user_cpus_ptr(tsk); + scs_release(tsk); ++ smp_task_ipi_mask_free(tsk); + + #ifndef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -945,10 +946,14 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) + #endif + account_kernel_stack(tsk, 1); + +- err = scs_prepare(tsk, node); ++ err = smp_task_ipi_mask_alloc(tsk); + if (err) + goto free_stack; + ++ err = scs_prepare(tsk, node); ++ if (err) ++ goto free_ipi_mask; ++ + #ifdef CONFIG_SECCOMP + /* + * We must handle setting up seccomp filters once we're under +@@ -1026,6 +1031,8 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) + #endif + return tsk; + ++free_ipi_mask: ++ smp_task_ipi_mask_free(tsk); + free_stack: + exit_task_stack_account(tsk); + free_thread_stack(tsk); +diff --git a/kernel/scftorture.c b/kernel/scftorture.c +--- a/kernel/scftorture.c ++++ b/kernel/scftorture.c +@@ -348,6 +348,8 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + int ret = 0; + struct scf_check *scfcp = NULL; + struct scf_selector *scfsp = scf_sel_rand(trsp); ++ bool is_single = (scfsp->scfs_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-bore/0121-smp-preempt.patch.sig b/pkgbuilds/linux-omarchy-bore/0121-smp-preempt.patch.sig new file mode 100644 index 0000000..078bc77 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0121-smp-preempt.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch b/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch new file mode 100644 index 0000000..9c155d1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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 +@@ -13464,12 +13464,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-bore/0130-sched-detach-tasks.patch.sig b/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch.sig new file mode 100644 index 0000000..3e089bd Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch b/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch new file mode 100644 index 0000000..cdb96f8 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch @@ -0,0 +1,23 @@ +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -3751,11 +3751,17 @@ static inline void ttwu_do_wakeup(struct task_struct *p) + + void update_rq_avg_idle(struct rq *rq) + { +- u64 delta = rq_clock(rq) - rq->idle_stamp; +- u64 max = 2*rq->max_idle_balance_cost; ++ u64 idle_stamp = rq->idle_stamp; ++ u64 delta, max; ++ ++ if (!idle_stamp) ++ return; ++ ++ delta = rq_clock(rq) - idle_stamp; + + update_avg(&rq->avg_idle, delta); + ++ max = 2 * rq->max_idle_balance_cost; + if (rq->avg_idle > max) + rq->avg_idle = max; + rq->idle_stamp = 0; diff --git a/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch.sig b/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch.sig new file mode 100644 index 0000000..3407a4e Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch b/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch new file mode 100644 index 0000000..8754792 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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 +@@ -5099,7 +5099,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 +@@ -5143,7 +5143,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; +@@ -5178,7 +5178,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; +@@ -5252,7 +5252,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)) + { + /* +@@ -5286,7 +5286,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)) +@@ -5340,7 +5340,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 +@@ -1460,12 +1460,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); + } +@@ -1474,7 +1474,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; +@@ -1482,7 +1482,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) +@@ -1585,12 +1585,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; +@@ -1650,33 +1650,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; +@@ -1687,7 +1687,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-bore/0140-sched-always-inline.patch.sig b/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch.sig new file mode 100644 index 0000000..290ce7c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch b/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch new file mode 100644 index 0000000..e59e1f1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch @@ -0,0 +1,125 @@ +diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c +--- a/kernel/sched/deadline.c ++++ b/kernel/sched/deadline.c +@@ -3026,8 +3026,8 @@ static struct task_struct *pick_next_pushable_dl_task(struct rq *rq) + next_node = rb_first_cached(&rq->dl.pushable_dl_tasks_root); + while (next_node) { + i = __node_2_pdl(next_node); +- /* make sure task isn't on_cpu (possible with proxy-exec) */ +- if (!task_on_cpu(rq, i)) { ++ /* skip tasks that cannot be migrated */ ++ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { + p = i; + break; + } +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -10691,17 +10691,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; +@@ -10758,6 +10781,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; + +@@ -10765,7 +10796,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; + } +@@ -10805,8 +10837,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; +@@ -11869,6 +11900,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/kernel/sched/rt.c b/kernel/sched/rt.c +--- a/kernel/sched/rt.c ++++ b/kernel/sched/rt.c +@@ -1871,8 +1871,8 @@ static struct task_struct *pick_next_pushable_task(struct rq *rq) + return NULL; + + plist_for_each_entry(i, head, pushable_tasks) { +- /* make sure task isn't on_cpu (possible with proxy-exec) */ +- if (!task_on_cpu(rq, i)) { ++ /* skip tasks that cannot be migrated */ ++ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { + p = i; + break; + } diff --git a/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch.sig new file mode 100644 index 0000000..89bb69e Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch b/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch new file mode 100644 index 0000000..a0f39ea --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch @@ -0,0 +1,24 @@ +diff --git a/arch/x86/kernel/itmt.c b/arch/x86/kernel/itmt.c +--- a/arch/x86/kernel/itmt.c ++++ b/arch/x86/kernel/itmt.c +@@ -110,18 +110,14 @@ int sched_set_itmt_support(void) + arch_debugfs_dir, + &sysctl_sched_itmt_enabled, + &dfs_sched_itmt_fops); +- if (IS_ERR_OR_NULL(dfs_sched_itmt)) { ++ if (IS_ERR(dfs_sched_itmt)) + dfs_sched_itmt = NULL; +- return -ENOMEM; +- } + + dfs_sched_core_prio = debugfs_create_file("sched_core_priority", 0644, + arch_debugfs_dir, NULL, + &sched_core_priority_fops); +- if (IS_ERR_OR_NULL(dfs_sched_core_prio)) { ++ if (IS_ERR(dfs_sched_core_prio)) + dfs_sched_core_prio = NULL; +- return -ENOMEM; +- } + + sched_itmt_capable = true; + diff --git a/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch.sig b/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch.sig new file mode 100644 index 0000000..d64847c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch b/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch new file mode 100644 index 0000000..be80f96 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch @@ -0,0 +1,124 @@ +diff --git a/include/linux/sched/sd_flags.h b/include/linux/sched/sd_flags.h +--- a/include/linux/sched/sd_flags.h ++++ b/include/linux/sched/sd_flags.h +@@ -146,8 +146,7 @@ SD_FLAG(SD_ASYM_PACKING, SDF_NEEDS_GROUPS) + /* + * Prefer to place tasks in a sibling domain + * +- * Set up until domains start spanning NUMA nodes. Close to being a SHARED_CHILD +- * flag, but cleared below domains with SD_ASYM_CPUCAPACITY. ++ * Set up until domains start spanning NUMA nodes. + * + * NEEDS_GROUPS: Load balancing flag. + */ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -12028,10 +12028,25 @@ static inline void update_sg_lb_stats(struct lb_env *env, + continue; + + if (sd_flags & SD_ASYM_CPUCAPACITY) { +- /* Check for a misfit task on the cpu */ +- if (sgs->group_misfit_task_load < rq->misfit_task_load) { +- sgs->group_misfit_task_load = rq->misfit_task_load; +- *sg_overloaded = 1; ++ 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 */ +@@ -12110,6 +12125,17 @@ static bool update_sd_pick_busiest(struct lb_env *env, + sds->local_stat.group_type != group_has_spare)) + return false; + ++ /* ++ * Candidate sg has no more than one task per CPU and has higher ++ * per-CPU capacity. Migrating tasks to less capable CPUs may harm ++ * throughput. Maximize throughput, power/energy consequences are not ++ * considered. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && ++ (sgs->group_type <= group_fully_busy) && ++ (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu)))) ++ return false; ++ + if (sgs->group_type > busiest->group_type) + return true; + +@@ -12216,17 +12242,6 @@ static bool update_sd_pick_busiest(struct lb_env *env, + break; + } + +- /* +- * Candidate sg has no more than one task per CPU and has higher +- * per-CPU capacity. Migrating tasks to less capable CPUs may harm +- * throughput. Maximize throughput, power/energy consequences are not +- * considered. +- */ +- if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && +- (sgs->group_type <= group_fully_busy) && +- (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu)))) +- return false; +- + return true; + } + +@@ -13144,9 +13159,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-bore/0143-sched-hybrid-cluster-balancing.patch.sig b/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch.sig new file mode 100644 index 0000000..91abc02 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch b/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch new file mode 100644 index 0000000..f3af36b --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch @@ -0,0 +1,77 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -14050,29 +14050,62 @@ static inline int on_null_domain(struct rq *rq) + */ + static inline int find_new_ilb(void) + { +- int this_cpu = smp_processor_id(); +- const struct cpumask *hk_mask; +- int ilb_cpu; ++ struct cpumask *ilb_cpus; ++ int ilb_cpu, fallback = -1; + +- hk_mask = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE); ++ lockdep_assert_irqs_disabled(); + +- for_each_cpu_and(ilb_cpu, nohz.idle_cpus_mask, hk_mask) { +- if (ilb_cpu == this_cpu) ++ /* ++ * Reuse the per-CPU select_rq_mask, which is protected from concurrent ++ * use on this CPU by having interrupts disabled. ++ */ ++ ilb_cpus = this_cpu_cpumask_var_ptr(select_rq_mask); ++ cpumask_and(ilb_cpus, nohz.idle_cpus_mask, ++ housekeeping_cpumask(HK_TYPE_KERNEL_NOISE)); ++ ++ for_each_cpu(ilb_cpu, ilb_cpus) { ++ if (!idle_cpu(ilb_cpu)) { ++ /* ++ * Once an idle fallback exists, a busy CPU proves that ++ * this core cannot be fully idle. Skip its siblings. ++ */ ++ if (sched_smt_active() && fallback >= 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-bore/0144-sched-nohz-idle-core.patch.sig b/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch.sig new file mode 100644 index 0000000..a6bbb0d Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch b/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch new file mode 100644 index 0000000..7d0ef21 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch @@ -0,0 +1,2154 @@ +diff --git a/block/Kconfig.iosched b/block/Kconfig.iosched +index 27f11320b8d1..e98585dd83e0 100644 +--- a/block/Kconfig.iosched ++++ b/block/Kconfig.iosched +@@ -16,6 +16,20 @@ config MQ_IOSCHED_KYBER + synchronous writes, it will self-tune queue depths to achieve that + goal. + ++config MQ_IOSCHED_ADIOS ++ tristate "Adaptive Deadline I/O scheduler" ++ default m ++ help ++ The Adaptive Deadline I/O Scheduler (ADIOS) is a multi-queue I/O ++ scheduler with learning-based adaptive latency control. ++ ++config MQ_IOSCHED_DEFAULT_ADIOS ++ bool "Enable ADIOS I/O scheduler as default MQ I/O scheduler" ++ depends on MQ_IOSCHED_ADIOS=y ++ default n ++ help ++ Enable the ADIOS I/O scheduler as the default scheduler for MQ I/O. ++ + config IOSCHED_BFQ + tristate "BFQ I/O scheduler" + select BLK_ICQ +diff --git a/block/Makefile b/block/Makefile +index e7bd320e3d69..5523e5f62f92 100644 +--- a/block/Makefile ++++ b/block/Makefile +@@ -25,6 +25,7 @@ obj-$(CONFIG_BLK_CGROUP_IOLATENCY) += blk-iolatency.o + obj-$(CONFIG_BLK_CGROUP_IOCOST) += blk-iocost.o + obj-$(CONFIG_MQ_IOSCHED_DEADLINE) += mq-deadline.o + obj-$(CONFIG_MQ_IOSCHED_KYBER) += kyber-iosched.o ++obj-$(CONFIG_MQ_IOSCHED_ADIOS) += adios.o + bfq-y := bfq-iosched.o bfq-wf2q.o bfq-cgroup.o + obj-$(CONFIG_IOSCHED_BFQ) += bfq.o + +@@ -39,3 +40,10 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \ + blk-crypto-sysfs.o + obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o + obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED) += holder.o ++ ++all: ++ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules ++ ++clean: ++ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean ++ +diff --git a/block/adios.c b/block/adios.c +new file mode 100644 +index 000000000000..c7f380bb65dd +--- /dev/null ++++ b/block/adios.c +@@ -0,0 +1,2062 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Adaptive Deadline I/O Scheduler (ADIOS) ++ * Copyright (C) 2025 Masahito Suzuki ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "elevator.h" ++#include "blk.h" ++#include "blk-mq.h" ++#include "blk-mq-sched.h" ++ ++#define ADIOS_VERSION "3.2.0" ++ ++/* Request Types: ++ * ++ * Tier 0 (Highest Priority): Emergency & System Integrity Requests ++ * ----------------------------------------------------------------- ++ * - Target: Requests with the BLK_MQ_INSERT_AT_HEAD flag. ++ * - Purpose: For critical, non-negotiable operations such as device error ++ * recovery or flush sequences that must bypass all other scheduling logic. ++ * - Implementation: Placed in a dedicated, high-priority FIFO queue ++ * (`prio_queue[0]`) for immediate dispatch. ++ * ++ * Tier 1 (High Priority): I/O Barrier Guarantees ++ * --------------------------------------------------------------- ++ * - Target: Requests with the REQ_OP_FLUSH flag. ++ * - Purpose: To enforce a strict I/O barrier. When a flush request is ++ * received, the scheduler stops processing new requests from its main ++ * queues until all preceding requests have been completed. This guarantees ++ * the order of operations required by filesystems for data integrity. ++ * - Implementation: A state flag (ADIOS_STATE_BARRIER) halts ++ * insertion into the main deadline tree. The barrier request and all ++ * subsequent requests are held in a temporary `barrier_queue`. Once the ++ * main queues are drained, the barrier request and the subsequent requests ++ * are released from the pending queue back into the scheduler. ++ * ++ * Tier 2 (Medium Priority): Application Responsiveness ++ * ---------------------------------------------------- ++ * - Target: Normal synchronous requests (e.g., from standard file reads). ++ * - Purpose: To ensure correct application behavior for operations that ++ * depend on sequential I/O completion (e.g., file system mounts) and to ++ * provide low latency for interactive applications. ++ * - Implementation: The deadline for these requests is set to their start ++ * time (`rq->start_time_ns`). This effectively enforces FIFO-like behavior ++ * within the deadline-sorted red-black tree, preventing out-of-order ++ * execution of dependent synchronous operations. ++ * ++ * Tier 3 (Normal Priority): Background Throughput ++ * ----------------------------------------------- ++ * - Target: Asynchronous requests. ++ * - Purpose: To maximize disk throughput for background tasks where latency ++ * is not critical. ++ * - Implementation: These are the only requests where ADIOS's adaptive ++ * latency prediction model is used. A dynamic deadline is calculated based ++ * on the predicted I/O latency, allowing for aggressive reordering to ++ * optimize I/O efficiency. ++ * ++ * Dispatch Logic: ++ * The scheduler always dispatches requests in strict priority order: ++ * 1. prio_queue[0] (Tier 0) ++ * 2. The deadline-sorted batch queue (which naturally prioritizes Tier 2 ++ * over Tier 3 due to their calculated deadlines). ++ * 3. Barrier-pending requests are handled only after the main queues are empty. ++ */ ++ ++// Global variable to control the latency ++static u64 default_global_latency_window = 16000000ULL; ++static u64 default_global_latency_window_rotational = 22000000ULL; ++// Ratio below which batch queues should be refilled ++static u8 default_bq_refill_below_ratio = 20; ++// Maximum latency sample to input ++static u64 default_lat_model_latency_limit = 500 * NSEC_PER_MSEC; ++// Batch ordering strategy ++static u64 default_batch_order = 0; ++ ++/* Compliance Flags: ++ * 0x1: Async requests will not be reordered based on the predicted latency ++ */ ++enum adios_compliance_flags { ++ ADIOS_CF_FIXORDER = 1U << 0, ++}; ++ ++// Flags to control compliance with block layer constraints ++static u64 default_compliance_flags = 0x0; ++ ++// Dynamic thresholds for shrinkage ++static u32 default_lm_shrink_at_kreqs = 5000; ++static u32 default_lm_shrink_at_gbytes = 50; ++static u32 default_lm_shrink_resist = 2; ++ ++enum adios_optype { ++ ADIOS_READ = 0, ++ ADIOS_WRITE = 1, ++ ADIOS_DISCARD = 2, ++ ADIOS_OTHER = 3, ++ ADIOS_OPTYPES = 4, ++}; ++ ++// Latency targets for each operation type ++static u64 default_latency_target[ADIOS_OPTYPES] = { ++ [ADIOS_READ] = 2ULL * NSEC_PER_MSEC, ++ [ADIOS_WRITE] = 2000ULL * NSEC_PER_MSEC, ++ [ADIOS_DISCARD] = 8000ULL * NSEC_PER_MSEC, ++ [ADIOS_OTHER] = 0ULL * NSEC_PER_MSEC, ++}; ++ ++// Maximum batch size limits for each operation type ++static u32 default_batch_limit[ADIOS_OPTYPES] = { ++ [ADIOS_READ] = 36, ++ [ADIOS_WRITE] = 72, ++ [ADIOS_DISCARD] = 1, ++ [ADIOS_OTHER] = 1, ++}; ++ ++enum adios_batch_order { ++ ADIOS_BO_OPTYPE = 0, ++ ADIOS_BO_ELEVATOR = 1, ++}; ++ ++// Thresholds for latency model control ++#define LM_BLOCK_SIZE_THRESHOLD 4096 ++#define LM_SAMPLES_THRESHOLD 1024 ++#define LM_INTERVAL_THRESHOLD 1500 ++#define LM_OUTLIER_PERCENTILE 99 ++#define LM_LAT_BUCKET_COUNT 64 ++ ++#define ADIOS_PQ_LEVELS 2 ++#define ADIOS_DL_TYPES 2 ++#define ADIOS_BQ_PAGES 2 ++ ++static u32 default_dl_prio[ADIOS_DL_TYPES] = {8, 0}; ++ ++// Bit flags for the atomic state variable, indicating which queues have requests. ++enum adios_state_flags { ++ ADIOS_STATE_PQ_0 = 1U << 0, ++ ADIOS_STATE_PQ_1 = 1U << 1, ++ ADIOS_STATE_DL_0 = 1U << 2, ++ ADIOS_STATE_DL_1 = 1U << 3, ++ ADIOS_STATE_BQ_PAGE_0 = 1U << 4, ++ ADIOS_STATE_BQ_PAGE_1 = 1U << 5, ++ ADIOS_STATE_BARRIER = 1U << 6, ++}; ++#define ADIOS_STATE_PQ 0 ++#define ADIOS_STATE_DL 2 ++#define ADIOS_STATE_BQ 4 ++#define ADIOS_STATE_BP 6 ++ ++// Temporal granularity of the deadline tree node (dl_group) ++#define ADIOS_QUANTUM_SHIFT 20 ++ ++#define ADIOS_MAX_INSERTS_PER_LOCK 72 ++#define ADIOS_MAX_DELETES_PER_LOCK 24 ++ ++// Structure to hold latency bucket data for small requests ++struct latency_bucket_small { ++ u64 weighted_sum_latency; ++ u64 sum_of_weights; ++}; ++ ++// Structure to hold latency bucket data for large requests ++struct latency_bucket_large { ++ u64 weighted_sum_latency; ++ u64 weighted_sum_block_size; ++ u64 sum_of_weights; ++}; ++ ++// Structure to hold per-cpu buckets, improving data locality and code clarity. ++struct lm_buckets { ++ struct latency_bucket_small small_bucket[LM_LAT_BUCKET_COUNT]; ++ struct latency_bucket_large large_bucket[LM_LAT_BUCKET_COUNT]; ++}; ++ ++// Structure to hold RCU-protected latency model parameters ++struct latency_model_params { ++ u64 base; ++ u64 slope; ++ u64 small_sum_delay; ++ u64 small_count; ++ u64 large_sum_delay; ++ u64 large_sum_bsize; ++ u64 last_update_jiffies; ++ struct rcu_head rcu; ++}; ++ ++// Structure to hold the latency model context data ++struct latency_model { ++ spinlock_t update_lock; ++ struct latency_model_params __rcu *params; ++ ++ // Per-CPU buckets to avoid lock contention on the completion path ++ struct lm_buckets __percpu *pcpu_buckets; ++ // Per-CPU snapshots for delta-based aggregation (accessed under update_lock) ++ struct lm_buckets __percpu *pcpu_snapshot; ++ ++ u32 lm_shrink_at_kreqs; ++ u32 lm_shrink_at_gbytes; ++ u8 lm_shrink_resist; ++}; ++ ++struct adios_pcpu_completion { ++ u64 last_completed_time; ++ sector_t last_completed_pos; ++}; ++ ++union adios_in_flight_rqs { ++ atomic64_t atomic; ++ u64 scalar; ++ struct { ++ u64 count: 16; ++ u64 total_pred_lat: 48; ++ }; ++}; ++ ++// Adios scheduler data ++struct adios_data { ++ spinlock_t pq_lock; ++ struct list_head prio_queue[2]; ++ ++ struct rb_root_cached dl_tree[2]; ++ spinlock_t lock; ++ s64 dl_bias; ++ s32 dl_prio[2]; ++ ++ atomic_t state; ++ u8 bq_state[ADIOS_BQ_PAGES]; ++ ++ bool models_stable; ++ ++ u64 global_latency_window; ++ u64 compliance_flags; ++ u64 latency_target[ADIOS_OPTYPES]; ++ u32 batch_limit[ADIOS_OPTYPES]; ++ u32 batch_actual_max_size[ADIOS_OPTYPES]; ++ u32 batch_actual_max_total; ++ u32 async_depth; ++ u32 lat_model_latency_limit; ++ u8 bq_refill_below_ratio; ++ u8 is_rotational; ++ u8 batch_order; ++ u8 elv_direction; ++ sector_t head_pos; ++ ++ bool bq_page; ++ struct list_head batch_queue[ADIOS_BQ_PAGES][ADIOS_OPTYPES]; ++ u32 batch_count[ADIOS_BQ_PAGES][ADIOS_OPTYPES]; ++ u8 bq_batch_order[ADIOS_BQ_PAGES]; ++ spinlock_t bq_lock; ++ spinlock_t barrier_lock; ++ struct list_head barrier_queue; ++ ++ struct lm_buckets *aggr_buckets; ++ ++ struct latency_model latency_model[ADIOS_OPTYPES]; ++ struct timer_list update_timer; ++ ++ union adios_in_flight_rqs in_flight_rqs; ++ atomic64_t total_pred_lat; ++ ++ struct adios_pcpu_completion __percpu *pcpu_completion; ++ ++ struct kmem_cache *rq_data_cache; ++ mempool_t *rq_data_pool; ++ struct kmem_cache *dl_group_pool; ++ ++ struct request_queue *queue; ++}; ++ ++// List of requests with the same deadline in the deadline-sorted tree ++struct dl_group { ++ struct rb_node node; ++ struct list_head rqs; ++ u64 deadline; ++} __attribute__((aligned(64))); ++ ++// Structure to hold scheduler-specific data for each request ++struct adios_rq_data { ++ struct list_head *dl_group; ++ struct list_head dl_node; ++ ++ struct request *rq; ++ u64 deadline; ++ u64 pred_lat; ++ u32 block_size; ++ bool managed; ++} __attribute__((aligned(64))); ++ ++static const int adios_prio_to_wmult[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 inline bool compliant(struct adios_data *ad, u32 flag) { ++ return ad->compliance_flags & flag; ++} ++ ++// Count the number of entries in aggregated small buckets ++static u64 lm_count_small_entries(struct latency_bucket_small *buckets) { ++ u64 total_weight = 0; ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) ++ total_weight += buckets[i].sum_of_weights; ++ return total_weight; ++} ++ ++// Update the small buckets in the latency model from aggregated data ++static bool lm_update_small_buckets(struct latency_model *model, ++ struct latency_model_params *params, ++ struct latency_bucket_small *buckets, ++ u64 total_weight, bool count_all) { ++ u64 sum_latency = 0; ++ u64 sum_weight = 0; ++ u64 cumulative_weight = 0, threshold_weight = 0; ++ u8 outlier_threshold_bucket = 0; ++ u8 outlier_percentile = LM_OUTLIER_PERCENTILE; ++ u8 reduction; ++ ++ if (count_all) ++ outlier_percentile = 100; ++ ++ // Calculate the threshold weight for outlier detection ++ threshold_weight = (total_weight * outlier_percentile) / 100; ++ ++ // Identify the bucket that corresponds to the outlier threshold ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) { ++ cumulative_weight += buckets[i].sum_of_weights; ++ if (cumulative_weight >= threshold_weight) { ++ outlier_threshold_bucket = i; ++ break; ++ } ++ } ++ ++ // Calculate the average latency, excluding outliers ++ for (u8 i = 0; i <= outlier_threshold_bucket; i++) { ++ struct latency_bucket_small *bucket = &buckets[i]; ++ if (i < outlier_threshold_bucket) { ++ sum_latency += bucket->weighted_sum_latency; ++ sum_weight += bucket->sum_of_weights; ++ } else { ++ // The threshold bucket's contribution is proportional ++ u64 remaining_weight = ++ threshold_weight - (cumulative_weight - bucket->sum_of_weights); ++ if (bucket->sum_of_weights > 0) { ++ sum_latency += div_u64(bucket->weighted_sum_latency * ++ remaining_weight, bucket->sum_of_weights); ++ sum_weight += remaining_weight; ++ } ++ } ++ } ++ ++ // Shrink the model if it reaches at the readjustment threshold ++ if (params->small_count >= 1000ULL * model->lm_shrink_at_kreqs) { ++ reduction = model->lm_shrink_resist; ++ if (params->small_count >> reduction) { ++ params->small_sum_delay -= params->small_sum_delay >> reduction; ++ params->small_count -= params->small_count >> reduction; ++ } ++ } ++ ++ if (!sum_weight) ++ return false; ++ ++ // Accumulate the average latency into the statistics ++ params->small_sum_delay += sum_latency; ++ params->small_count += sum_weight; ++ ++ return true; ++} ++ ++// Count the number of entries in aggregated large buckets ++static u64 lm_count_large_entries(struct latency_bucket_large *buckets) { ++ u64 total_weight = 0; ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) ++ total_weight += buckets[i].sum_of_weights; ++ return total_weight; ++} ++ ++// Update the large buckets in the latency model from aggregated data ++static bool lm_update_large_buckets(struct latency_model *model, ++ struct latency_model_params *params, ++ struct latency_bucket_large *buckets, ++ u64 total_weight, bool count_all) { ++ s64 sum_latency = 0; ++ u64 sum_block_size = 0, intercept; ++ u64 cumulative_weight = 0, threshold_weight = 0; ++ u64 sum_weight = 0; ++ u8 outlier_threshold_bucket = 0; ++ u8 outlier_percentile = LM_OUTLIER_PERCENTILE; ++ u8 reduction; ++ ++ if (count_all) ++ outlier_percentile = 100; ++ ++ // Calculate the threshold weight for outlier detection ++ threshold_weight = (total_weight * outlier_percentile) / 100; ++ ++ // Identify the bucket that corresponds to the outlier threshold ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) { ++ cumulative_weight += buckets[i].sum_of_weights; ++ if (cumulative_weight >= threshold_weight) { ++ outlier_threshold_bucket = i; ++ break; ++ } ++ } ++ ++ // Calculate the average latency and block size, excluding outliers ++ for (u8 i = 0; i <= outlier_threshold_bucket; i++) { ++ struct latency_bucket_large *bucket = &buckets[i]; ++ if (i < outlier_threshold_bucket) { ++ sum_latency += bucket->weighted_sum_latency; ++ sum_block_size += bucket->weighted_sum_block_size; ++ sum_weight += bucket->sum_of_weights; ++ } else { ++ // The threshold bucket's contribution is proportional ++ u64 remaining_weight = ++ threshold_weight - (cumulative_weight - bucket->sum_of_weights); ++ if (bucket->sum_of_weights > 0) { ++ sum_latency += div_u64(bucket->weighted_sum_latency * ++ remaining_weight, bucket->sum_of_weights); ++ sum_block_size += div_u64(bucket->weighted_sum_block_size * ++ remaining_weight, bucket->sum_of_weights); ++ sum_weight += remaining_weight; ++ } ++ } ++ } ++ ++ if (!sum_weight) ++ return false; ++ ++ // Shrink the model if it reaches at the readjustment threshold ++ if (params->large_sum_bsize >= 0x40000000ULL * model->lm_shrink_at_gbytes) { ++ reduction = model->lm_shrink_resist; ++ if (params->large_sum_bsize >> reduction) { ++ params->large_sum_delay -= params->large_sum_delay >> reduction; ++ params->large_sum_bsize -= params->large_sum_bsize >> reduction; ++ } ++ } ++ ++ // Accumulate the average delay into the statistics ++ intercept = params->base; ++ if (sum_latency > intercept) ++ sum_latency -= intercept; ++ ++ params->large_sum_delay += sum_latency; ++ params->large_sum_bsize += sum_block_size; ++ ++ return true; ++} ++ ++static void reset_buckets(struct lm_buckets *buckets) ++{ memset(buckets, 0, sizeof(*buckets)); } ++ ++static void lm_reset_pcpu_buckets(struct latency_model *model) { ++ int cpu; ++ for_each_possible_cpu(cpu) { ++ reset_buckets(per_cpu_ptr(model->pcpu_buckets, cpu)); ++ reset_buckets(per_cpu_ptr(model->pcpu_snapshot, cpu)); ++ } ++} ++ ++// Update the latency model parameters and statistics ++static void latency_model_update( ++ struct adios_data *ad, struct latency_model *model) { ++ u64 now; ++ u64 small_weight, large_weight; ++ bool time_elapsed; ++ bool small_processed = false, large_processed = false; ++ struct lm_buckets *aggr = ad->aggr_buckets; ++ struct latency_bucket_small *asb; ++ struct latency_bucket_large *alb; ++ struct lm_buckets *pcpu_b, *snap; ++ unsigned long flags; ++ int cpu; ++ struct latency_model_params *old_params, *new_params; ++ ++ spin_lock_irqsave(&model->update_lock, flags); ++ ++ old_params = rcu_dereference_protected(model->params, ++ lockdep_is_held(&model->update_lock)); ++ new_params = kmemdup(old_params, sizeof(*new_params), GFP_ATOMIC); ++ if (!new_params) { ++ spin_unlock_irqrestore(&model->update_lock, flags); ++ return; ++ } ++ ++ // Aggregate deltas from all CPUs using snapshot-delta method. ++ // Per-CPU counters increase monotonically; we compute delta = current - snapshot. ++ for_each_possible_cpu(cpu) { ++ pcpu_b = per_cpu_ptr(model->pcpu_buckets, cpu); ++ snap = per_cpu_ptr(model->pcpu_snapshot, cpu); ++ ++ for (u8 i = 0; i < LM_LAT_BUCKET_COUNT; i++) { ++ u64 sw = pcpu_b->small_bucket[i].sum_of_weights; ++ u64 sl = pcpu_b->small_bucket[i].weighted_sum_latency; ++ u64 dsw = sw - snap->small_bucket[i].sum_of_weights; ++ u64 dsl = sl - snap->small_bucket[i].weighted_sum_latency; ++ snap->small_bucket[i].sum_of_weights = sw; ++ snap->small_bucket[i].weighted_sum_latency = sl; ++ if (dsw) { ++ asb = &aggr->small_bucket[i]; ++ asb->sum_of_weights += dsw; ++ asb->weighted_sum_latency += dsl; ++ } ++ ++ u64 lw = pcpu_b->large_bucket[i].sum_of_weights; ++ u64 ll = pcpu_b->large_bucket[i].weighted_sum_latency; ++ u64 lb = pcpu_b->large_bucket[i].weighted_sum_block_size; ++ u64 dlw = lw - snap->large_bucket[i].sum_of_weights; ++ u64 dll = ll - snap->large_bucket[i].weighted_sum_latency; ++ u64 dlb = lb - snap->large_bucket[i].weighted_sum_block_size; ++ snap->large_bucket[i].sum_of_weights = lw; ++ snap->large_bucket[i].weighted_sum_latency = ll; ++ snap->large_bucket[i].weighted_sum_block_size = lb; ++ if (dlw) { ++ alb = &aggr->large_bucket[i]; ++ alb->sum_of_weights += dlw; ++ alb->weighted_sum_latency += dll; ++ alb->weighted_sum_block_size += dlb; ++ } ++ } ++ } ++ ++ // Count the number of entries in aggregated buckets ++ small_weight = lm_count_small_entries(aggr->small_bucket); ++ large_weight = lm_count_large_entries(aggr->large_bucket); ++ ++ // Whether enough time has elapsed since the last update ++ now = jiffies; ++ time_elapsed = unlikely(!new_params->base) || ++ new_params->last_update_jiffies + ++ msecs_to_jiffies(LM_INTERVAL_THRESHOLD) <= now; ++ ++ // Update small buckets ++ if (small_weight && (time_elapsed || ++ LM_SAMPLES_THRESHOLD <= small_weight || !new_params->base)) { ++ small_processed = lm_update_small_buckets(model, new_params, ++ aggr->small_bucket, small_weight, !new_params->base); ++ memset(&aggr->small_bucket[0], 0, sizeof(aggr->small_bucket)); ++ } ++ // Update large buckets ++ if (large_weight && (time_elapsed || ++ LM_SAMPLES_THRESHOLD <= large_weight || !new_params->slope)) { ++ large_processed = lm_update_large_buckets(model, new_params, ++ aggr->large_bucket, large_weight, !new_params->slope); ++ memset(&aggr->large_bucket[0], 0, sizeof(aggr->large_bucket)); ++ } ++ ++ // Update the base parameter if small bucket was processed ++ if (small_processed && likely(new_params->small_count)) ++ new_params->base = div_u64(new_params->small_sum_delay, ++ new_params->small_count); ++ ++ // Update the slope parameter if large bucket was processed ++ if (large_processed && likely(new_params->large_sum_bsize)) ++ new_params->slope = div_u64(new_params->large_sum_delay, ++ DIV_ROUND_UP_ULL(new_params->large_sum_bsize, 1024)); ++ ++ // Update last updated jiffies if update happened or time has elapsed ++ if (small_processed || large_processed || time_elapsed) ++ new_params->last_update_jiffies = now; ++ ++ rcu_assign_pointer(model->params, new_params); ++ spin_unlock_irqrestore(&model->update_lock, flags); ++ ++ kfree_rcu(old_params, rcu); ++} ++ ++// Determine the bucket index for a given measured and predicted latency ++static u8 lm_input_bucket_index(u64 measured, u64 predicted) { ++ u32 bucket_index; ++ ++ if (measured < predicted * 2) ++ bucket_index = div_u64((measured * 20), predicted); ++ else if (measured < predicted * 5) ++ bucket_index = div_u64((measured * 10), predicted) + 20; ++ else ++ bucket_index = div_u64((measured * 3), predicted) + 40; ++ ++ if (bucket_index >= LM_LAT_BUCKET_COUNT) ++ bucket_index = LM_LAT_BUCKET_COUNT - 1; ++ ++ return (u8)bucket_index; ++} ++ ++// Input latency data into the latency model ++static void latency_model_input(struct adios_data *ad, ++ struct latency_model *model, ++ u32 block_size, u64 latency, u64 pred_lat, u32 weight) { ++ unsigned long flags; ++ u8 bucket_index; ++ struct lm_buckets *buckets; ++ u64 current_base; ++ struct latency_model_params *params; ++ ++ local_irq_save(flags); ++ buckets = per_cpu_ptr(model->pcpu_buckets, __smp_processor_id()); ++ ++ rcu_read_lock(); ++ params = rcu_dereference(model->params); ++ current_base = params->base; ++ rcu_read_unlock(); ++ ++ if (block_size <= LM_BLOCK_SIZE_THRESHOLD) { ++ // Handle small requests ++ bucket_index = lm_input_bucket_index(latency, current_base ?: 1); ++ ++ buckets->small_bucket[bucket_index].sum_of_weights += weight; ++ buckets->small_bucket[bucket_index].weighted_sum_latency += ++ latency * weight; ++ ++ local_irq_restore(flags); ++ ++ if (unlikely(!current_base)) { ++ latency_model_update(ad, model); ++ return; ++ } ++ } else { ++ // Handle large requests ++ if (!current_base || !pred_lat) { ++ local_irq_restore(flags); ++ return; ++ } ++ ++ bucket_index = lm_input_bucket_index(latency, pred_lat); ++ ++ buckets->large_bucket[bucket_index].sum_of_weights += weight; ++ buckets->large_bucket[bucket_index].weighted_sum_latency += ++ latency * weight; ++ buckets->large_bucket[bucket_index].weighted_sum_block_size += ++ block_size * weight; ++ ++ local_irq_restore(flags); ++ } ++} ++ ++// Predict the latency for a given block size using the latency model ++static u64 latency_model_predict(struct latency_model *model, u32 block_size) { ++ u64 result; ++ struct latency_model_params *params; ++ ++ rcu_read_lock(); ++ params = rcu_dereference(model->params); ++ ++ result = params->base; ++ if (block_size > LM_BLOCK_SIZE_THRESHOLD) ++ result += params->slope * ++ DIV_ROUND_UP_ULL(block_size - LM_BLOCK_SIZE_THRESHOLD, 1024); ++ ++ rcu_read_unlock(); ++ ++ return result; ++} ++ ++// Determine the type of operation based on request flags ++static u8 adios_optype(struct request *rq) { ++ switch (rq->cmd_flags & REQ_OP_MASK) { ++ case REQ_OP_READ: ++ return ADIOS_READ; ++ case REQ_OP_WRITE: ++ return ADIOS_WRITE; ++ case REQ_OP_DISCARD: ++ return ADIOS_DISCARD; ++ default: ++ return ADIOS_OTHER; ++ } ++} ++ ++static inline u8 adios_optype_not_read(struct request *rq) { ++ return (rq->cmd_flags & REQ_OP_MASK) != REQ_OP_READ; ++} ++ ++// Helper function to retrieve adios_rq_data from a request ++static inline struct adios_rq_data *get_rq_data(struct request *rq) { ++ return rq->elv.priv[0]; ++} ++ ++static inline ++void set_adios_state(struct adios_data *ad, u32 shift, u32 idx, bool flag) { ++ if (flag) ++ atomic_or(1U << (idx + shift), &ad->state); ++ else ++ atomic_andnot(1U << (idx + shift), &ad->state); ++} ++ ++static inline u32 get_adios_state(struct adios_data *ad) ++{ return atomic_read(&ad->state); } ++ ++static inline u32 eval_this_adios_state(u32 state, u32 shift) ++{ return (state >> shift) & 0x3; } ++ ++static inline u32 eval_adios_state(struct adios_data *ad, u32 shift) ++{ return eval_this_adios_state(get_adios_state(ad), shift); } ++ ++// Add a request to the deadline-sorted red-black tree ++static void add_to_dl_tree( ++ struct adios_data *ad, bool dl_idx, struct request *rq) { ++ struct rb_root_cached *root = &ad->dl_tree[dl_idx]; ++ struct rb_node **link = &(root->rb_root.rb_node), *parent = NULL; ++ bool leftmost = true; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ struct dl_group *dlg; ++ u64 deadline; ++ bool was_empty = RB_EMPTY_ROOT(&root->rb_root); ++ ++ /* Tier-2: Synchronous Requests ++ * - Needs to be FIFO within a same optype ++ * - Relaxed order between different optypes ++ * - basically needs to be processed in early time */ ++ rd->deadline = rq->start_time_ns; ++ ++ /* Tier-3: Aynchronous Requests ++ * - Can be reordered and delayed freely */ ++ if (!(rq->cmd_flags & REQ_SYNC)) { ++ rd->deadline += ad->latency_target[adios_optype(rq)]; ++ if (!compliant(ad, ADIOS_CF_FIXORDER)) ++ rd->deadline += rd->pred_lat; ++ } ++ ++ // Now quantize the deadline (-> dlg->deadline == RB-Tree key) ++ deadline = rd->deadline & ~((1ULL << ADIOS_QUANTUM_SHIFT) - 1); ++ ++ while (*link) { ++ dlg = rb_entry(*link, struct dl_group, node); ++ s64 diff = deadline - dlg->deadline; ++ ++ parent = *link; ++ if (diff < 0) { ++ link = &((*link)->rb_left); ++ } else if (diff > 0) { ++ link = &((*link)->rb_right); ++ leftmost = false; ++ } else { // diff == 0 ++ goto found; ++ } ++ } ++ ++ dlg = rb_entry_safe(parent, struct dl_group, node); ++ if (!dlg || dlg->deadline != deadline) { ++ dlg = kmem_cache_zalloc(ad->dl_group_pool, GFP_ATOMIC); ++ if (!dlg) ++ return; ++ dlg->deadline = deadline; ++ INIT_LIST_HEAD(&dlg->rqs); ++ rb_link_node(&dlg->node, parent, link); ++ rb_insert_color_cached(&dlg->node, root, leftmost); ++ } ++found: ++ list_add_tail(&rd->dl_node, &dlg->rqs); ++ rd->dl_group = &dlg->rqs; ++ ++ if (was_empty) ++ set_adios_state(ad, ADIOS_STATE_DL, dl_idx, true); ++} ++ ++// Remove a request from the deadline-sorted red-black tree ++static void del_from_dl_tree( ++ struct adios_data *ad, bool dl_idx, struct request *rq) { ++ struct rb_root_cached *root = &ad->dl_tree[dl_idx]; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ struct dl_group *dlg = container_of(rd->dl_group, struct dl_group, rqs); ++ ++ list_del_init(&rd->dl_node); ++ if (list_empty(&dlg->rqs)) { ++ rb_erase_cached(&dlg->node, root); ++ kmem_cache_free(ad->dl_group_pool, dlg); ++ } ++ rd->dl_group = NULL; ++ ++ if (RB_EMPTY_ROOT(&ad->dl_tree[dl_idx].rb_root)) ++ set_adios_state(ad, ADIOS_STATE_DL, dl_idx, false); ++} ++ ++// Remove a request from the scheduler ++static void remove_request(struct adios_data *ad, struct request *rq) { ++ bool dl_idx = adios_optype_not_read(rq); ++ struct request_queue *q = rq->q; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ ++ list_del_init(&rq->queuelist); ++ ++ // We might not be on the rbtree, if we are doing an insert merge ++ if (rd->dl_group) ++ del_from_dl_tree(ad, dl_idx, rq); ++ ++ elv_rqhash_del(q, rq); ++ if (q->last_merge == rq) ++ q->last_merge = NULL; ++} ++ ++// Convert a queue depth to the corresponding word depth for shallow allocation ++static int to_word_depth(struct blk_mq_hw_ctx *hctx, unsigned int qdepth) { ++ struct sbitmap_queue *bt = &hctx->sched_tags->bitmap_tags; ++ const unsigned int nrr = hctx->queue->nr_requests; ++ ++ return ((qdepth << bt->sb.shift) + nrr - 1) / nrr; ++} ++ ++// We limit the depth of request allocation for asynchronous and write requests ++static void adios_limit_depth(blk_opf_t opf, struct blk_mq_alloc_data *data) { ++ struct adios_data *ad = data->q->elevator->elevator_data; ++ ++ // Do not throttle synchronous reads ++ if (op_is_sync(opf) && !op_is_write(opf)) ++ return; ++ ++ data->shallow_depth = to_word_depth(data->hctx, ad->async_depth); ++} ++ ++// The number of requests in the queue was notified from the block layer ++static void adios_depth_updated(struct request_queue *q) { ++ struct adios_data *ad = q->elevator->elevator_data; ++ ++ ad->async_depth = q->nr_requests; ++ blk_mq_set_min_shallow_depth(q, 1); ++} ++ ++// Handle request merging after a merge operation ++static void adios_request_merged(struct request_queue *q, struct request *req, ++ enum elv_merge type) { ++ bool dl_idx = adios_optype_not_read(req); ++ struct adios_data *ad = q->elevator->elevator_data; ++ ++ // Reposition request in the deadline-sorted tree ++ del_from_dl_tree(ad, dl_idx, req); ++ add_to_dl_tree(ad, dl_idx, req); ++} ++ ++// Handle merging of requests after one has been merged into another ++static void adios_merged_requests(struct request_queue *q, struct request *req, ++ struct request *next) { ++ struct adios_data *ad = q->elevator->elevator_data; ++ ++ lockdep_assert_held(&ad->lock); ++ ++ // kill knowledge of next, this one is a goner ++ remove_request(ad, next); ++} ++ ++// Try to merge a bio into an existing rq before associating it with an rq ++static bool adios_bio_merge(struct request_queue *q, struct bio *bio, ++ unsigned int nr_segs) { ++ unsigned long flags; ++ struct adios_data *ad = q->elevator->elevator_data; ++ struct request *free = NULL; ++ bool ret; ++ ++ if (eval_adios_state(ad, ADIOS_STATE_BP)) ++ return false; ++ ++ if (!spin_trylock_irqsave(&ad->lock, flags)) ++ return false; ++ ++ ret = blk_mq_sched_try_merge(q, bio, nr_segs, &free); ++ spin_unlock_irqrestore(&ad->lock, flags); ++ ++ if (free) ++ blk_mq_free_request(free); ++ ++ return ret; ++} ++ ++static bool merge_or_insert_to_dl_tree(struct adios_data *ad, ++ struct request *rq, struct request_queue *q, struct list_head *free) { ++ if (blk_mq_sched_try_insert_merge(q, rq, free)) ++ return true; ++ ++ bool dl_idx = adios_optype_not_read(rq); ++ add_to_dl_tree(ad, dl_idx, rq); ++ ++ if (rq_mergeable(rq)) { ++ elv_rqhash_add(q, rq); ++ if (!q->last_merge) ++ q->last_merge = rq; ++ } ++ ++ return false; ++} ++ ++static void insert_to_prio_queue(struct adios_data *ad, ++ struct request *rq, bool pq_idx) { ++ struct adios_rq_data *rd = get_rq_data(rq); ++ ++ /* We're sure that rd->managed == true */ ++ union adios_in_flight_rqs ifr = { ++ .count = 1, ++ .total_pred_lat = rd->pred_lat, ++ }; ++ atomic64_add(ifr.scalar, &ad->in_flight_rqs.atomic); ++ ++ scoped_guard(spinlock_irqsave, &ad->pq_lock) { ++ bool was_empty = list_empty(&ad->prio_queue[pq_idx]); ++ list_add_tail(&rq->queuelist, &ad->prio_queue[pq_idx]); ++ if (was_empty) ++ set_adios_state(ad, ADIOS_STATE_PQ, pq_idx, true); ++ } ++} ++ ++// Insert a request into the scheduler (after Read & Write models stabilized) ++static void insert_request_post_stability(struct blk_mq_hw_ctx *hctx, ++ struct request *rq, blk_insert_t insert_flags, struct list_head *free) { ++ struct request_queue *q = hctx->queue; ++ struct adios_data *ad = q->elevator->elevator_data; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ u8 optype = adios_optype(rq); ++ bool rq_is_flush; ++ ++ rd->managed = true; ++ rd->block_size = blk_rq_bytes(rq); ++ rd->pred_lat = ++ latency_model_predict(&ad->latency_model[optype], rd->block_size); ++ if (unlikely(rd->pred_lat > ad->lat_model_latency_limit)) ++ rd->pred_lat = ad->lat_model_latency_limit; ++ ++ /* Tier-0: BLK_MQ_INSERT_AT_HEAD Requests */ ++ if (insert_flags & BLK_MQ_INSERT_AT_HEAD) { ++ insert_to_prio_queue(ad, rq, 0); ++ return; ++ } ++ ++ /* ++ * Strict Barrier Handling for REQ_OP_FLUSH: ++ * If a flush request arrives, or if the scheduler is already in a ++ * barrier-pending state, all subsequent requests are diverted to a ++ * separate barrier_queue. This ensures that no new requests are processed ++ * until all work preceding the barrier is complete. ++ */ ++ rq_is_flush = (rq->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH; ++ if (eval_adios_state(ad, ADIOS_STATE_BP) || rq_is_flush) { ++ scoped_guard(spinlock_irqsave, &ad->barrier_lock) { ++ if (rq_is_flush) ++ set_adios_state(ad, ADIOS_STATE_BP, 0, true); ++ list_add_tail(&rq->queuelist, &ad->barrier_queue); ++ } ++ return; ++ } ++ ++ if (merge_or_insert_to_dl_tree(ad, rq, q, free)) ++ return; ++} ++ ++// Insert a request into the scheduler (before Read & Write models stabilizes) ++static void insert_request_pre_stability(struct blk_mq_hw_ctx *hctx, ++ struct request *rq, blk_insert_t insert_flags, struct list_head *free) { ++ struct adios_data *ad = hctx->queue->elevator->elevator_data; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ u8 optype = adios_optype(rq); ++ u8 pq_idx = !(insert_flags & BLK_MQ_INSERT_AT_HEAD); ++ bool stable = false; ++ ++ rd->managed = true; ++ rd->block_size = blk_rq_bytes(rq); ++ rd->pred_lat = ++ latency_model_predict(&ad->latency_model[optype], rd->block_size); ++ if (unlikely(rd->pred_lat > ad->lat_model_latency_limit)) ++ rd->pred_lat = ad->lat_model_latency_limit; ++ ++ insert_to_prio_queue(ad, rq, pq_idx); ++ ++ rcu_read_lock(); ++ if (rcu_dereference(ad->latency_model[ADIOS_READ].params)->base > 0 && ++ rcu_dereference(ad->latency_model[ADIOS_WRITE].params)->base > 0) ++ stable = true; ++ rcu_read_unlock(); ++ ++ if (stable) ++ ad->models_stable = true; ++} ++ ++// Insert multiple requests into the scheduler ++static void adios_insert_requests(struct blk_mq_hw_ctx *hctx, ++ struct list_head *list, ++ blk_insert_t insert_flags) { ++ struct request_queue *q = hctx->queue; ++ struct adios_data *ad = q->elevator->elevator_data; ++ struct request *rq; ++ bool stop = false; ++ LIST_HEAD(free); ++ ++ do { ++ scoped_guard(spinlock_irqsave, &ad->lock) ++ for (int i = 0; i < ADIOS_MAX_INSERTS_PER_LOCK; i++) { ++ if (list_empty(list)) { ++ stop = true; ++ break; ++ } ++ rq = list_first_entry(list, struct request, queuelist); ++ list_del_init(&rq->queuelist); ++ if (likely(ad->models_stable)) ++ insert_request_post_stability(hctx, rq, insert_flags, &free); ++ else ++ insert_request_pre_stability(hctx, rq, insert_flags, &free); ++ }} while (!stop); ++ ++ blk_mq_free_requests(&free); ++} ++ ++// Prepare a request before it is inserted into the scheduler ++static void adios_prepare_request(struct request *rq) { ++ struct adios_data *ad = rq->q->elevator->elevator_data; ++ struct adios_rq_data *rd; ++ ++ rd = mempool_alloc(ad->rq_data_pool, GFP_ATOMIC); ++ memset(rd, 0, sizeof(*rd)); ++ rd->rq = rq; ++ rq->elv.priv[0] = rd; ++} ++ ++static struct adios_rq_data *get_dl_first_rd(struct adios_data *ad, bool idx) { ++ struct rb_root_cached *root = &ad->dl_tree[idx]; ++ struct rb_node *first = rb_first_cached(root); ++ struct dl_group *dl_group = rb_entry(first, struct dl_group, node); ++ ++ return list_first_entry(&dl_group->rqs, struct adios_rq_data, dl_node); ++} ++ ++// Comparison function for sorting requests by block address ++static int cmp_rq_pos(void *priv, ++ const struct list_head *a, const struct list_head *b) { ++ struct request *rq_a = list_entry(a, struct request, queuelist); ++ struct request *rq_b = list_entry(b, struct request, queuelist); ++ u64 pos_a = blk_rq_pos(rq_a); ++ u64 pos_b = blk_rq_pos(rq_b); ++ ++ return (int)(pos_a > pos_b) - (int)(pos_a < pos_b); ++} ++ ++#ifndef list_last_entry_or_null ++#define list_last_entry_or_null(ptr, type, member) \ ++ (!list_empty(ptr) ? list_last_entry(ptr, type, member) : NULL) ++#endif ++ ++// Update the elevator direction ++static void update_elv_direction(struct adios_data *ad) { ++ if (!ad->is_rotational) ++ return; ++ ++ bool page = ad->bq_page; ++ struct list_head *q = &ad->batch_queue[page][1]; ++ if (ad->bq_batch_order[page] < ADIOS_BO_ELEVATOR || list_empty(q)) { ++ ad->elv_direction = 0; ++ return; ++ } ++ ++ // Get first and last request positions in the queue ++ struct request *rq_a = list_first_entry(q, struct request, queuelist); ++ struct request *rq_b = list_last_entry (q, struct request, queuelist); ++ u64 pos_a = blk_rq_pos(rq_a); ++ u64 pos_b = blk_rq_pos(rq_b); ++ u64 avg_rq_pos = (pos_a + pos_b) >> 1; ++ ++ ad->elv_direction = !!(ad->head_pos > avg_rq_pos); ++} ++ ++// Fill the batch queues with requests from the deadline-sorted red-black tree ++static bool fill_batch_queues(struct adios_data *ad, u64 tpl) { ++ struct adios_rq_data *rd; ++ struct request *rq; ++ struct list_head *dest_q; ++ u8 dest_idx; ++ u64 added_lat = 0; ++ u32 optype_count[ADIOS_OPTYPES] = {0}; ++ u32 count = 0; ++ u8 optype; ++ bool page = !ad->bq_page, dl_idx, bias_idx, update_bias; ++ u32 dl_queued; ++ u8 bq_batch_order; ++ bool stop = false; ++ ++ // Reset batch queue counts for the back page ++ memset(&ad->batch_count[page], 0, sizeof(ad->batch_count[page])); ++ ++ ad->bq_batch_order[page] = ++ bq_batch_order = ad->batch_order; ++ ++ do { ++ scoped_guard(spinlock_irqsave, &ad->lock) ++ for (int i = 0; i < ADIOS_MAX_DELETES_PER_LOCK; i++) { ++ bool has_base = false; ++ ++ dl_queued = eval_adios_state(ad, ADIOS_STATE_DL); ++ // Check if there are any requests queued in the deadline tree ++ if (!dl_queued) { ++ stop = true; ++ break; ++ } ++ ++ // Reads if both queues have requests, otherwise pick the non-empty. ++ dl_idx = dl_queued >> 1; ++ ++ // Get the first request from the deadline-sorted tree ++ rd = get_dl_first_rd(ad, dl_idx); ++ ++ bias_idx = ad->dl_bias < 0; ++ // If read and write requests are queued, choose one based on bias ++ if (dl_queued == 0x3) { ++ struct adios_rq_data *trd[2] = {get_dl_first_rd(ad, 0), rd}; ++ rd = trd[bias_idx]; ++ ++ update_bias = (trd[bias_idx]->deadline > trd[!bias_idx]->deadline); ++ } else ++ update_bias = (bias_idx == dl_idx); ++ ++ rq = rd->rq; ++ optype = adios_optype(rq); ++ ++ rcu_read_lock(); ++ has_base = ++ !!rcu_dereference(ad->latency_model[optype].params)->base; ++ rcu_read_unlock(); ++ ++ // Check batch size and total predicted latency ++ if (count && (!has_base || ++ ad->batch_count[page][optype] >= ad->batch_limit[optype] || ++ (tpl + added_lat + rd->pred_lat) > ad->global_latency_window)) { ++ stop = true; ++ break; ++ } ++ ++ if (update_bias) { ++ s64 sign = ((s64)bias_idx << 1) - 1; ++ if (unlikely(!rd->pred_lat)) ++ ad->dl_bias = sign; ++ else ++ // Adjust the bias based on the predicted latency ++ ad->dl_bias += sign * (s64)((rd->pred_lat * ++ adios_prio_to_wmult[ad->dl_prio[bias_idx] + 20]) >> 10); ++ } ++ ++ remove_request(ad, rq); ++ ++ // Add request to the corresponding batch queue ++ dest_idx = (bq_batch_order == ADIOS_BO_OPTYPE || optype == ADIOS_OTHER)? ++ optype : !!(rd->deadline != rq->start_time_ns); ++ dest_q = &ad->batch_queue[page][dest_idx]; ++ list_add_tail(&rq->queuelist, dest_q); ++ ad->bq_state[page] |= 1U << dest_idx; ++ ad->batch_count[page][optype]++; ++ optype_count[optype]++; ++ added_lat += rd->pred_lat; ++ count++; ++ }} while (!stop); ++ ++ if (bq_batch_order == ADIOS_BO_ELEVATOR && ad->batch_count[page][1] > 1) ++ list_sort(NULL, &ad->batch_queue[page][1], cmp_rq_pos); ++ ++ if (count) { ++ /* We're sure that every request's rd->managed == true */ ++ union adios_in_flight_rqs ifr = { ++ .count = count, ++ .total_pred_lat = added_lat, ++ }; ++ atomic64_add(ifr.scalar, &ad->in_flight_rqs.atomic); ++ ++ set_adios_state(ad, ADIOS_STATE_BQ, page, true); ++ ++ for (optype = 0; optype < ADIOS_OPTYPES; optype++) ++ if (ad->batch_actual_max_size[optype] < optype_count[optype]) ++ ad->batch_actual_max_size[optype] = optype_count[optype]; ++ if (ad->batch_actual_max_total < count) ++ ad->batch_actual_max_total = count; ++ } ++ return count; ++} ++ ++// Flip to the next batch queue page ++static void flip_bq_page(struct adios_data *ad) { ++ ad->bq_page = !ad->bq_page; ++ update_elv_direction(ad); ++} ++ ++// Pop a request from the specified index (optype or elevator tier) ++static inline struct request *pop_bq_request( ++ struct adios_data *ad, u8 idx, bool direction) { ++ bool page = ad->bq_page; ++ struct list_head *q = &ad->batch_queue[page][idx]; ++ struct request *rq = direction ? ++ list_last_entry_or_null (q, struct request, queuelist): ++ list_first_entry_or_null(q, struct request, queuelist); ++ if (rq) { ++ list_del_init(&rq->queuelist); ++ if (list_empty(q)) ++ ad->bq_state[page] &= ~(1U << idx); ++ } ++ return rq; ++} ++ ++static struct request *pop_next_bq_request_optype(struct adios_data *ad) { ++ u32 bq_state = ad->bq_state[ad->bq_page]; ++ if (!bq_state) return NULL; ++ ++ struct request *rq; ++ u32 bq_idx = __builtin_ctz(bq_state); ++ ++ // Dispatch based on optype (FIFO within each) or single-queue elevator ++ rq = pop_bq_request(ad, bq_idx, false); ++ return rq; ++} ++ ++static struct request *pop_next_bq_request_elevator(struct adios_data *ad) { ++ u32 bq_state = ad->bq_state[ad->bq_page]; ++ if (!bq_state) return NULL; ++ ++ struct request *rq; ++ u32 bq_idx = __builtin_ctz(bq_state); ++ bool direction = (bq_idx == 1) & ad->elv_direction; ++ ++ // Tier-2 (sync) is always high priority ++ // Tier-3 (async) uses the pre-calculated elevator direction ++ rq = pop_bq_request(ad, bq_idx, direction); ++ ++ /* If batch queue for the sync requests just became empty */ ++ if (bq_idx == 0 && rq && !(bq_state & 0x1)) ++ update_elv_direction(ad); ++ ++ return rq; ++} ++ ++// Returns the state of the batch queue page ++static inline bool bq_page_has_rq(u32 bq_state, bool page) { ++ return bq_state & (1U << page); ++} ++ ++// Dispatch a request from the batch queues ++static struct request *dispatch_from_bq(struct adios_data *ad) { ++ struct request *rq; ++ ++ guard(spinlock_irqsave)(&ad->bq_lock); ++ ++ u32 state = get_adios_state(ad); ++ u32 bq_state = eval_this_adios_state(state, ADIOS_STATE_BQ); ++ u32 bq_curr_page_has_rq = bq_page_has_rq(bq_state, ad->bq_page); ++ union adios_in_flight_rqs ifr; ++ ifr.scalar = atomic64_read(&ad->in_flight_rqs.atomic); ++ u64 tpl = ifr.total_pred_lat; ++ ++ // Refill the batch queues if the back page is empty, dl_tree has work, and ++ // current page is empty or the total ongoing latency is below the threshold ++ if (!bq_page_has_rq(bq_state, !ad->bq_page) && ++ (!bq_curr_page_has_rq || (!tpl || tpl < div_u64( ++ ad->global_latency_window * ad->bq_refill_below_ratio, 100))) && ++ eval_this_adios_state(state, ADIOS_STATE_DL)) ++ fill_batch_queues(ad, tpl); ++ ++ // If current batch queue page is empty, and the other page has work, flip ++ if (!bq_curr_page_has_rq && ++ bq_page_has_rq(eval_adios_state(ad, ADIOS_STATE_BQ), !ad->bq_page)) ++ flip_bq_page(ad); ++ ++ // Use the per-page state to decide the dispatch logic, ensuring correctness ++ rq = (ad->bq_batch_order[ad->bq_page] == ADIOS_BO_ELEVATOR) ? ++ pop_next_bq_request_elevator(ad): ++ pop_next_bq_request_optype(ad); ++ ++ if (rq) { ++ bool page = ad->bq_page; ++ bool is_empty = !ad->bq_state[page]; ++ if (is_empty) ++ set_adios_state(ad, ADIOS_STATE_BQ, page, false); ++ return rq; ++ } ++ ++ return NULL; ++} ++ ++// Dispatch a request from the priority queue ++static struct request *dispatch_from_pq(struct adios_data *ad) { ++ struct request *rq = NULL; ++ ++ guard(spinlock_irqsave)(&ad->pq_lock); ++ u32 pq_state = eval_adios_state(ad, ADIOS_STATE_PQ); ++ u8 pq_idx = pq_state >> 1; ++ struct list_head *q = &ad->prio_queue[pq_idx]; ++ ++ if (unlikely(list_empty(q))) return NULL; ++ ++ rq = list_first_entry(q, struct request, queuelist); ++ list_del_init(&rq->queuelist); ++ if (list_empty(q)) { ++ set_adios_state(ad, ADIOS_STATE_PQ, pq_idx, false); ++ update_elv_direction(ad); ++ } ++ return rq; ++} ++ ++static bool release_barrier_requests(struct adios_data *ad) { ++ u32 moved_count = 0; ++ LIST_HEAD(local_list); ++ ++ scoped_guard(spinlock_irqsave, &ad->barrier_lock) { ++ if (!list_empty(&ad->barrier_queue)) { ++ struct request *trq, *next; ++ bool first_barrier_moved = false; ++ ++ list_for_each_entry_safe(trq, next, &ad->barrier_queue, queuelist) { ++ if (!first_barrier_moved) { ++ list_del_init(&trq->queuelist); ++ insert_to_prio_queue(ad, trq, 1); ++ moved_count++; ++ first_barrier_moved = true; ++ continue; ++ } ++ ++ if ((trq->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH) ++ break; ++ ++ list_move_tail(&trq->queuelist, &local_list); ++ moved_count++; ++ } ++ ++ if (list_empty(&ad->barrier_queue)) ++ set_adios_state(ad, ADIOS_STATE_BP, 0, false); ++ } ++ } ++ ++ if (!moved_count) ++ return false; ++ ++ if (!list_empty(&local_list)) { ++ struct request *trq, *next; ++ LIST_HEAD(free_list); ++ ++ /* ad->lock is already held */ ++ list_for_each_entry_safe(trq, next, &local_list, queuelist) { ++ list_del_init(&trq->queuelist); ++ if (merge_or_insert_to_dl_tree(ad, trq, ad->queue, &free_list)) ++ continue; ++ } ++ ++ if (!list_empty(&free_list)) ++ blk_mq_free_requests(&free_list); ++ } ++ ++ return true; ++} ++ ++// Dispatch a request to the hardware queue ++static struct request *adios_dispatch_request(struct blk_mq_hw_ctx *hctx) { ++ struct adios_data *ad = hctx->queue->elevator->elevator_data; ++ struct request *rq; ++ ++retry: ++ rq = dispatch_from_pq(ad); ++ if (rq) ++ goto found; ++ ++ rq = dispatch_from_bq(ad); ++ if (rq) ++ goto found; ++ ++ /* ++ * If all active queues are empty, check if we need to process a barrier. ++ * This is the trigger to release requests that were held in barrier_queue ++ * due to a REQ_OP_FLUSH barrier. ++ */ ++ if (eval_adios_state(ad, ADIOS_STATE_BP)) { ++ bool barrier_released = false; ++ scoped_guard(spinlock_irqsave, &ad->lock) ++ barrier_released = release_barrier_requests(ad); ++ if (barrier_released) ++ goto retry; ++ } ++ ++ return NULL; ++found: ++ if (ad->is_rotational) ++ ad->head_pos = blk_rq_pos(rq) + blk_rq_sectors(rq); ++ ++ rq->rq_flags |= RQF_STARTED; ++ return rq; ++} ++ ++// Timer callback function to periodically update latency models ++static void update_timer_callback(struct timer_list *t) { ++ struct adios_data *ad = timer_container_of(ad, t, update_timer); ++ ++ for (u8 optype = 0; optype < ADIOS_OPTYPES; optype++) ++ latency_model_update(ad, &ad->latency_model[optype]); ++} ++ ++// Handle the completion of a request ++static void adios_completed_request(struct request *rq, u64 now) { ++ struct adios_data *ad = rq->q->elevator->elevator_data; ++ struct adios_rq_data *rd = get_rq_data(rq); ++ union adios_in_flight_rqs ifr = { .scalar = 0 }; ++ ++ if (rd->managed) { ++ union adios_in_flight_rqs ifr_to_sub = { ++ .count = 1, ++ .total_pred_lat = rd->pred_lat, ++ }; ++ ifr.scalar = atomic64_sub_return( ++ ifr_to_sub.scalar, &ad->in_flight_rqs.atomic); ++ } ++ u8 optype = adios_optype(rq); ++ ++ unsigned long flags; ++ struct adios_pcpu_completion *pc; ++ ++ local_irq_save(flags); ++ pc = this_cpu_ptr(ad->pcpu_completion); ++ ++ if (optype == ADIOS_OTHER) { ++ // Non-positional commands make the head position unpredictable. ++ // Invalidate our knowledge of the last completed position. ++ if (ad->is_rotational) ++ pc->last_completed_pos = 0; ++ local_irq_restore(flags); ++ return; ++ } ++ ++ u64 lct = pc->last_completed_time ?: rq->io_start_time_ns; ++ pc->last_completed_time = (ifr.count) ? now : 0; ++ ++ if (!rq->io_start_time_ns || !rd->block_size || unlikely(now < lct)) { ++ local_irq_restore(flags); ++ return; ++ } ++ ++ u64 latency = now - lct; ++ ++ u32 weight = 1; ++ if (ad->is_rotational) { ++ sector_t current_pos = blk_rq_pos(rq); ++ // Only calculate seek distance if we have a valid last position. ++ if (pc->last_completed_pos > 0) { ++ u64 seek_distance = abs( ++ (s64)current_pos - (s64)pc->last_completed_pos); ++ if (seek_distance) ++ weight = 65 - __builtin_clzll(seek_distance); ++ } ++ // Update (or re-synchronize) our knowledge of the head position. ++ pc->last_completed_pos = current_pos + blk_rq_sectors(rq); ++ } ++ ++ local_irq_restore(flags); ++ ++ if (latency > ad->lat_model_latency_limit) ++ return; ++ ++ latency_model_input(ad, &ad->latency_model[optype], ++ rd->block_size, latency, rd->pred_lat, weight); ++ timer_reduce(&ad->update_timer, jiffies + msecs_to_jiffies(100)); ++} ++ ++// Clean up after a request is finished ++static void adios_finish_request(struct request *rq) { ++ struct adios_data *ad = rq->q->elevator->elevator_data; ++ ++ if (rq->elv.priv[0]) { ++ // Free adios_rq_data back to the memory pool ++ mempool_free(get_rq_data(rq), ad->rq_data_pool); ++ rq->elv.priv[0] = NULL; ++ } ++} ++ ++// Check if there are any requests available for dispatch ++static bool adios_has_work(struct blk_mq_hw_ctx *hctx) { ++ struct adios_data *ad = hctx->queue->elevator->elevator_data; ++ ++ return atomic_read(&ad->state) != 0; ++} ++ ++// Initialize the scheduler-specific data when initializing the request queue ++static int adios_init_sched(struct request_queue *q, struct elevator_queue *eq) { ++ struct adios_data *ad; ++ int ret = -ENOMEM; ++ u8 optype = 0; ++ ++ ad = kzalloc_node(sizeof(*ad), GFP_KERNEL, q->node); ++ if (!ad) { ++ pr_err("adios: Failed to create adios_data\n"); ++ goto put_eq; ++ } ++ ++ eq->elevator_data = ad; ++ ++ // Create a memory pool for adios_rq_data ++ ad->rq_data_cache = kmem_cache_create("adios_rq_data", ++ sizeof(struct adios_rq_data), ++ 0, SLAB_HWCACHE_ALIGN, NULL); ++ if (!ad->rq_data_cache) { ++ pr_err("adios: Failed to create rq_data_cache\n"); ++ goto free_ad; ++ } ++ ++ ad->rq_data_pool = mempool_create_slab_pool( ++ q->nr_requests, ad->rq_data_cache); ++ if (!ad->rq_data_pool) { ++ pr_err("adios: Failed to create rq_data_pool\n"); ++ goto destroy_rq_data_cache; ++ } ++ ++ /* Create a memory pool for dl_group */ ++ ad->dl_group_pool = kmem_cache_create("dl_group_pool", ++ sizeof(struct dl_group), ++ 0, SLAB_HWCACHE_ALIGN, NULL); ++ if (!ad->dl_group_pool) { ++ pr_err("adios: Failed to create dl_group_pool\n"); ++ goto destroy_rq_data_pool; ++ } ++ ++ for (int i = 0; i < ADIOS_PQ_LEVELS; i++) ++ INIT_LIST_HEAD(&ad->prio_queue[i]); ++ ++ for (u8 i = 0; i < ADIOS_DL_TYPES; i++) { ++ ad->dl_tree[i] = RB_ROOT_CACHED; ++ ad->dl_prio[i] = default_dl_prio[i]; ++ } ++ ad->dl_bias = 0; ++ ++ for (u8 page = 0; page < ADIOS_BQ_PAGES; page++) ++ for (optype = 0; optype < ADIOS_OPTYPES; optype++) ++ INIT_LIST_HEAD(&ad->batch_queue[page][optype]); ++ ++ ad->aggr_buckets = kzalloc(sizeof(*ad->aggr_buckets), GFP_KERNEL); ++ if (!ad->aggr_buckets) { ++ pr_err("adios: Failed to allocate aggregation buckets\n"); ++ goto destroy_dl_group_pool; ++ } ++ ++ ad->pcpu_completion = alloc_percpu(struct adios_pcpu_completion); ++ if (!ad->pcpu_completion) { ++ pr_err("adios: Failed to allocate per-CPU completion data\n"); ++ goto free_aggr_buckets; ++ } ++ ++ for (optype = 0; optype < ADIOS_OPTYPES; optype++) { ++ struct latency_model *model = &ad->latency_model[optype]; ++ struct latency_model_params *params; ++ ++ spin_lock_init(&model->update_lock); ++ params = kzalloc(sizeof(*params), GFP_KERNEL); ++ if (!params) { ++ pr_err("adios: Failed to allocate latency_model_params\n"); ++ goto free_buckets; ++ } ++ params->last_update_jiffies = jiffies; ++ RCU_INIT_POINTER(model->params, params); ++ ++ model->pcpu_buckets = alloc_percpu(struct lm_buckets); ++ if (!model->pcpu_buckets) { ++ pr_err("adios: Failed to allocate per-CPU buckets\n"); ++ kfree(params); ++ goto free_buckets; ++ } ++ ++ model->pcpu_snapshot = alloc_percpu(struct lm_buckets); ++ if (!model->pcpu_snapshot) { ++ pr_err("adios: Failed to allocate per-CPU snapshot\n"); ++ free_percpu(model->pcpu_buckets); ++ kfree(params); ++ goto free_buckets; ++ } ++ ++ model->lm_shrink_at_kreqs = default_lm_shrink_at_kreqs; ++ model->lm_shrink_at_gbytes = default_lm_shrink_at_gbytes; ++ model->lm_shrink_resist = default_lm_shrink_resist; ++ } ++ ++ for (optype = 0; optype < ADIOS_OPTYPES; optype++) { ++ ad->latency_target[optype] = default_latency_target[optype]; ++ ad->batch_limit[optype] = default_batch_limit[optype]; ++ } ++ ++ eq->elevator_data = ad; ++ ++ ad->is_rotational = !!(q->limits.features & BLK_FEAT_ROTATIONAL); ++ ad->global_latency_window = (ad->is_rotational)? ++ default_global_latency_window_rotational: ++ default_global_latency_window; ++ ad->bq_refill_below_ratio = default_bq_refill_below_ratio; ++ ad->lat_model_latency_limit = default_lat_model_latency_limit; ++ ad->batch_order = default_batch_order; ++ ad->compliance_flags = default_compliance_flags; ++ ++ ad->models_stable = false; ++ ++ atomic_set(&ad->state, 0); ++ ++ spin_lock_init(&ad->lock); ++ spin_lock_init(&ad->pq_lock); ++ spin_lock_init(&ad->bq_lock); ++ spin_lock_init(&ad->barrier_lock); ++ INIT_LIST_HEAD(&ad->barrier_queue); ++ ++ timer_setup(&ad->update_timer, update_timer_callback, 0); ++ ++ /* We dispatch from request queue wide instead of hw queue */ ++ blk_queue_flag_set(QUEUE_FLAG_SQ_SCHED, q); ++ ++ ad->queue = q; ++ blk_stat_enable_accounting(q); ++ ++ q->elevator = eq; ++ adios_depth_updated(q); ++ return 0; ++ ++free_buckets: ++ pr_err("adios: Failed to allocate per-cpu buckets\n"); ++ while (optype-- > 0) { ++ struct latency_model *prev_model = &ad->latency_model[optype]; ++ kfree(rcu_access_pointer(prev_model->params)); ++ free_percpu(prev_model->pcpu_snapshot); ++ free_percpu(prev_model->pcpu_buckets); ++ } ++ free_percpu(ad->pcpu_completion); ++free_aggr_buckets: ++ kfree(ad->aggr_buckets); ++destroy_dl_group_pool: ++ kmem_cache_destroy(ad->dl_group_pool); ++destroy_rq_data_pool: ++ mempool_destroy(ad->rq_data_pool); ++destroy_rq_data_cache: ++ kmem_cache_destroy(ad->rq_data_cache); ++free_ad: ++ kfree(ad); ++put_eq: ++ kobject_put(&eq->kobj); ++ return ret; ++} ++ ++// Clean up and free resources when exiting the scheduler ++static void adios_exit_sched(struct elevator_queue *e) { ++ struct adios_data *ad = e->elevator_data; ++ ++ timer_shutdown_sync(&ad->update_timer); ++ ++ WARN_ON_ONCE(!list_empty(&ad->barrier_queue)); ++ for (int i = 0; i < 2; i++) ++ WARN_ON_ONCE(!list_empty(&ad->prio_queue[i])); ++ ++ for (u8 i = 0; i < ADIOS_OPTYPES; i++) { ++ struct latency_model *model = &ad->latency_model[i]; ++ struct latency_model_params *params = rcu_access_pointer(model->params); ++ ++ RCU_INIT_POINTER(model->params, NULL); ++ kfree_rcu(params, rcu); ++ ++ free_percpu(model->pcpu_snapshot); ++ free_percpu(model->pcpu_buckets); ++ } ++ ++ synchronize_rcu(); ++ ++ free_percpu(ad->pcpu_completion); ++ kfree(ad->aggr_buckets); ++ ++ mempool_destroy(ad->rq_data_pool); ++ kmem_cache_destroy(ad->rq_data_cache); ++ ++ if (ad->dl_group_pool) ++ kmem_cache_destroy(ad->dl_group_pool); ++ ++ blk_stat_disable_accounting(ad->queue); ++ ++ kfree(ad); ++} ++ ++static void sideload_latency_model( ++ struct latency_model *model, u64 base, u64 slope) { ++ struct latency_model_params *old_params, *new_params; ++ unsigned long flags; ++ ++ new_params = kzalloc(sizeof(*new_params), GFP_KERNEL); ++ if (!new_params) ++ return; ++ ++ spin_lock_irqsave(&model->update_lock, flags); ++ ++ old_params = rcu_dereference_protected(model->params, ++ lockdep_is_held(&model->update_lock)); ++ ++ new_params->last_update_jiffies = jiffies; ++ ++ // Initialize base and its statistics as a single sample. ++ new_params->base = base; ++ new_params->small_sum_delay = base; ++ new_params->small_count = 1; ++ ++ // Initialize slope and its statistics as a single sample. ++ new_params->slope = slope; ++ new_params->large_sum_delay = slope; ++ new_params->large_sum_bsize = 1024; /* Corresponds to 1 KiB */ ++ ++ lm_reset_pcpu_buckets(model); ++ ++ rcu_assign_pointer(model->params, new_params); ++ spin_unlock_irqrestore(&model->update_lock, flags); ++ ++ kfree_rcu(old_params, rcu); ++} ++ ++// Define sysfs attributes for operation types ++#define SYSFS_OPTYPE_DECL(name, optype) \ ++static ssize_t adios_lat_model_##name##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ struct latency_model *model = &ad->latency_model[optype]; \ ++ struct latency_model_params *params; \ ++ ssize_t len = 0; \ ++ u64 base, slope; \ ++ rcu_read_lock(); \ ++ params = rcu_dereference(model->params); \ ++ base = params->base; \ ++ slope = params->slope; \ ++ rcu_read_unlock(); \ ++ len += sprintf(page, "base : %llu ns\n", base); \ ++ len += sprintf(page + len, "slope: %llu ns/KiB\n", slope); \ ++ return len; \ ++} \ ++static ssize_t adios_lat_model_##name##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ struct latency_model *model = &ad->latency_model[optype]; \ ++ u64 base, slope; \ ++ int ret; \ ++ ret = sscanf(page, "%llu %llu", &base, &slope); \ ++ if (ret != 2) \ ++ return -EINVAL; \ ++ sideload_latency_model(model, base, slope); \ ++ reset_buckets(ad->aggr_buckets); \ ++ return count; \ ++} \ ++static ssize_t adios_lat_target_##name##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ return sprintf(page, "%llu\n", ad->latency_target[optype]); \ ++} \ ++static ssize_t adios_lat_target_##name##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ unsigned long nsec; \ ++ int ret; \ ++ ret = kstrtoul(page, 10, &nsec); \ ++ if (ret) \ ++ return ret; \ ++ sideload_latency_model(&ad->latency_model[optype], 0, 0); \ ++ ad->latency_target[optype] = nsec; \ ++ return count; \ ++} \ ++static ssize_t adios_batch_limit_##name##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ return sprintf(page, "%u\n", ad->batch_limit[optype]); \ ++} \ ++static ssize_t adios_batch_limit_##name##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ unsigned long max_batch; \ ++ int ret; \ ++ ret = kstrtoul(page, 10, &max_batch); \ ++ if (ret || max_batch == 0) \ ++ return -EINVAL; \ ++ struct adios_data *ad = e->elevator_data; \ ++ ad->batch_limit[optype] = max_batch; \ ++ return count; \ ++} ++ ++SYSFS_OPTYPE_DECL(read, ADIOS_READ); ++SYSFS_OPTYPE_DECL(write, ADIOS_WRITE); ++SYSFS_OPTYPE_DECL(discard, ADIOS_DISCARD); ++ ++// Show the maximum batch size actually achieved for each operation type ++static ssize_t adios_batch_actual_max_show( ++ struct elevator_queue *e, char *page) { ++ struct adios_data *ad = e->elevator_data; ++ u32 total_count, read_count, write_count, discard_count; ++ ++ total_count = ad->batch_actual_max_total; ++ read_count = ad->batch_actual_max_size[ADIOS_READ]; ++ write_count = ad->batch_actual_max_size[ADIOS_WRITE]; ++ discard_count = ad->batch_actual_max_size[ADIOS_DISCARD]; ++ ++ return sprintf(page, ++ "Total : %u\nDiscard: %u\nRead : %u\nWrite : %u\n", ++ total_count, discard_count, read_count, write_count); ++} ++ ++#define SYSFS_ULL_DECL(field, min_val, max_val) \ ++static ssize_t adios_##field##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ return sprintf(page, "%llu\n", ad->field); \ ++} \ ++static ssize_t adios_##field##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ unsigned long val; \ ++ int ret; \ ++ ret = kstrtoul(page, 10, &val); \ ++ if (ret || val < (min_val) || val > (max_val)) \ ++ return -EINVAL; \ ++ ad->field = val; \ ++ return count; \ ++} ++ ++SYSFS_ULL_DECL(global_latency_window, 0, ULLONG_MAX) ++SYSFS_ULL_DECL(compliance_flags, 0, ULLONG_MAX) ++ ++#define SYSFS_INT_DECL(field, min_val, max_val) \ ++static ssize_t adios_##field##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ return sprintf(page, "%d\n", ad->field); \ ++} \ ++static ssize_t adios_##field##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ int val; \ ++ int ret; \ ++ ret = kstrtoint(page, 10, &val); \ ++ if (ret || val < (min_val) || val > (max_val)) \ ++ return -EINVAL; \ ++ ad->field = val; \ ++ return count; \ ++} ++ ++SYSFS_INT_DECL(bq_refill_below_ratio, 0, 100) ++SYSFS_INT_DECL(lat_model_latency_limit, 0, 2*NSEC_PER_SEC) ++SYSFS_INT_DECL(batch_order, ADIOS_BO_OPTYPE, !!ad->is_rotational) ++ ++// Show the read priority ++static ssize_t adios_read_priority_show( ++ struct elevator_queue *e, char *page) { ++ struct adios_data *ad = e->elevator_data; ++ return sprintf(page, "%d\n", ad->dl_prio[0]); ++} ++ ++// Set the read priority ++static ssize_t adios_read_priority_store( ++ struct elevator_queue *e, const char *page, size_t count) { ++ struct adios_data *ad = e->elevator_data; ++ int prio; ++ int ret; ++ ++ ret = kstrtoint(page, 10, &prio); ++ if (ret || prio < -20 || prio > 19) ++ return -EINVAL; ++ ++ guard(spinlock_irqsave)(&ad->lock); ++ ad->dl_prio[0] = prio; ++ ad->dl_bias = 0; ++ ++ return count; ++} ++ ++// Reset batch queue statistics ++static ssize_t adios_reset_bq_stats_store( ++ struct elevator_queue *e, const char *page, size_t count) { ++ struct adios_data *ad = e->elevator_data; ++ unsigned long val; ++ int ret; ++ ++ ret = kstrtoul(page, 10, &val); ++ if (ret || val != 1) ++ return -EINVAL; ++ ++ for (u8 i = 0; i < ADIOS_OPTYPES; i++) ++ ad->batch_actual_max_size[i] = 0; ++ ++ ad->batch_actual_max_total = 0; ++ ++ return count; ++} ++ ++// Reset the latency model parameters or load them from user input ++static ssize_t adios_reset_lat_model_store( ++ struct elevator_queue *e, const char *page, size_t count) ++{ ++ struct adios_data *ad = e->elevator_data; ++ struct latency_model *model; ++ int ret; ++ ++ /* ++ * Differentiate between two modes based on input format: ++ * 1. "1": Fully reset the model (backward compatibility). ++ * 2. "R_base R_slope W_base W_slope D_base D_slope": Load values. ++ */ ++ if (!strchr(page, ' ')) { ++ // Mode 1: Full reset. ++ unsigned long val; ++ ++ ret = kstrtoul(page, 10, &val); ++ if (ret || val != 1) ++ return -EINVAL; ++ ++ for (u8 i = 0; i < ADIOS_OPTYPES; i++) { ++ model = &ad->latency_model[i]; ++ sideload_latency_model(model, 0, 0); ++ } ++ } else { ++ // Mode 2: Load initial values for all latency models. ++ u64 params[3][2]; /* 0:base, 1:slope for R, W, D */ ++ ++ ret = sscanf(page, "%llu %llu %llu %llu %llu %llu", ++ ¶ms[ADIOS_READ ][0], ¶ms[ADIOS_READ ][1], ++ ¶ms[ADIOS_WRITE ][0], ¶ms[ADIOS_WRITE ][1], ++ ¶ms[ADIOS_DISCARD][0], ¶ms[ADIOS_DISCARD][1]); ++ ++ if (ret != 6) ++ return -EINVAL; ++ ++ for (u8 i = ADIOS_READ; i <= ADIOS_DISCARD; i++) { ++ model = &ad->latency_model[i]; ++ sideload_latency_model(model, params[i][0], params[i][1]); ++ } ++ } ++ reset_buckets(ad->aggr_buckets); ++ ++ return count; ++} ++ ++// Show the ADIOS version ++static ssize_t adios_version_show(struct elevator_queue *e, char *page) { ++ return sprintf(page, "%s\n", ADIOS_VERSION); ++} ++ ++// Define sysfs attributes for dynamic thresholds ++#define SHRINK_THRESHOLD_ATTR_RW(name, model_field, min_value, max_value) \ ++static ssize_t adios_shrink_##name##_store( \ ++ struct elevator_queue *e, const char *page, size_t count) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ unsigned long val; \ ++ int ret; \ ++ ret = kstrtoul(page, 10, &val); \ ++ if (ret || val < min_value || val > max_value) \ ++ return -EINVAL; \ ++ for (u8 i = 0; i < ADIOS_OPTYPES; i++) { \ ++ struct latency_model *model = &ad->latency_model[i]; \ ++ unsigned long flags; \ ++ spin_lock_irqsave(&model->update_lock, flags); \ ++ model->model_field = val; \ ++ spin_unlock_irqrestore(&model->update_lock, flags); \ ++ } \ ++ return count; \ ++} \ ++static ssize_t adios_shrink_##name##_show( \ ++ struct elevator_queue *e, char *page) { \ ++ struct adios_data *ad = e->elevator_data; \ ++ u32 val = 0; \ ++ unsigned long flags; \ ++ struct latency_model *model = &ad->latency_model[0]; \ ++ spin_lock_irqsave(&model->update_lock, flags); \ ++ val = model->model_field; \ ++ spin_unlock_irqrestore(&model->update_lock, flags); \ ++ return sprintf(page, "%u\n", val); \ ++} ++ ++SHRINK_THRESHOLD_ATTR_RW(at_kreqs, lm_shrink_at_kreqs, 1, 100000) ++SHRINK_THRESHOLD_ATTR_RW(at_gbytes, lm_shrink_at_gbytes, 1, 1000) ++SHRINK_THRESHOLD_ATTR_RW(resist, lm_shrink_resist, 1, 3) ++ ++// Define sysfs attributes ++#define AD_ATTR(name, show_func, store_func) \ ++ __ATTR(name, 0644, show_func, store_func) ++#define AD_ATTR_RW(name) \ ++ __ATTR(name, 0644, adios_##name##_show, adios_##name##_store) ++#define AD_ATTR_RO(name) \ ++ __ATTR(name, 0444, adios_##name##_show, NULL) ++#define AD_ATTR_WO(name) \ ++ __ATTR(name, 0200, NULL, adios_##name##_store) ++ ++// Define sysfs attributes for ADIOS scheduler ++static struct elv_fs_entry adios_sched_attrs[] = { ++ AD_ATTR_RO(batch_actual_max), ++ AD_ATTR_RW(bq_refill_below_ratio), ++ AD_ATTR_RW(global_latency_window), ++ AD_ATTR_RW(lat_model_latency_limit), ++ AD_ATTR_RW(batch_order), ++ AD_ATTR_RW(compliance_flags), ++ ++ AD_ATTR_RW(batch_limit_read), ++ AD_ATTR_RW(batch_limit_write), ++ AD_ATTR_RW(batch_limit_discard), ++ ++ AD_ATTR_RW(lat_model_read), ++ AD_ATTR_RW(lat_model_write), ++ AD_ATTR_RW(lat_model_discard), ++ ++ AD_ATTR_RW(lat_target_read), ++ AD_ATTR_RW(lat_target_write), ++ AD_ATTR_RW(lat_target_discard), ++ ++ AD_ATTR_RW(shrink_at_kreqs), ++ AD_ATTR_RW(shrink_at_gbytes), ++ AD_ATTR_RW(shrink_resist), ++ ++ AD_ATTR_RW(read_priority), ++ ++ AD_ATTR_WO(reset_bq_stats), ++ AD_ATTR_WO(reset_lat_model), ++ AD_ATTR(adios_version, adios_version_show, NULL), ++ ++ __ATTR_NULL ++}; ++ ++// Define the ADIOS scheduler type ++static struct elevator_type mq_adios = { ++ .ops = { ++ .next_request = elv_rb_latter_request, ++ .former_request = elv_rb_former_request, ++ .limit_depth = adios_limit_depth, ++ .depth_updated = adios_depth_updated, ++ .request_merged = adios_request_merged, ++ .requests_merged = adios_merged_requests, ++ .bio_merge = adios_bio_merge, ++ .insert_requests = adios_insert_requests, ++ .prepare_request = adios_prepare_request, ++ .dispatch_request = adios_dispatch_request, ++ .completed_request = adios_completed_request, ++ .finish_request = adios_finish_request, ++ .has_work = adios_has_work, ++ .init_sched = adios_init_sched, ++ .exit_sched = adios_exit_sched, ++ }, ++ .elevator_attrs = adios_sched_attrs, ++ .elevator_name = "adios", ++ .elevator_owner = THIS_MODULE, ++}; ++MODULE_ALIAS("mq-adios-iosched"); ++ ++#define ADIOS_PROGNAME "Adaptive Deadline I/O Scheduler" ++#define ADIOS_AUTHOR "Masahito Suzuki" ++ ++// Initialize the ADIOS scheduler module ++static int __init adios_init(void) { ++ printk(KERN_INFO "%s %s by %s\n", ++ ADIOS_PROGNAME, ADIOS_VERSION, ADIOS_AUTHOR); ++ return elv_register(&mq_adios); ++} ++ ++// Exit the ADIOS scheduler module ++static void __exit adios_exit(void) { ++ elv_unregister(&mq_adios); ++} ++ ++module_init(adios_init); ++module_exit(adios_exit); ++ ++MODULE_AUTHOR(ADIOS_AUTHOR); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION(ADIOS_PROGNAME); +\ No newline at end of file +diff --git a/block/elevator.c b/block/elevator.c +index 2161b6eea680..72a0dc6b345f 100644 +--- a/block/elevator.c ++++ b/block/elevator.c +@@ -740,6 +740,14 @@ void elevator_set_default(struct request_queue *q) + if (q->tag_set->flags & BLK_MQ_F_NO_SCHED_BY_DEFAULT) + return; + ++#ifdef CONFIG_MQ_IOSCHED_DEFAULT_ADIOS ++ ctx.name = "adios"; ++#else // !CONFIG_MQ_IOSCHED_DEFAULT_ADIOS ++ bool is_sq = q->nr_hw_queues == 1 || blk_mq_is_shared_tags(q->tag_set->flags); ++ if (!is_sq) ++ return; ++#endif // CONFIG_MQ_IOSCHED_DEFAULT_ADIOS ++ + /* + * For single queue devices, default to using mq-deadline. If we + * have multiple queues or mq-deadline is not available, default +@@ -749,13 +757,10 @@ void elevator_set_default(struct request_queue *q) + if (!ctx.type) + return; + +- if ((q->nr_hw_queues == 1 || +- blk_mq_is_shared_tags(q->tag_set->flags))) { +- err = elevator_change(q, &ctx); +- if (err < 0) +- pr_warn("\"%s\" elevator initialization, failed %d, falling back to \"none\"\n", +- ctx.name, err); +- } ++ err = elevator_change(q, &ctx); ++ if (err < 0) ++ pr_warn("\"%s\" elevator initialization, failed %d, falling back to \"none\"\n", ++ ctx.name, err); + elevator_put(ctx.type); + } + diff --git a/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch.sig b/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch.sig new file mode 100644 index 0000000..1f838b7 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0150-adios-3.2.0.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0200-idle.patch b/pkgbuilds/linux-omarchy-bore/0200-idle.patch new file mode 100644 index 0000000..dc499e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0200-idle.patch @@ -0,0 +1,61 @@ +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 +@@ -53,6 +53,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2697,6 +2698,9 @@ static void __init cmdline_table_adjust(struct cpuidle_driver *drv) + pr_info("Failed to adjust C-states with data from 'intel_idle.table'\n"); + } + ++#define INTEL_IDLE_INIT_QOS 20 ++static struct pm_qos_request qos_req __initdata; ++ + static int __init intel_idle_init(void) + { + const struct x86_cpu_id *id; +@@ -2766,6 +2770,13 @@ static int __init intel_idle_init(void) + if (retval) + pr_warn("failed to initialized sysfs"); + ++ /* ++ * Some platforms, in particular the Intel S1200BTL motherboard, have a ++ * problem with using package idle states too early, so prevent that ++ * from taking place until the device_initcall() phase is over. ++ */ ++ cpu_latency_qos_add_request(&qos_req, INTEL_IDLE_INIT_QOS); ++ + retval = cpuidle_register_driver(&intel_idle_driver); + if (retval) { + struct cpuidle_driver *drv = cpuidle_get_driver(); +@@ -2790,6 +2801,9 @@ static int __init intel_idle_init(void) + intel_idle_cpuidle_devices_uninit(); + cpuidle_unregister_driver(&intel_idle_driver); + init_driver_fail: ++ if (cpu_latency_qos_request_active((&qos_req))) ++ cpu_latency_qos_remove_request(&qos_req); ++ + intel_idle_sysfs_uninit(); + free_percpu(intel_idle_cpuidle_devices); + return retval; +@@ -2797,6 +2811,15 @@ static int __init intel_idle_init(void) + } + subsys_initcall_sync(intel_idle_init); + ++static int __init intel_idle_init_complete(void) ++{ ++ if (cpu_latency_qos_request_active((&qos_req))) ++ cpu_latency_qos_remove_request(&qos_req); ++ ++ return 0; ++} ++device_initcall_sync(intel_idle_init_complete); ++ + /* + * We are not really modular, but we used to support that. Meaning we also + * support "intel_idle.max_cstate=..." at boot and also a read-only export of diff --git a/pkgbuilds/linux-omarchy-bore/0200-idle.patch.sig b/pkgbuilds/linux-omarchy-bore/0200-idle.patch.sig new file mode 100644 index 0000000..20c9157 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0200-idle.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0210-pstate.patch b/pkgbuilds/linux-omarchy-bore/0210-pstate.patch new file mode 100644 index 0000000..3d4eefc --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0210-pstate.patch @@ -0,0 +1,480 @@ +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 +@@ -782,6 +782,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; +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,19 +2322,13 @@ 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(); + + if (pstate_funcs.get_vid) + pstate_funcs.get_vid(cpu); +- +- intel_pstate_set_min_pstate(cpu); + } + + /* +@@ -2884,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) +@@ -2901,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); + + /* +@@ -2910,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); + +@@ -2971,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]; +@@ -3063,6 +3061,7 @@ static int __intel_pstate_cpu_init(struct cpufreq_policy *policy) + static int intel_pstate_cpu_init(struct cpufreq_policy *policy) + { + int ret = __intel_pstate_cpu_init(policy); ++ struct cpudata *cpu; + + if (ret) + return ret; +@@ -3073,11 +3072,11 @@ static int intel_pstate_cpu_init(struct cpufreq_policy *policy) + */ + policy->policy = CPUFREQ_POLICY_POWERSAVE; + +- if (hwp_active) { +- struct cpudata *cpu = all_cpu_data[policy->cpu]; +- ++ cpu = all_cpu_data[policy->cpu]; ++ if (hwp_active) + cpu->epp_cached = intel_pstate_get_epp(cpu, 0); +- } ++ else ++ intel_pstate_set_min_pstate(cpu); + + return 0; + } +@@ -3243,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]; +@@ -3273,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; + +@@ -3301,8 +3307,6 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) + return ret; + + policy->cpuinfo.transition_latency = INTEL_CPUFREQ_TRANSITION_LATENCY; +- /* This reflects the intel_pstate_get_cpu_pstates() setting. */ +- policy->cur = policy->cpuinfo.min_freq; + + req = kzalloc_objs(*req, 2); + if (!req) { +@@ -3323,9 +3327,15 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) + WRITE_ONCE(cpu->hwp_req_cached, value); + + cpu->epp_cached = intel_pstate_get_epp(cpu, value); ++ ++ intel_cpufreq_hwp_update(cpu, cpu->pstate.min_pstate, ++ cpu->pstate.max_pstate, ++ cpu->pstate.min_pstate, false); + } else { + policy->transition_delay_us = INTEL_CPUFREQ_TRANSITION_DELAY; ++ intel_pstate_set_min_pstate(cpu); + } ++ policy->cur = policy->cpuinfo.min_freq; + + freq = DIV_ROUND_UP(cpu->pstate.turbo_freq * global.min_perf_pct, 100); + +@@ -3676,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), + {} + }; +@@ -3808,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; +@@ -3816,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/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/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; +diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs +--- a/rust/kernel/cpufreq.rs ++++ b/rust/kernel/cpufreq.rs +@@ -792,7 +792,13 @@ fn fast_switch(_policy: &mut Policy, _target_freq: u32) -> u32 { + } + + /// 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) + } + +@@ -1263,12 +1269,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-bore/0210-pstate.patch.sig b/pkgbuilds/linux-omarchy-bore/0210-pstate.patch.sig new file mode 100644 index 0000000..463f061 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0210-pstate.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch b/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch new file mode 100644 index 0000000..67a6cf4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch @@ -0,0 +1,127 @@ +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 +@@ -560,6 +560,11 @@ static int amd_pstate_ut_check_freq_attrs(u32 index) + static int __init amd_pstate_ut_init(void) + { + u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); ++ enum amd_pstate_mode mode = amd_pstate_get_status(); ++ ++ /* don't test if no running amd-pstate driver */ ++ if (mode == AMD_PSTATE_UNDEFINED || mode == AMD_PSTATE_DISABLE) ++ return -EOPNOTSUPP; + + for (i = 0; i < arr_size; i++) { + int ret; +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 +@@ -199,7 +199,7 @@ static inline int get_mode_idx_from_str(const char *str, size_t size) + + static DEFINE_MUTEX(amd_pstate_driver_lock); + +-static u8 msr_get_epp(struct amd_cpudata *cpudata) ++static int msr_get_epp(struct amd_cpudata *cpudata) + { + u64 value; + int ret; +@@ -215,12 +215,12 @@ static u8 msr_get_epp(struct amd_cpudata *cpudata) + + DEFINE_STATIC_CALL(amd_pstate_get_epp, msr_get_epp); + +-static inline s16 amd_pstate_get_epp(struct amd_cpudata *cpudata) ++static inline int amd_pstate_get_epp(struct amd_cpudata *cpudata) + { + return static_call(amd_pstate_get_epp)(cpudata); + } + +-static u8 shmem_get_epp(struct amd_cpudata *cpudata) ++static int shmem_get_epp(struct amd_cpudata *cpudata) + { + u64 epp; + int ret; +@@ -526,9 +526,6 @@ static int shmem_init_perf(struct amd_cpudata *cpudata) + WRITE_ONCE(cpudata->perf, perf); + WRITE_ONCE(cpudata->prefcore_ranking, cppc_perf.highest_perf); + +- if (cppc_state == AMD_PSTATE_ACTIVE) +- return 0; +- + ret = cppc_get_auto_sel(cpudata->cpu, &auto_sel); + if (ret) { + pr_warn("failed to get auto_sel, ret: %d\n", ret); +@@ -1174,6 +1171,9 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, + if (cpudata->current_profile != PLATFORM_PROFILE_BALANCED) + return 0; + ++ if (!policy) ++ return NOTIFY_OK; ++ + epp = amd_pstate_get_balanced_epp(policy); + + ret = amd_pstate_set_epp(policy, epp); +@@ -1209,6 +1209,9 @@ static int amd_pstate_profile_set(struct device *dev, + struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); + int ret; + ++ if (!policy) ++ return -ENODEV; ++ + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); +@@ -1877,6 +1880,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + struct amd_cpudata *cpudata; + union perf_cached perf; + struct device *dev; ++ int default_epp; + int ret; + + /* +@@ -1925,6 +1929,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 */ ++ default_epp = amd_pstate_get_epp(cpudata); ++ if (default_epp < 0) { ++ ret = default_epp; ++ goto free_cpudata1; ++ } ++ + /* + * Set the policy to provide a valid fallback value in case + * the default cpufreq governor is neither powersave nor performance. +@@ -1932,7 +1943,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + if (amd_pstate_acpi_pm_profile_server() || + amd_pstate_acpi_pm_profile_undefined()) { + policy->policy = CPUFREQ_POLICY_PERFORMANCE; +- cpudata->epp_default_ac = cpudata->epp_default_dc = amd_pstate_get_epp(cpudata); ++ cpudata->epp_default_ac = cpudata->epp_default_dc = default_epp; + cpudata->current_profile = PLATFORM_PROFILE_PERFORMANCE; + } else { + policy->policy = CPUFREQ_POLICY_POWERSAVE; +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 +@@ -32,6 +32,7 @@ + * @min_limit_perf: Cached value of the performance corresponding to policy->min + * @max_limit_perf: Cached value of the performance corresponding to policy->max + * @bios_min_perf: Cached perf value corresponding to the "Requested CPU Min Frequency" BIOS option ++ * @val: Raw 64-bit value for atomic access via READ_ONCE()/WRITE_ONCE() + */ + union perf_cached { + struct { +@@ -89,7 +90,12 @@ struct amd_aperf_mperf { + * @epp_default_ac: Default EPP value for AC power source + * @epp_default_dc: Default EPP value for DC power source + * @dynamic_epp: Whether dynamic EPP is enabled ++ * @raw_epp: Whether the last EPP write was a raw numeric value rather than a ++ * named preference + * @power_nb: Notifier block for power events ++ * @current_profile: Currently selected platform profile option ++ * @ppdev: Device registered with the platform profile handler ++ * @profile_name: Name under which @ppdev is registered + * + * The amd_cpudata is key private data for each CPU thread in AMD P-State, and + * represents all the attributes and goals that AMD P-State requests at runtime. diff --git a/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch.sig new file mode 100644 index 0000000..018259f Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch b/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch new file mode 100644 index 0000000..61ea14c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch @@ -0,0 +1,18 @@ +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 +@@ -1929,12 +1929,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 diff --git a/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch.sig b/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch.sig new file mode 100644 index 0000000..ef0e5da Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0250-zsmalloc.patch b/pkgbuilds/linux-omarchy-bore/0250-zsmalloc.patch new file mode 100644 index 0000000..43793e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0250-zsmalloc.patch @@ -0,0 +1,396 @@ +diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c +--- a/mm/zsmalloc.c ++++ b/mm/zsmalloc.c +@@ -21,6 +21,10 @@ + * pool->lock + * 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-bore/0250-zsmalloc.patch.sig b/pkgbuilds/linux-omarchy-bore/0250-zsmalloc.patch.sig new file mode 100644 index 0000000..24e9d20 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0250-zsmalloc.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0260-mglru-exec-protect.patch b/pkgbuilds/linux-omarchy-bore/0260-mglru-exec-protect.patch new file mode 100644 index 0000000..f3b0e56 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0260-mglru-exec-protect.patch.sig b/pkgbuilds/linux-omarchy-bore/0260-mglru-exec-protect.patch.sig new file mode 100644 index 0000000..b84157c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0260-mglru-exec-protect.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0270-ksm-rmap-walk.patch b/pkgbuilds/linux-omarchy-bore/0270-ksm-rmap-walk.patch new file mode 100644 index 0000000..afe8241 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0270-ksm-rmap-walk.patch.sig b/pkgbuilds/linux-omarchy-bore/0270-ksm-rmap-walk.patch.sig new file mode 100644 index 0000000..f6989e4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0270-ksm-rmap-walk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch new file mode 100644 index 0000000..46a53c5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch @@ -0,0 +1,1270 @@ +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 +@@ -270,10 +270,15 @@ struct mem_cgroup { + #endif + int kmemcg_id; + +- struct memcg_vmstats_percpu __percpu *vmstats_percpu; +- + #ifdef CONFIG_CGROUP_WRITEBACK + struct list_head cgwb_list; ++#endif ++ ++ /* Keep the hot per-CPU stats pointer away from memory event counters. */ ++ struct memcg_vmstats_percpu __percpu *vmstats_percpu ++ ____cacheline_aligned_in_smp; ++ ++#ifdef CONFIG_CGROUP_WRITEBACK + struct wb_domain cgwb_domain; + struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT]; + #endif +@@ -947,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); + +@@ -1399,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; +@@ -3793,35 +3825,40 @@ int mas_alloc_cyclic(struct ma_state *mas, unsigned long *startp, + void *entry, unsigned long range_lo, unsigned long range_hi, + unsigned long *next, gfp_t gfp) + { +- unsigned long min = range_lo; +- int ret = 0; ++ int ret; ++ unsigned long min; ++ ++ min = range_lo; ++ do { ++ range_lo = max(min, *next); ++ ret = mas_empty_area(mas, range_lo, range_hi, 1); ++ if (ret < 0 && range_lo > min) { ++ mas_reset(mas); ++ ret = mas_empty_area(mas, min, range_hi, 1); ++ if (ret == 0) ++ ret = 1; ++ } ++ if (ret < 0) ++ goto out; ++ ++ mas_insert(mas, entry); ++ } while (mas_nomem(mas, gfp)); ++ ++ if (mas_is_err(mas)) { ++ ret = xa_err(mas->node); ++ goto out; ++ } + +- range_lo = max(min, *next); +- ret = mas_empty_area(mas, range_lo, range_hi, 1); + if ((mas->tree->ma_flags & MT_FLAGS_ALLOC_WRAPPED) && ret == 0) { + mas->tree->ma_flags &= ~MT_FLAGS_ALLOC_WRAPPED; + ret = 1; + } +- if (ret < 0 && range_lo > min) { +- mas_reset(mas); +- ret = mas_empty_area(mas, min, range_hi, 1); +- if (ret == 0) +- ret = 1; +- } +- if (ret < 0) +- return ret; +- +- do { +- mas_insert(mas, entry); +- } while (mas_nomem(mas, gfp)); +- if (mas_is_err(mas)) +- return xa_err(mas->node); +- + *startp = mas->index; + *next = *startp + 1; + if (*next == 0) + mas->tree->ma_flags |= MT_FLAGS_ALLOC_WRAPPED; + ++out: + mas_destroy(mas); + return ret; + } +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); +@@ -1153,7 +1086,16 @@ static void lruvec_reparent_lru(struct lruvec *child_lruvec, + for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { + unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + ++ if (!size) ++ continue; ++ ++ /* ++ * The folios are accounted to the parent from now on, so the ++ * size has to be moved, not just copied. Leaving it behind ++ * makes the dying child describe folios it no longer owns. ++ */ + mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); ++ mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); + } + } + +@@ -1164,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; +@@ -4571,7 +4629,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, + for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { + unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + ++ if (!size) ++ continue; ++ ++ /* Move the accounting, do not duplicate it. */ + mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); ++ mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); + } + } + } +@@ -4814,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); +@@ -4826,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) +@@ -4838,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); +@@ -4887,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-bore/0280-mm-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch.sig new file mode 100644 index 0000000..4e1b338 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch b/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch new file mode 100644 index 0000000..a14613c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch @@ -0,0 +1,97 @@ +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 +@@ -202,6 +202,8 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_readNCount_body_bmi2( + { + return FSE_readNCount_body(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } ++#else ++#define FSE_readNCount_body_bmi2 FSE_readNCount_body_default + #endif + + size_t FSE_readNCount_bmi2( +@@ -323,6 +325,8 @@ static BMI2_TARGET_ATTRIBUTE size_t HUF_readStats_body_bmi2(BYTE* huffWeight, si + { + return HUF_readStats_body(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize, 1); + } ++#else ++#define HUF_readStats_body_bmi2 HUF_readStats_body_default + #endif + + size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, +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 +@@ -300,6 +300,8 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, siz + { + return FSE_decompress_wksp_body(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, 1); + } ++#else ++#define FSE_decompress_wksp_body_bmi2 FSE_decompress_wksp_body_default + #endif + + 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) +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 +@@ -415,6 +415,10 @@ ZSTD_encodeSequences_bmi2( + sequences, nbSeq, longOffsets); + } + ++#else ++ ++#define ZSTD_encodeSequences_bmi2 ZSTD_encodeSequences_default ++ + #endif + + size_t ZSTD_encodeSequences( +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 +@@ -700,6 +700,8 @@ size_t HUF_decompress4X1_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } ++#else ++#define HUF_decompress4X1_usingDTable_internal_bmi2 HUF_decompress4X1_usingDTable_internal_default + #endif + + static +@@ -1503,6 +1505,8 @@ size_t HUF_decompress4X2_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } ++#else ++#define HUF_decompress4X2_usingDTable_internal_bmi2 HUF_decompress4X2_usingDTable_internal_default + #endif + + static +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 +@@ -622,6 +622,8 @@ BMI2_TARGET_ATTRIBUTE static void ZSTD_buildFSETable_body_bmi2(ZSTD_seqSymbol* d + ZSTD_buildFSETable_body(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); + } ++#else ++#define ZSTD_buildFSETable_body_bmi2 ZSTD_buildFSETable_body_default + #endif + + void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, +@@ -1934,6 +1936,16 @@ ZSTD_decompressSequencesLong_bmi2(ZSTD_DCtx* dctx, + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ + ++#else ++ ++#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG ++#define ZSTD_decompressSequences_bmi2 ZSTD_decompressSequences_default ++#define ZSTD_decompressSequencesSplitLitBuffer_bmi2 ZSTD_decompressSequencesSplitLitBuffer_default ++#endif ++#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT ++#define ZSTD_decompressSequencesLong_bmi2 ZSTD_decompressSequencesLong_default ++#endif ++ + #endif /* DYNAMIC_BMI2 */ + + #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG diff --git a/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch.sig b/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch.sig new file mode 100644 index 0000000..66df3c4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0290-zstd-bmi2-fallback-aliases.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0291-zstd-bmi2-cpu-feature-dispatch.patch b/pkgbuilds/linux-omarchy-bore/0291-zstd-bmi2-cpu-feature-dispatch.patch new file mode 100644 index 0000000..9f0b872 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig b/pkgbuilds/linux-omarchy-bore/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig new file mode 100644 index 0000000..9fb78f3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0292-crypto-zstd-defer-cstream-init.patch b/pkgbuilds/linux-omarchy-bore/0292-crypto-zstd-defer-cstream-init.patch new file mode 100644 index 0000000..4007e61 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0292-crypto-zstd-defer-cstream-init.patch @@ -0,0 +1,44 @@ +diff --git a/crypto/zstd.c b/crypto/zstd.c +--- a/crypto/zstd.c ++++ b/crypto/zstd.c +@@ -96,6 +96,7 @@ static int zstd_compress_one(struct acomp_req *req, struct zstd_ctx *ctx, + + static int zstd_compress(struct acomp_req *req) + { ++ bool stream_initialized = false; + struct crypto_acomp_stream *s; + unsigned int pos, scur, dcur; + unsigned int total_out = 0; +@@ -115,12 +116,6 @@ static int zstd_compress(struct acomp_req *req) + if (ret) + goto out; + +- ctx->cctx = 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-bore/0292-crypto-zstd-defer-cstream-init.patch.sig b/pkgbuilds/linux-omarchy-bore/0292-crypto-zstd-defer-cstream-init.patch.sig new file mode 100644 index 0000000..38fc2a9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0292-crypto-zstd-defer-cstream-init.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0293-crypto-zstd-defer-dstream-init.patch b/pkgbuilds/linux-omarchy-bore/0293-crypto-zstd-defer-dstream-init.patch new file mode 100644 index 0000000..224f1ad --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0293-crypto-zstd-defer-dstream-init.patch @@ -0,0 +1,44 @@ +diff --git a/crypto/zstd.c b/crypto/zstd.c +--- a/crypto/zstd.c ++++ b/crypto/zstd.c +@@ -215,6 +215,7 @@ static int zstd_decompress_one(struct acomp_req *req, struct zstd_ctx *ctx, + + static int zstd_decompress(struct acomp_req *req) + { ++ bool stream_initialized = false; + struct crypto_acomp_stream *s; + unsigned int total_out = 0; + unsigned int scur, dcur; +@@ -232,12 +233,6 @@ static int zstd_decompress(struct acomp_req *req) + if (ret) + goto out; + +- ctx->dctx = 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-bore/0293-crypto-zstd-defer-dstream-init.patch.sig b/pkgbuilds/linux-omarchy-bore/0293-crypto-zstd-defer-dstream-init.patch.sig new file mode 100644 index 0000000..5edd1fa Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0293-crypto-zstd-defer-dstream-init.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0295-af-alg-restrict.patch b/pkgbuilds/linux-omarchy-bore/0295-af-alg-restrict.patch new file mode 100644 index 0000000..2700fc9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0295-af-alg-restrict.patch @@ -0,0 +1,363 @@ +--- a/crypto/af_alg.c ++++ b/crypto/af_alg.c +@@ -8,6 +8,7 @@ + */ + + #include ++#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-bore/0295-af-alg-restrict.patch.sig b/pkgbuilds/linux-omarchy-bore/0295-af-alg-restrict.patch.sig new file mode 100644 index 0000000..94672f5 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0295-af-alg-restrict.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch b/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch new file mode 100644 index 0000000..602ef86 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch @@ -0,0 +1,12 @@ +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 +@@ -806,7 +806,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) + pmdval_t val = pmd_val(pmd), oldval = val; + pmd_t pmd_result; + +- val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY); ++ val &= _HPAGE_CHG_MASK; + val |= check_pgprot(newprot) & ~_HPAGE_CHG_MASK; + val = flip_protnone_guard(oldval, val, PHYSICAL_PMD_PAGE_MASK); + diff --git a/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch.sig b/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch.sig new file mode 100644 index 0000000..b5e1f0f Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch new file mode 100644 index 0000000..0cb9f8d --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch @@ -0,0 +1,5023 @@ +diff --git a/fs/btrfs/acl.c b/fs/btrfs/acl.c +--- a/fs/btrfs/acl.c ++++ b/fs/btrfs/acl.c +@@ -15,6 +15,7 @@ + #include "xattr.h" + #include "acl.h" + #include "misc.h" ++#include "btrfs_inode.h" + + struct posix_acl *btrfs_get_acl(struct inode *inode, int type, bool rcu) + { +@@ -107,6 +108,9 @@ int btrfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, + struct inode *inode = d_inode(dentry); + umode_t old_mode = inode->i_mode; + ++ if (btrfs_root_readonly(BTRFS_I(inode)->root)) ++ return -EROFS; ++ + if (type == ACL_TYPE_ACCESS && acl) { + ret = posix_acl_update_mode(idmap, inode, + &inode->i_mode, &acl); +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 +@@ -2047,6 +2047,11 @@ static int btrfs_reclaim_block_group(struct btrfs_block_group *bg, int *reclaime + + trace_btrfs_reclaim_block_group(bg); + ret = btrfs_relocate_chunk(fs_info, bg->start, false); ++ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) { ++ btrfs_dec_block_group_ro(bg); ++ btrfs_debug(fs_info, "deferring reclaim of chunk %llu", bg->start); ++ return ret; ++ } + if (ret) { + btrfs_dec_block_group_ro(bg); + btrfs_err(fs_info, "error relocating chunk %llu", +@@ -2113,7 +2118,8 @@ void btrfs_reclaim_block_groups(struct btrfs_fs_info *fs_info, unsigned int limi + spin_unlock(&fs_info->unused_bgs_lock); + ret = btrfs_reclaim_block_group(bg, &reclaimed); + +- if (ret && !READ_ONCE(space_info->periodic_reclaim)) ++ if ((btrfs_is_zoned(fs_info) && ret == -EAGAIN) || ++ (ret && !READ_ONCE(space_info->periodic_reclaim))) + btrfs_link_bg_list(bg, &retry_list); + btrfs_put_block_group(bg); + +@@ -2624,10 +2630,9 @@ static int fill_dummy_bgs(struct btrfs_fs_info *fs_info) + + /* Fill dummy cache as FULL */ + bg->length = 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); + /* +@@ -3916,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); + } + } +@@ -1130,20 +1130,15 @@ static_assert(PAGE_ALIGNED(CLUSTER_SIZE)); + * + * - Extent bits are locked + */ +-static int defrag_one_locked_target(struct btrfs_inode *inode, +- struct defrag_target_range *target, +- struct folio **folios, int nr_pages, +- struct extent_state **cached_state) ++static void defrag_one_locked_target(struct btrfs_inode *inode, ++ struct defrag_target_range *target, ++ struct folio **folios, int nr_pages, ++ struct extent_state **cached_state) + { + struct btrfs_fs_info *fs_info = inode->root->fs_info; +- struct extent_changeset *data_reserved = NULL; + const u64 start = target->start; + const u64 len = target->len; +- int ret = 0; + +- ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); +- if (ret < 0) +- return ret; + btrfs_clear_extent_bit(&inode->io_tree, start, start + len - 1, + EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | + EXTENT_DEFRAG, cached_state); +@@ -1164,10 +1159,6 @@ static int defrag_one_locked_target(struct btrfs_inode *inode, + continue; + btrfs_folio_clamp_set_dirty(fs_info, folio, start, len); + } +- btrfs_delalloc_release_extents(inode, len); +- extent_changeset_free(data_reserved); +- +- return ret; + } + + static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, +@@ -1178,11 +1169,13 @@ 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) - + (start >> PAGE_SHIFT) + 1; ++ struct extent_changeset *data_reserved = NULL; ++ u64 last_defrag_end = start; + int ret = 0; + + ASSERT(nr_pages <= CLUSTER_SIZE / PAGE_SIZE); +@@ -1192,6 +1185,20 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + if (!folios) + return -ENOMEM; + ++ /* ++ * Reserve delalloc space before locking the range and before locking ++ * and dirtying any folios - otherwise we could deadlock, for example ++ * after defrag of one range we dirty folios and keep them locked when ++ * we move to the next range, so reserving delalloc space right before ++ * each range could trigger flushing of delalloc and deadlock on the ++ * extent lock or trigger a transaction commit with flushoncommit, which ++ * can either deadlock on the lock of a folio made dirty in the previous ++ * range or the extent lock. ++ */ ++ ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); ++ if (ret < 0) ++ return ret; ++ + /* Prepare all pages */ + for (int i = 0; cur < start + len && i < nr_pages; i++) { + folios[i] = defrag_prepare_one_folio(inode, cur >> PAGE_SHIFT); +@@ -1225,15 +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) { +- ret = defrag_one_locked_target(inode, entry, folios, nr_pages, +- &cached_state); +- if (ret < 0) +- break; +- } +- + list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->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; + kfree(entry); + } + unlock_extent: +@@ -1245,7 +1249,12 @@ 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, ++ start + len - last_defrag_end, true); ++ extent_changeset_free(data_reserved); ++ + return ret; + } + +@@ -1310,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 +@@ -271,14 +271,15 @@ int btree_csum_one_bio(struct btrfs_bio *bbio) + return -EIO; + + /* +- * If an extent_buffer is marked as EXTENT_BUFFER_ZONED_ZEROOUT, don't +- * checksum it but zero-out its content. This is done to preserve +- * ordering of I/O without unnecessarily writing out data. ++ * An extent_buffer marked EXTENT_BUFFER_ZONED_ZEROOUT is written out as ++ * zeros to preserve ordering of I/O without persisting the now ++ * unnecessary block. The bio is fed from the shared zero page (see ++ * write_one_eb()), so there is nothing to checksum here. Crucially, the ++ * buffer's own content is left intact: it may still be referenced, e.g. ++ * btrfs_free_tree_block() reads its header to add a delayed reference. + */ +- if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) { +- memzero_extent_buffer(eb, 0, eb->len); ++ if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) + return 0; +- } + + if (WARN_ON_ONCE(found_start != eb->start)) + return -EIO; +@@ -590,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); + } + + /* +@@ -608,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; +@@ -666,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); +@@ -2052,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; + } +@@ -2062,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; + } + +@@ -2303,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; + } + +@@ -2395,8 +2395,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); + +@@ -2438,24 +2438,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; + } +@@ -2905,7 +2905,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; +@@ -2979,8 +2978,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; + } +@@ -3083,7 +3082,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; + } + } +@@ -3094,7 +3093,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; + } + } +@@ -3105,7 +3104,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; + } + /* +@@ -3139,7 +3139,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; + } + +@@ -3149,7 +3149,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; + } + } +@@ -3177,7 +3177,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; + } + } +@@ -3314,6 +3314,8 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info) + */ + rcu_read_lock(); + xa_for_each(&fs_info->buffer_tree, index, eb) { ++ unsigned int refs; ++ + /* Increase the ref so that the eb won't disappear. */ + if (!refcount_inc_not_zero(&eb->refs)) + continue; +@@ -3323,17 +3325,27 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info) + if (test_bit(EXTENT_BUFFER_READING, &eb->bflags)) + wait_on_bit_io(&eb->bflags, EXTENT_BUFFER_READING, + TASK_UNINTERRUPTIBLE); ++ /* ++ * We hold the spinlock to make sure above ++ * EXTENT_BUFFER_READING flag is cleared with the held ++ * ref dropped. ++ * Or we can hit a race window and lead to false alerts. ++ */ ++ spin_lock(&eb->refs_lock); ++ refs = refcount_read(&eb->refs); ++ spin_unlock(&eb->refs_lock); ++ + /* + * The refs threshold is 2, one held by us at the beginning + * of the loop, one for the ownership in the buffer tree. + */ +- if (unlikely(refcount_read(&eb->refs) > 2 || extent_buffer_under_io(eb))) { ++ if (unlikely(refs > 2 || extent_buffer_under_io(eb))) { + WARN_ON_ONCE(IS_ENABLED(CONFIG_BTRFS_DEBUG)); + btrfs_warn(fs_info, + "unable to release extent buffer %llu owner %llu gen %llu refs %u flags 0x%lx", + eb->start, btrfs_header_owner(eb), + btrfs_header_generation(eb), +- refcount_read(&eb->refs), eb->bflags); ++ refs, eb->bflags); + } + free_extent_buffer(eb); + rcu_read_lock(); +@@ -3355,7 +3367,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; +@@ -3464,7 +3475,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)); + +@@ -3483,7 +3493,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) +@@ -3549,7 +3558,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; + } + +@@ -3568,7 +3577,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; + } + +@@ -3598,7 +3607,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; + } + +@@ -3621,72 +3630,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; + } + +@@ -3742,7 +3752,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; + } +@@ -3763,7 +3773,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; + } +@@ -3879,8 +3889,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; + } +@@ -3898,8 +3908,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; + } +@@ -4381,6 +4391,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 +@@ -4511,7 +4536,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)); + } + } + +@@ -4550,6 +4575,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. +@@ -4998,6 +5030,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--; + +@@ -2379,14 +2397,17 @@ static struct extent_buffer *find_extent_buffer_nolock( + static void end_bbio_meta_write(struct btrfs_bio *bbio) + { + struct extent_buffer *eb = bbio->private; +- struct folio_iter fi; + + if (bbio->bio.bi_status != BLK_STS_OK) + set_btree_ioerr(eb); + +- bio_for_each_folio_all(fi, &bbio->bio) { +- btrfs_meta_folio_clear_writeback(fi.folio, eb); +- } ++ /* ++ * Clear writeback on the buffer's own folios. The bio may carry the ++ * shared zero page instead (EXTENT_BUFFER_ZONED_ZEROOUT), so iterate ++ * the extent buffer folios rather than the bio folios. ++ */ ++ for (int i = 0; i < num_extent_folios(eb); i++) ++ btrfs_meta_folio_clear_writeback(eb->folios[i], eb); + + buffer_tree_clear_mark(eb, PAGECACHE_TAG_WRITEBACK); + clear_and_wake_up_bit(EXTENT_BUFFER_WRITEBACK, &eb->bflags); +@@ -2427,7 +2448,8 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, + struct btrfs_fs_info *fs_info = eb->fs_info; + struct btrfs_bio *bbio; + +- prepare_eb_write(eb); ++ if (!test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) ++ prepare_eb_write(eb); + + bbio = btrfs_bio_alloc(INLINE_EXTENT_BUFFER_PAGES, + REQ_OP_WRITE | REQ_META | wbc_to_write_flags(wbc), +@@ -2447,8 +2469,21 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, + btrfs_meta_folio_set_writeback(folio, eb); + if (!folio_test_dirty(folio)) + wbc->nr_to_write -= folio_nr_pages(folio); +- bio_add_folio_nofail(&bbio->bio, folio, range_len, +- offset_in_folio(folio, range_start)); ++ if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) { ++ u32 off = 0; ++ ++ while (off < range_len) { ++ u32 add = min_t(u32, PAGE_SIZE, range_len - off); ++ ++ bio_add_folio_nofail(&bbio->bio, ++ page_folio(ZERO_PAGE(0)), ++ add, 0); ++ off += add; ++ } ++ } else { ++ bio_add_folio_nofail(&bbio->bio, folio, range_len, ++ offset_in_folio(folio, range_start)); ++ } + wbc_account_cgroup_owner(wbc, folio, range_len); + folio_unlock(folio); + } +@@ -2496,6 +2531,76 @@ void btrfs_btree_wait_writeback_range(struct btrfs_fs_info *fs_info, u64 start, + } + } + ++static int write_meta_extent_buffer(struct btrfs_eb_write_context *ctx, ++ struct writeback_control *wbc) ++{ ++ struct extent_buffer *eb = ctx->eb; ++ int ret; ++ ++ ret = btrfs_check_meta_write_pointer(eb->fs_info, ctx); ++ if (ret) ++ return ret; ++ ++ if (!lock_extent_buffer_for_io(eb, wbc)) ++ return 0; ++ ++ /* Implies write in zoned mode. */ ++ if (ctx->zoned_bg) { ++ /* Mark the last eb in the block group. */ ++ btrfs_schedule_zone_finish_bg(ctx->zoned_bg, eb); ++ ctx->zoned_bg->meta_write_pointer += eb->len; ++ } ++ write_one_eb(eb, wbc); ++ return 0; ++} ++ ++/* ++ * On a zoned filesystem, write out the currently dirty metadata extent buffers ++ * of @bg. Used to flush the active metadata/system block group before the ++ * ascending-address walk in btree_writepages(), so that walk can pivot the ++ * active block group away (finishing it) instead of aborting the commit; see ++ * the caller for details. ++ */ ++static void flush_active_meta_bg(struct address_space *mapping, ++ struct writeback_control *wbc, ++ struct btrfs_eb_write_context *ctx, ++ struct btrfs_block_group *bg) ++{ ++ struct btrfs_fs_info *fs_info = inode_to_fs_info(mapping->host); ++ unsigned long index = bg->start >> fs_info->nodesize_bits; ++ unsigned long end = (btrfs_block_group_end(bg) - 1) >> fs_info->nodesize_bits; ++ struct eb_batch batch; ++ unsigned int nr_ebs; ++ ++ ASSERT(btrfs_is_zoned(fs_info)); ++ lockdep_assert_held(&fs_info->zoned_meta_io_lock); ++ ++ eb_batch_init(&batch); ++ while (index <= end && ++ (nr_ebs = buffer_tree_get_ebs_tag(fs_info, &index, end, ++ PAGECACHE_TAG_DIRTY, &batch))) { ++ struct extent_buffer *eb; ++ ++ while ((eb = eb_batch_next(&batch)) != NULL) { ++ ctx->eb = eb; ++ ++ /* ++ * If the eb is behind the write pointer (-EBUSY, e.g. ++ * already being written by someone else) skip it and ++ * carry on. Only a hole at the write pointer (-EAGAIN) ++ * stops the flush. The main walk in btree_writepages() ++ * then deals with it. ++ */ ++ if (write_meta_extent_buffer(ctx, wbc) == -EAGAIN) { ++ eb_batch_release(&batch); ++ return; ++ } ++ } ++ eb_batch_release(&batch); ++ cond_resched(); ++ } ++} ++ + int btree_writepages(struct address_space *mapping, struct writeback_control *wbc) + { + struct btrfs_eb_write_context ctx = { .wbc = wbc }; +@@ -2531,6 +2636,22 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb + else + tag = PAGECACHE_TAG_DIRTY; + btrfs_zoned_meta_io_lock(fs_info); ++ ++ /* ++ * On a zoned filesystem, flush the currently active metadata/system ++ * block group(s) first, under this same lock, so the ascending-address ++ * walk below can pivot the active block group instead of aborting the ++ * transaction commit with -EAGAIN. ++ */ ++ if (btrfs_is_zoned(fs_info) && wbc->sync_mode == WB_SYNC_ALL && ++ !wbc->for_sync) { ++ if (fs_info->active_meta_bg) ++ flush_active_meta_bg(mapping, wbc, &ctx, ++ fs_info->active_meta_bg); ++ if (fs_info->active_system_bg) ++ flush_active_meta_bg(mapping, wbc, &ctx, ++ fs_info->active_system_bg); ++ } + retry: + if (wbc->sync_mode == WB_SYNC_ALL) + buffer_tree_tag_for_writeback(fs_info, index, end); +@@ -2541,28 +2662,13 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb + while ((eb = eb_batch_next(&batch)) != NULL) { + ctx.eb = eb; + +- ret = btrfs_check_meta_write_pointer(eb->fs_info, &ctx); +- if (ret) { +- if (ret == -EBUSY) +- ret = 0; +- +- if (ret) { +- done = true; +- break; +- } +- continue; ++ ret = write_meta_extent_buffer(&ctx, wbc); ++ if (ret == -EBUSY) { ++ ret = 0; ++ } else if (ret) { ++ done = true; ++ break; + } +- +- if (!lock_extent_buffer_for_io(eb, wbc)) +- continue; +- +- /* Implies write in zoned mode. */ +- if (ctx.zoned_bg) { +- /* Mark the last eb in the block group. */ +- btrfs_schedule_zone_finish_bg(ctx.zoned_bg, eb); +- ctx.zoned_bg->meta_write_pointer += eb->len; +- } +- write_one_eb(eb, wbc); + } + nr_to_write_done = (wbc->nr_to_write <= 0); + eb_batch_release(&batch); +@@ -2888,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); +@@ -3129,47 +3247,71 @@ static inline void btrfs_release_extent_buffer(struct extent_buffer *eb) + kmem_cache_free(extent_buffer_cache, eb); + } + ++/* ++ * Claim a slot to track an extent buffer in, evicting the coldest tracked buffer ++ * when the array is full. ++ * ++ * Slots fill in order until the array is full. After that a CLOCK (second ++ * chance) scan advances the hand, clearing one reference bit per step, until ++ * it lands on an unreferenced slot whose buffer is evicted. Clearing a bit per ++ * step bounds the scan to BTRFS_INHIBITED_EBS_SLOTS iterations. ++ */ ++static int btrfs_inhibit_claim_slot(struct btrfs_trans_handle *trans) ++{ ++ int slot; ++ ++ if (trans->nr_inhibited_ebs < BTRFS_INHIBITED_EBS_SLOTS) ++ return trans->nr_inhibited_ebs++; ++ ++ while (trans->inhibited_ebs_referenced & (1U << trans->inhibited_ebs_hand)) { ++ trans->inhibited_ebs_referenced &= ~(1U << trans->inhibited_ebs_hand); ++ trans->inhibited_ebs_hand = ++ (trans->inhibited_ebs_hand + 1) % BTRFS_INHIBITED_EBS_SLOTS; ++ } ++ slot = trans->inhibited_ebs_hand; ++ trans->inhibited_ebs_hand = (trans->inhibited_ebs_hand + 1) % BTRFS_INHIBITED_EBS_SLOTS; ++ ++ atomic_dec(&trans->inhibited_ebs[slot]->writeback_inhibitors); ++ free_extent_buffer(trans->inhibited_ebs[slot]); ++ ++ return slot; ++} ++ + /* + * Inhibit writeback on buffer during transaction. + * + * @trans: transaction handle that will own the inhibitor + * @eb: extent buffer to inhibit writeback on + * +- * Attempt to track this extent buffer in the transaction's inhibited set. If +- * memory allocation fails, the buffer is simply not tracked. It may be written +- * back and need re-COW, which is the original behavior. This is acceptable +- * since inhibiting writeback is an optimization. ++ * Attempt to track this extent buffer in the transaction's inhibited set. When ++ * the set is full the coldest tracked buffer is evicted instead. An untracked ++ * buffer may be written back and need re-COW, which is the original behavior. ++ * This is acceptable since inhibiting writeback is an optimization. + */ + void btrfs_inhibit_eb_writeback(struct btrfs_trans_handle *trans, struct extent_buffer *eb) + { +- unsigned long index = eb->start >> trans->fs_info->nodesize_bits; +- void *old; ++ int slot; + + lockdep_assert_held(&eb->lock); +- /* Check if already inhibited by this handle. */ +- old = xa_load(&trans->writeback_inhibited_ebs, index); +- if (old == eb) +- return; + +- /* Take reference for the xarray entry. */ ++ /* Already tracked: set its reference bit (second chance) and return. */ ++ for (int i = 0; i < trans->nr_inhibited_ebs; i++) { ++ if (trans->inhibited_ebs[i] == eb) { ++ trans->inhibited_ebs_referenced |= 1U << i; ++ return; ++ } ++ } ++ ++ slot = btrfs_inhibit_claim_slot(trans); ++ ++ /* ++ * Pin the eb while the array holds a raw pointer to it; the counter is ++ * what lock_extent_buffer_for_io() checks. ++ */ + refcount_inc(&eb->refs); +- +- old = xa_store(&trans->writeback_inhibited_ebs, index, eb, GFP_NOFS); +- if (xa_is_err(old)) { +- /* Allocation failed, just skip inhibiting this buffer. */ +- free_extent_buffer(eb); +- return; +- } +- +- /* Handle replacement of different eb at same index. */ +- if (old && old != eb) { +- struct extent_buffer *old_eb = old; +- +- atomic_dec(&old_eb->writeback_inhibitors); +- free_extent_buffer(old_eb); +- } +- + atomic_inc(&eb->writeback_inhibitors); ++ trans->inhibited_ebs[slot] = eb; ++ trans->inhibited_ebs_referenced |= 1U << slot; + } + + /* +@@ -3177,22 +3319,18 @@ void btrfs_inhibit_eb_writeback(struct btrfs_trans_handle *trans, struct extent_ + */ + void btrfs_uninhibit_all_eb_writeback(struct btrfs_trans_handle *trans) + { +- struct extent_buffer *eb; +- unsigned long index; +- +- xa_for_each(&trans->writeback_inhibited_ebs, index, eb) { +- atomic_dec(&eb->writeback_inhibitors); +- free_extent_buffer(eb); ++ for (int i = 0; i < trans->nr_inhibited_ebs; i++) { ++ atomic_dec(&trans->inhibited_ebs[i]->writeback_inhibitors); ++ free_extent_buffer(trans->inhibited_ebs[i]); + } +- xa_destroy(&trans->writeback_inhibited_ebs); ++ trans->nr_inhibited_ebs = 0; ++ trans->inhibited_ebs_referenced = 0; ++ 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; +@@ -3205,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; + } + +@@ -3502,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) + { + +@@ -3524,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)) +@@ -3532,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; +@@ -3563,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 +@@ -3579,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; +@@ -3609,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 +@@ -3622,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++; + + /* +@@ -3758,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 +@@ -3842,12 +4063,31 @@ static int release_extent_buffer(struct extent_buffer *eb) + return 0; + } + +-void free_extent_buffer(struct extent_buffer *eb) ++static void clear_extent_buffer_reading(struct extent_buffer *eb) ++{ ++ clear_and_wake_up_bit(EXTENT_BUFFER_READING, &eb->bflags); ++} ++ ++static void free_extent_buffer_clear_reading(struct extent_buffer *eb, ++ bool clear_reading) + { + int refs; ++ + if (!eb) + return; + ++ /* ++ * We want to clear EXTENT_BUFFER_READING flag and decrease refs ++ * in the same critical section. ++ * This will make sure invalidate_and_check_btree_folios() won't ++ * see an eb with EXTENT_BUFFER_READING cleared but refs not yet ++ * decreased. ++ */ ++ if (clear_reading) { ++ spin_lock(&eb->refs_lock); ++ clear_extent_buffer_reading(eb); ++ } ++ + refs = refcount_read(&eb->refs); + while (1) { + if (test_bit(EXTENT_BUFFER_UNMAPPED, &eb->bflags)) { +@@ -3858,11 +4098,16 @@ void free_extent_buffer(struct extent_buffer *eb) + } + + /* Optimization to avoid locking eb->refs_lock. */ +- if (atomic_try_cmpxchg(&eb->refs.refs, &refs, refs - 1)) ++ if (atomic_try_cmpxchg(&eb->refs.refs, &refs, refs - 1)) { ++ if (clear_reading) ++ spin_unlock(&eb->refs_lock); + return; ++ } + } + +- spin_lock(&eb->refs_lock); ++ if (!clear_reading) ++ spin_lock(&eb->refs_lock); ++ + if (refcount_read(&eb->refs) == 2 && + test_bit(EXTENT_BUFFER_STALE, &eb->bflags) && + !extent_buffer_under_io(eb) && +@@ -3876,6 +4121,11 @@ void free_extent_buffer(struct extent_buffer *eb) + release_extent_buffer(eb); + } + ++void free_extent_buffer(struct extent_buffer *eb) ++{ ++ return free_extent_buffer_clear_reading(eb, false); ++} ++ + void free_extent_buffer_stale(struct extent_buffer *eb) + { + if (!eb) +@@ -3890,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) + { +@@ -3914,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) +@@ -4001,11 +4293,6 @@ void set_extent_buffer_uptodate(struct extent_buffer *eb) + btrfs_meta_folio_set_uptodate(eb->folios[i], eb); + } + +-static void clear_extent_buffer_reading(struct extent_buffer *eb) +-{ +- clear_and_wake_up_bit(EXTENT_BUFFER_READING, &eb->bflags); +-} +- + static void end_bbio_meta_read(struct btrfs_bio *bbio) + { + struct extent_buffer *eb = bbio->private; +@@ -4029,8 +4316,7 @@ static void end_bbio_meta_read(struct btrfs_bio *bbio) + else + clear_extent_buffer_uptodate(eb); + +- clear_extent_buffer_reading(eb); +- free_extent_buffer(eb); ++ free_extent_buffer_clear_reading(eb, true); + + bio_put(&bbio->bio); + } +@@ -4789,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; + +@@ -4797,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; + +@@ -660,7 +660,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + range_end = round_up(start + len, sectorsize); + prev_extent_end = range_start; + +- btrfs_lock_extent(&inode->io_tree, range_start, range_end, &cached_state); ++ btrfs_lock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state); + + ret = fiemap_find_last_extent_offset(inode, path, &last_extent_end); + if (ret < 0) +@@ -840,7 +840,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + } + + out_unlock: +- btrfs_unlock_extent(&inode->io_tree, range_start, range_end, &cached_state); ++ btrfs_unlock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state); + + if (ret == BTRFS_FIEMAP_FLUSH_CACHE) { + btrfs_release_path(path); +diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c +--- a/fs/btrfs/file.c ++++ b/fs/btrfs/file.c +@@ -875,70 +875,64 @@ static noinline int prepare_one_folio(struct inode *inode, struct folio **folio_ + + /* + * Locks the extent and properly waits for data=ordered extents to finish +- * before allowing the folios to be modified if need. ++ * before allowing the folios to be modified. + * + * Return: +- * 1 - the extent is locked +- * 0 - the extent is not locked, and everything is OK ++ * 0 - the extent is locked + * -EAGAIN - need to prepare the folios again + */ + static noinline int +-lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct folio *folio, +- loff_t pos, size_t write_bytes, +- u64 *lockstart, u64 *lockend, bool nowait, +- struct extent_state **cached_state) ++lock_and_cleanup_extent(struct btrfs_inode *inode, struct folio *folio, ++ loff_t pos, size_t write_bytes, ++ u64 *lockstart, u64 *lockend, bool nowait, ++ struct extent_state **cached_state) + { + struct btrfs_fs_info *fs_info = inode->root->fs_info; ++ struct btrfs_ordered_extent *ordered; + u64 start_pos; + u64 last_pos; +- int ret = 0; + + start_pos = round_down(pos, fs_info->sectorsize); + last_pos = round_up(pos + write_bytes, fs_info->sectorsize) - 1; + +- if (start_pos < inode->vfs_inode.i_size) { +- struct btrfs_ordered_extent *ordered; +- +- if (nowait) { +- if (!btrfs_try_lock_extent(&inode->io_tree, start_pos, +- last_pos, cached_state)) { +- folio_unlock(folio); +- folio_put(folio); +- return -EAGAIN; +- } +- } else { +- btrfs_lock_extent(&inode->io_tree, start_pos, last_pos, +- cached_state); +- } +- +- ordered = btrfs_lookup_ordered_range(inode, start_pos, +- last_pos - start_pos + 1); +- if (ordered && +- ordered->file_offset + ordered->num_bytes > start_pos && +- ordered->file_offset <= last_pos) { +- btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos, +- cached_state); ++ if (nowait) { ++ if (!btrfs_try_lock_extent(&inode->io_tree, start_pos, ++ last_pos, cached_state)) { + folio_unlock(folio); + folio_put(folio); +- btrfs_start_ordered_extent(ordered); +- btrfs_put_ordered_extent(ordered); + return -EAGAIN; + } +- if (ordered) +- btrfs_put_ordered_extent(ordered); +- +- *lockstart = start_pos; +- *lockend = last_pos; +- ret = 1; ++ } else { ++ btrfs_lock_extent(&inode->io_tree, start_pos, last_pos, ++ cached_state); + } + ++ ordered = btrfs_lookup_ordered_range(inode, start_pos, ++ last_pos - start_pos + 1); ++ if (ordered && ++ ordered->file_offset + ordered->num_bytes > start_pos && ++ ordered->file_offset <= last_pos) { ++ btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos, ++ cached_state); ++ folio_unlock(folio); ++ folio_put(folio); ++ btrfs_start_ordered_extent(ordered); ++ btrfs_put_ordered_extent(ordered); ++ return -EAGAIN; ++ } ++ if (ordered) ++ btrfs_put_ordered_extent(ordered); ++ ++ *lockstart = start_pos; ++ *lockend = last_pos; ++ + /* + * We should be called after prepare_one_folio() which should have locked + * all pages in the range. + */ + WARN_ON(!folio_test_locked(folio)); + +- return ret; ++ return 0; + } + + /* +@@ -1195,7 +1189,6 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + const u64 reserved_start = round_down(start, fs_info->sectorsize); + u64 reserved_len; + struct folio *folio = NULL; +- int extents_locked; + u64 lockstart; + u64 lockend; + bool only_release_metadata = false; +@@ -1253,18 +1246,16 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + reserved_len = last_block - reserved_start; + } + +- extents_locked = lock_and_cleanup_extent_if_need(inode, folio, start, +- write_bytes, &lockstart, +- &lockend, nowait, +- &cached_state); +- if (extents_locked < 0) { +- if (!nowait && extents_locked == -EAGAIN) ++ ret = lock_and_cleanup_extent(inode, folio, start, write_bytes, ++ &lockstart, &lockend, nowait, &cached_state); ++ if (ret < 0) { ++ if (!nowait) + goto again; + + btrfs_delalloc_release_extents(inode, reserved_len); + release_space(inode, *data_reserved, reserved_start, reserved_len, + only_release_metadata); +- return extents_locked; ++ return ret; + } + + copied = copy_folio_from_iter_atomic(folio, offset_in_folio(folio, start), +@@ -1288,11 +1279,8 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + + /* No copied bytes, unlock, release reserved space and exit. */ + if (copied == 0) { +- if (extents_locked) +- btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, +- &cached_state); +- else +- btrfs_free_extent_state(cached_state); ++ btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, ++ &cached_state); + btrfs_delalloc_release_extents(inode, reserved_len); + release_space(inode, *data_reserved, reserved_start, reserved_len, + only_release_metadata); +@@ -1311,17 +1299,7 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + + ret = btrfs_dirty_folio(inode, folio, start, copied, &cached_state, + only_release_metadata); +- /* +- * If we have not locked the extent range, because the range's start +- * offset is >= i_size, we might still have a non-NULL cached extent +- * state, acquired while marking the extent range as delalloc through +- * btrfs_dirty_page(). Therefore free any possible cached extent state +- * to avoid a memory leak. +- */ +- if (extents_locked) +- btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state); +- else +- btrfs_free_extent_state(cached_state); ++ btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state); + + btrfs_delalloc_release_extents(inode, reserved_len); + if (ret) { +@@ -1595,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 +@@ -1679,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 +@@ -2683,8 +2657,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. +@@ -2888,7 +2862,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; + +@@ -2918,7 +2892,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); +@@ -2967,8 +2941,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; + } + +@@ -3050,7 +3051,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; +@@ -3907,7 +3908,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; + } + +@@ -4210,8 +4211,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)); + } + + /* +@@ -6825,8 +6826,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)); + } + + /* +@@ -10205,6 +10206,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); +@@ -10214,15 +10217,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 { +@@ -10300,8 +10312,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 +@@ -10551,6 +10565,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 && +@@ -10598,6 +10615,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)); + } + } + +@@ -4339,12 +4339,13 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode, + struct ulist_node *unode; + struct ulist_iterator uiter; + struct extent_changeset changeset; ++ const u32 sectorsize = root->fs_info->sectorsize; ++ const u64 aligned_start = round_down(start, sectorsize); ++ const u64 aligned_len = round_up(start + len, sectorsize) - aligned_start; + u64 freed = 0; + int ret; + + extent_changeset_init_bytes_only(&changeset); +- len = round_up(start + len, root->fs_info->sectorsize); +- start = round_down(start, root->fs_info->sectorsize); + + ULIST_ITER_INIT(&uiter); + while ((unode = ulist_next(&reserved->range_changed, &uiter))) { +@@ -4356,12 +4357,15 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode, + + extent_changeset_release(&changeset); + +- /* Only free range in range [start, start + len) */ +- if (range_start >= start + len || +- range_start + range_len <= start) ++ /* ++ * Only free the range within ++ * [aligned_start, aligned_start + aligned_len). ++ */ ++ if (range_start >= aligned_start + aligned_len || ++ range_start + range_len <= aligned_start) + continue; +- free_start = max(range_start, start); +- free_len = min(start + len, range_start + range_len) - ++ free_start = max(range_start, aligned_start); ++ free_len = min(aligned_start + aligned_len, range_start + range_len) - + free_start; + /* + * TODO: To also modify reserved->ranges_reserved to reflect +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; + } + +@@ -1537,6 +1538,33 @@ static void clear_reloc_root(struct btrfs_root *root) + clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state); + } + ++/* Drop the reloc trees of a relocation that is being deferred and retried. */ ++static void abort_reloc_roots(struct reloc_control *rc, struct list_head *list) ++{ ++ struct btrfs_fs_info *fs_info = rc->extent_root->fs_info; ++ struct btrfs_root *reloc_root, *tmp; ++ ++ list_for_each_entry_safe(reloc_root, tmp, list, root_list) { ++ struct btrfs_root *root; ++ ++ root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset, false); ++ if (!IS_ERR(root)) { ++ if (root->reloc_root == reloc_root) { ++ clear_reloc_root(root); ++ btrfs_put_root(reloc_root); ++ } ++ btrfs_put_root(root); ++ } ++ ++ btrfs_set_root_refs(&reloc_root->root_item, 0); ++ memset(&reloc_root->root_item.drop_progress, 0, sizeof(struct btrfs_disk_key)); ++ btrfs_set_root_drop_level(&reloc_root->root_item, 0); ++ ++ list_del_init(&reloc_root->root_list); ++ list_add_tail(&reloc_root->reloc_dirty_list, &rc->dirty_subvol_roots); ++ } ++} ++ + static int clean_dirty_subvols(struct reloc_control *rc) + { + struct btrfs_root *root; +@@ -1876,8 +1904,7 @@ int prepare_to_merge(struct reloc_control *rc, int err) + return err; + } + +-static noinline_for_stack +-void merge_reloc_roots(struct reloc_control *rc) ++static noinline_for_stack int merge_reloc_roots(struct reloc_control *rc) + { + struct btrfs_fs_info *fs_info = rc->extent_root->fs_info; + struct btrfs_root *root; +@@ -1975,7 +2002,15 @@ void merge_reloc_roots(struct reloc_control *rc) + goto again; + } + out: +- if (ret) { ++ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) { ++ abort_reloc_roots(rc, &reloc_roots); ++ ++ /* New reloc root may be added. */ ++ mutex_lock(&fs_info->reloc_mutex); ++ list_splice_init(&rc->reloc_roots, &reloc_roots); ++ mutex_unlock(&fs_info->reloc_mutex); ++ abort_reloc_roots(rc, &reloc_roots); ++ } else if (ret) { + btrfs_handle_fs_error(fs_info, ret, NULL); + free_reloc_roots(&reloc_roots); + +@@ -2001,6 +2036,7 @@ void merge_reloc_roots(struct reloc_control *rc) + * + * The remaining nodes will be cleaned up by put_reloc_control(). + */ ++ return ret; + } + + static void free_block_list(struct rb_root *blocks) +@@ -3730,7 +3766,9 @@ static noinline_for_stack int relocate_block_group(struct reloc_control *rc) + */ + err = prepare_to_merge(rc, err); + +- merge_reloc_roots(rc); ++ ret = merge_reloc_roots(rc); ++ if (ret && !err) ++ err = ret; + + rc->merge_reloc_tree = false; + unset_reloc_control(rc); +@@ -4114,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) +@@ -5720,7 +5758,9 @@ int btrfs_recover_relocation(struct btrfs_fs_info *fs_info) + goto out_drop_reloc_refs; + release_recovered_fs_roots(&recovered_roots, false); + +- merge_reloc_roots(rc); ++ ret = merge_reloc_roots(rc); ++ if (ret) ++ goto out_unset; + + unset_reloc_control(rc); + +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); + +@@ -1245,20 +1227,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); +@@ -1270,7 +1241,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) { +@@ -1349,7 +1320,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); +@@ -1844,7 +1815,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); + } + +@@ -1869,7 +1840,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; + +@@ -1882,7 +1852,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++) +@@ -2138,7 +2108,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); +@@ -3091,14 +3061,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 +@@ -698,8 +698,6 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, + goto alloc_fail; + } + +- xa_init(&h->writeback_inhibited_ebs); +- + /* + * If we are JOIN_NOLOCK we're already committing a transaction and + * waiting on this guy, so we don't need to do the sb_start_intwrite +@@ -1519,12 +1517,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), +@@ -1642,7 +1636,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; + } + +@@ -2588,7 +2582,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)); + mutex_unlock(&fs_info->tree_log_mutex); + goto scrub_continue; + } +@@ -2749,8 +2743,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/transaction.h b/fs/btrfs/transaction.h +--- a/fs/btrfs/transaction.h ++++ b/fs/btrfs/transaction.h +@@ -7,12 +7,12 @@ + #define BTRFS_TRANSACTION_H + + #include ++#include + #include + #include + #include + #include + #include +-#include + #include "btrfs_inode.h" + #include "delayed-ref.h" + +@@ -23,6 +23,7 @@ struct btrfs_fs_info; + struct btrfs_root_item; + struct btrfs_root; + struct btrfs_path; ++struct extent_buffer; + + /* + * Signal that a direct IO write is in progress, to avoid deadlock for sync +@@ -136,6 +137,18 @@ enum { + + #define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH) + ++/* ++ * Number of extent buffers a transaction handle tracks for writeback ++ * inhibition. The CLOCK reference bits pack into a u32 so this must not exceed ++ * 32, and keeping it a power of two lets the compiler reduce the CLOCK hand ++ * modulo to a mask. ++ */ ++#define BTRFS_INHIBITED_EBS_SLOTS 8 ++ ++static_assert(BTRFS_INHIBITED_EBS_SLOTS <= 32); ++static_assert(BTRFS_INHIBITED_EBS_SLOTS != 0 && ++ (BTRFS_INHIBITED_EBS_SLOTS & (BTRFS_INHIBITED_EBS_SLOTS - 1)) == 0); ++ + struct btrfs_trans_handle { + u64 transid; + u64 bytes_reserved; +@@ -163,8 +176,14 @@ struct btrfs_trans_handle { + struct btrfs_fs_info *fs_info; + struct list_head new_bgs; + struct btrfs_block_rsv delayed_rsv; +- /* Extent buffers with writeback inhibited by this handle. */ +- struct xarray writeback_inhibited_ebs; ++ ++ /* Extent buffers this handle has inhibited writeback on. */ ++ struct extent_buffer *inhibited_ebs[BTRFS_INHIBITED_EBS_SLOTS]; ++ /* CLOCK reference bit per slot. */ ++ u32 inhibited_ebs_referenced; ++ u32 nr_inhibited_ebs; ++ /* CLOCK hand. */ ++ u32 inhibited_ebs_hand; + }; + + /* +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 +@@ -638,7 +638,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; + } + +@@ -720,14 +720,18 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + goto out; + + folio_lock(folio); +- /* If it's not uptodate after we have the lock, we got a read error. */ +- if (!folio_test_uptodate(folio)) { ++ /* Folio was truncated from mapping. */ ++ if (!folio->mapping) { + folio_unlock(folio); + folio_put(folio); +- return ERR_PTR(-EIO); ++ goto again; + } +- folio_unlock(folio); +- goto out; ++ /* Another reader may have filled the folio while we waited. */ ++ if (folio_test_uptodate(folio)) { ++ folio_unlock(folio); ++ goto out; ++ } ++ goto read_folio; + } + + folio = filemap_alloc_folio(mapping_gfp_constraint(inode->i_mapping, ~__GFP_FS), +@@ -744,6 +748,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + return ERR_PTR(ret); + } + ++read_folio: + /* + * Merkle item keys are indexed from byte 0 in the merkle tree. + * They have the form: +@@ -753,6 +758,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + ret = read_key_bytes(BTRFS_I(inode), BTRFS_VERITY_MERKLE_ITEM_KEY, off, + folio_address(folio), PAGE_SIZE, folio); + if (ret < 0) { ++ folio_unlock(folio); + folio_put(folio); + return ERR_PTR(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; + + /* +@@ -4588,7 +4553,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; +@@ -6049,6 +6014,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) +@@ -6067,11 +6045,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++) { +@@ -6250,7 +6227,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); +@@ -7632,9 +7609,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 +@@ -7645,6 +7620,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-bore/0300-btrfs.patch.sig b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch.sig new file mode 100644 index 0000000..1586085 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch new file mode 100644 index 0000000..c5a4318 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch @@ -0,0 +1,386 @@ +diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c +--- a/fs/btrfs/dev-replace.c ++++ b/fs/btrfs/dev-replace.c +@@ -626,7 +626,7 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, + + ret = mark_block_group_to_copy(fs_info, src_device); + if (ret) +- return ret; ++ goto leave; + + down_write(&dev_replace->rwsem); + dev_replace->replace_task = current; +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -3436,6 +3436,9 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent) + */ + btrfs_remove_ordered_extent(ordered_extent); + ++ /* Cleanup any remaining biocs attached to the OE. */ ++ btrfs_cleanup_ordered_bioc_list(ordered_extent); ++ + /* once for us */ + btrfs_put_ordered_extent(ordered_extent); + /* once for the tree */ +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/raid-stripe-tree.c b/fs/btrfs/raid-stripe-tree.c +--- a/fs/btrfs/raid-stripe-tree.c ++++ b/fs/btrfs/raid-stripe-tree.c +@@ -310,8 +310,10 @@ static int update_raid_extent_item(struct btrfs_trans_handle *trans, + + ret = btrfs_search_slot(trans, trans->fs_info->stripe_root, key, path, + 0, 1); +- if (ret) +- return (ret == 1 ? ret : -EINVAL); ++ if (ret > 0) ++ ret = -ENOENT; ++ if (ret < 0) ++ return ret; + + leaf = path->nodes[0]; + slot = path->slots[0]; +@@ -337,7 +339,6 @@ int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, + stripe_extent = kzalloc(item_size, GFP_NOFS); + if (unlikely(!stripe_extent)) { + btrfs_abort_transaction(trans, -ENOMEM); +- btrfs_end_transaction(trans); + return -ENOMEM; + } + +@@ -374,7 +375,7 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + struct btrfs_ordered_extent *ordered_extent) + { + struct btrfs_io_context *bioc; +- int ret; ++ int ret = 0; + + if (!btrfs_fs_incompat(trans->fs_info, RAID_STRIPE_TREE)) + return 0; +@@ -382,17 +383,23 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + list_for_each_entry(bioc, &ordered_extent->bioc_list, rst_ordered_entry) { + ret = btrfs_insert_one_raid_extent(trans, bioc); + if (ret) +- return ret; ++ break; + } + +- while (!list_empty(&ordered_extent->bioc_list)) { +- bioc = list_first_entry(&ordered_extent->bioc_list, ++ btrfs_cleanup_ordered_bioc_list(ordered_extent); ++ return ret; ++} ++ ++void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered) ++{ ++ while (!list_empty(&ordered->bioc_list)) { ++ struct btrfs_io_context *bioc; ++ ++ bioc = list_first_entry(&ordered->bioc_list, + typeof(*bioc), rst_ordered_entry); + list_del(&bioc->rst_ordered_entry); + btrfs_put_bioc(bioc); + } +- +- return 0; + } + + int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, +diff --git a/fs/btrfs/raid-stripe-tree.h b/fs/btrfs/raid-stripe-tree.h +--- a/fs/btrfs/raid-stripe-tree.h ++++ b/fs/btrfs/raid-stripe-tree.h +@@ -28,6 +28,7 @@ int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, + u32 stripe_index, struct btrfs_io_stripe *stripe); + int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + struct btrfs_ordered_extent *ordered_extent); ++void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered); + + #ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS + int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, +diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c +--- a/fs/btrfs/scrub.c ++++ b/fs/btrfs/scrub.c +@@ -1023,6 +1023,10 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + + skip: + for_each_set_bit(sector_nr, &extent_bitmap, stripe->nr_sectors) { ++ const u64 sector_logical = stripe->logical + ++ ((u64)sector_nr << fs_info->sectorsize_bits); ++ const u64 sector_physical = physical + ++ ((u64)sector_nr << fs_info->sectorsize_bits); + bool repaired = false; + + if (scrub_bitmap_test_bit_is_metadata(stripe, sector_nr)) { +@@ -1051,12 +1055,12 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + if (dev) { + btrfs_err_rl(fs_info, + "scrub: fixed up error at logical %llu on dev %s physical %llu", +- stripe->logical, btrfs_dev_name(dev), +- physical); ++ sector_logical, btrfs_dev_name(dev), ++ sector_physical); + } else { + btrfs_err_rl(fs_info, + "scrub: fixed up error at logical %llu on mirror %u", +- stripe->logical, stripe->mirror_num); ++ sector_logical, stripe->mirror_num); + } + continue; + } +@@ -1065,30 +1069,30 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + if (dev) { + btrfs_err_rl(fs_info, + "scrub: unable to fixup (regular) error at logical %llu on dev %s physical %llu", +- stripe->logical, btrfs_dev_name(dev), +- physical); ++ sector_logical, btrfs_dev_name(dev), ++ sector_physical); + } else { + btrfs_err_rl(fs_info, + "scrub: unable to fixup (regular) error at logical %llu on mirror %u", +- stripe->logical, stripe->mirror_num); ++ sector_logical, stripe->mirror_num); + } + + if (scrub_bitmap_test_bit_io_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("i/o error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_csum_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("checksum error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_meta_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("header error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_meta_gen_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("generation error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + } + + /* Update the device stats. */ +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -2065,7 +2065,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen, + + ret = is_inode_existent(sctx, dir, dir_gen, NULL, &parent_root_dir_gen); + if (ret <= 0) +- return 0; ++ return ret; + + /* + * If we have a parent root we need to verify that the parent dir was +@@ -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/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c +--- a/fs/btrfs/tests/extent-io-tests.c ++++ b/fs/btrfs/tests/extent-io-tests.c +@@ -133,14 +133,14 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) + if (IS_ERR(root)) { + test_std_err(TEST_ALLOC_ROOT); + ret = PTR_ERR(root); +- goto out; ++ goto out_root_info; + } + + inode = btrfs_new_test_inode(); + if (!inode) { + test_std_err(TEST_ALLOC_INODE); + ret = -ENOMEM; +- goto out; ++ goto out_root_info; + } + tmp = &BTRFS_I(inode)->io_tree; + BTRFS_I(inode)->root = root; +@@ -333,6 +333,7 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) + process_page_range(inode, 0, total_dirty - 1, + PROCESS_UNLOCK | PROCESS_RELEASE); + iput(inode); ++out_root_info: + btrfs_free_dummy_root(root); + btrfs_free_dummy_fs_info(fs_info); + return ret; +diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c +--- a/fs/btrfs/transaction.c ++++ b/fs/btrfs/transaction.c +@@ -458,8 +458,19 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, + * through btrfs_record_root_in_trans without having to take the + * lock. smp_wmb() makes sure that all the writes above are + * done before we pop in the zero below ++ * ++ * If @force is true, it means the call is from ++ * qgroup_account_snapshot(), which only requires radix tree ++ * tracking. ++ * We should not force reloc root creation here, as the root ++ * may have already been modified, and in that case ++ * root->commit_root has already been dropped. ++ * ++ * Using that commit root will cause the reloc root to refer ++ * to a deleted extent, causing extent tree corruption. + */ +- ret = btrfs_init_reloc_root(trans, root); ++ if (!force) ++ ret = btrfs_init_reloc_root(trans, root); + smp_mb__before_atomic(); + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state); + } +@@ -2583,6 +2594,12 @@ 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: %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 ++ * for this failed transaction. See btrfs_sync_log(). ++ */ ++ btrfs_abort_transaction(trans, ret); + mutex_unlock(&fs_info->tree_log_mutex); + goto scrub_continue; + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -3035,7 +3035,11 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path + error_sysfs: + btrfs_sysfs_remove_device(device); + mutex_lock(&fs_info->fs_devices->device_list_mutex); ++ if (seeding_dev) ++ btrfs_assign_next_active_device(device, seed_devices->latest_dev); + mutex_lock(&fs_info->chunk_mutex); ++ if (!list_empty(&device->post_commit_list)) ++ list_del_init(&device->post_commit_list); + list_del_rcu(&device->dev_list); + list_del(&device->dev_alloc_list); + fs_info->fs_devices->num_devices--; +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2626,16 +2626,13 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ + down_read(&dev_replace->rwsem); + map = block_group->physical_map; + for (i = 0; i < map->num_stripes; i++) { +- + ret = call_zone_finish(block_group, &map->stripes[i]); +- if (ret) { +- up_read(&dev_replace->rwsem); +- return ret; +- } ++ if (ret) ++ break; + } + up_read(&dev_replace->rwsem); + +- if (!fully_written) ++ if (!ret && !fully_written) + btrfs_dec_block_group_ro(block_group); + + spin_lock(&fs_info->zone_active_bgs_lock); +@@ -2648,7 +2645,7 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ + + clear_and_wake_up_bit(BTRFS_FS_NEED_ZONE_FINISH, &fs_info->flags); + +- return 0; ++ return ret; + } + + int btrfs_zone_finish(struct btrfs_block_group *block_group) +@@ -2713,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; +@@ -2732,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/fs/btrfs/zstd.c b/fs/btrfs/zstd.c +--- a/fs/btrfs/zstd.c ++++ b/fs/btrfs/zstd.c +@@ -307,8 +307,17 @@ struct list_head *zstd_get_workspace(struct btrfs_fs_info *fs_info, int level) + DEFINE_WAIT(wait); + + prepare_to_wait(&zwsm->wait, &wait, TASK_UNINTERRUPTIBLE); +- schedule(); ++ /* ++ * Re-check after being queued: zstd_put_workspace() only wakes ++ * a queue that already has a sleeper, so a workspace returned ++ * since the failed allocation woke nobody. ++ */ ++ ws = zstd_find_workspace(fs_info, level); ++ if (!ws) ++ schedule(); + finish_wait(&zwsm->wait, &wait); ++ if (ws) ++ return ws; + + goto again; + } diff --git a/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch.sig new file mode 100644 index 0000000..b319318 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0302-btrfs-zstd-decompress-direct-to-page.patch b/pkgbuilds/linux-omarchy-bore/0302-btrfs-zstd-decompress-direct-to-page.patch new file mode 100644 index 0000000..d949f95 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0302-btrfs-zstd-decompress-direct-to-page.patch.sig b/pkgbuilds/linux-omarchy-bore/0302-btrfs-zstd-decompress-direct-to-page.patch.sig new file mode 100644 index 0000000..a75cf51 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0302-btrfs-zstd-decompress-direct-to-page.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch b/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch new file mode 100644 index 0000000..b34a909 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch @@ -0,0 +1,42 @@ +diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c +--- a/fs/fuse/dir.c ++++ b/fs/fuse/dir.c +@@ -2280,6 +2280,9 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + */ + if ((is_truncate || !is_wb) && + S_ISREG(inode->i_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); + +@@ -2908,6 +2912,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-bore/0310-fuse-eof-zeroing.patch.sig b/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch.sig new file mode 100644 index 0000000..dc63cd6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch new file mode 100644 index 0000000..5162077 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch @@ -0,0 +1,104 @@ +diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c +--- a/fs/fuse/dev.c ++++ b/fs/fuse/dev.c +@@ -216,10 +216,13 @@ EXPORT_SYMBOL_GPL(fuse_req_hash); + /* + * A new request is available, wake fiq->waitq + */ +-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); + } +@@ -236,7 +239,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); +@@ -258,7 +261,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); +@@ -288,11 +291,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; +@@ -755,6 +760,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); +@@ -1809,7 +1819,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 +@@ -1417,6 +1417,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-bore/0311-fuse-perf.patch.sig b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch.sig new file mode 100644 index 0000000..ff6a782 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0312-fuse-writethrough-uptodate.patch b/pkgbuilds/linux-omarchy-bore/0312-fuse-writethrough-uptodate.patch new file mode 100644 index 0000000..8aaf4c6 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0312-fuse-writethrough-uptodate.patch.sig b/pkgbuilds/linux-omarchy-bore/0312-fuse-writethrough-uptodate.patch.sig new file mode 100644 index 0000000..bd3d135 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0312-fuse-writethrough-uptodate.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch b/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch new file mode 100644 index 0000000..77e750a --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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 +@@ -409,7 +409,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-bore/0313-fuse-background-wakeup.patch.sig b/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch.sig new file mode 100644 index 0000000..8ec069b Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch b/pkgbuilds/linux-omarchy-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch new file mode 100644 index 0000000..9a8f97f --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch @@ -0,0 +1,123 @@ +diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h +index 4590d6a3d821..dbdc2053332d 100644 +--- a/drivers/gpu/drm/drm_displayid_internal.h ++++ b/drivers/gpu/drm/drm_displayid_internal.h +@@ -67,6 +67,7 @@ struct drm_edid; + #define DATA_BLOCK_2_TILED_DISPLAY_TOPOLOGY 0x28 + #define DATA_BLOCK_2_CONTAINER_ID 0x29 + #define DATA_BLOCK_2_TYPE_10_FORMULA_TIMING 0x2a ++#define DATA_BLOCK_2_ADAPTIVE_SYNC 0x2b + #define DATA_BLOCK_2_VENDOR_SPECIFIC 0x7e + #define DATA_BLOCK_2_CTA_DISPLAY_ID 0x81 + +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +index df3c25bac761..354d4ff1aff7 100644 +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -6598,6 +6598,49 @@ static void drm_get_monitor_range(struct drm_connector *connector, + info->monitor_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-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig b/pkgbuilds/linux-omarchy-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig new file mode 100644 index 0000000..a00bf83 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0360-gpu-mem-cgroup.patch b/pkgbuilds/linux-omarchy-bore/0360-gpu-mem-cgroup.patch new file mode 100644 index 0000000..278fc0c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0360-gpu-mem-cgroup.patch @@ -0,0 +1,601 @@ +diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c +--- a/drivers/gpu/drm/nouveau/nouveau_ttm.c ++++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c +@@ -188,6 +188,11 @@ nouveau_ttm_init_vram(struct nouveau_drm *drm) + + man->func = &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-bore/0360-gpu-mem-cgroup.patch.sig b/pkgbuilds/linux-omarchy-bore/0360-gpu-mem-cgroup.patch.sig new file mode 100644 index 0000000..6e989c6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0360-gpu-mem-cgroup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch b/pkgbuilds/linux-omarchy-bore/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch new file mode 100644 index 0000000..32f3010 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig b/pkgbuilds/linux-omarchy-bore/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig new file mode 100644 index 0000000..80b2f7b Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch b/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch new file mode 100644 index 0000000..28a6330 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch @@ -0,0 +1,196 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +index c21e0c0ef0b1..e62788328bfd 100644 +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -1780,6 +1780,7 @@ struct intel_psr { + u32 dc3co_exitline; + u32 dc3co_exit_delay; + struct delayed_work dc3co_work; ++ struct delayed_work panel_replay_reenable_work; + u8 entry_setup_frames; + + u8 io_wake_lines; +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +index beaa1d62613d..644ab9b1bee9 100644 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2475,6 +2475,7 @@ void intel_psr_disable(struct intel_dp *intel_dp, + mutex_unlock(&intel_dp->psr.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); + } + + /** +@@ -2506,6 +2507,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); + } + + /** +@@ -3539,6 +3541,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_alpm_cursor_lag_workaround_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_ALPM_CURSOR_LAG); ++} ++ + static void intel_psr_work(struct work_struct *work) + { + struct intel_dp *intel_dp = +@@ -3557,6 +3570,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 workaround is armed. */ ++ if (panel_replay_alpm_cursor_lag_workaround_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. +@@ -3566,6 +3584,10 @@ static void intel_psr_work(struct work_struct *work) + if (!__psr_wait_for_idle_locked(intel_dp)) + goto unlock; + ++ if (panel_replay_alpm_cursor_lag_workaround_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 +@@ -3579,6 +3601,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_alpm_cursor_lag_workaround_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); +@@ -3650,8 +3699,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_alpm_cursor_lag_workaround_enabled(intel_dp)) ++ cancel_delayed_work(&intel_dp->psr.panel_replay_reenable_work); + _psr_invalidate_handle(intel_dp); ++ } + + mutex_unlock(&intel_dp->psr.lock); + } +@@ -3767,6 +3819,15 @@ void intel_psr_flush(struct intel_display *display, + if (intel_dp->psr.pause_counter) + goto unlock; + ++ if (pipe_frontbuffer_bits && ++ panel_replay_alpm_cursor_lag_workaround_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)) { +@@ -3830,6 +3891,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 +index 33245f44c0d5..dc6576016400 100644 +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -86,12 +86,13 @@ static void quirk_edp_limit_rate_hbr2(struct intel_display *display) + drm_info(display->drm, "Applying eDP Limit rate to HBR2 quirk\n"); + } + +-static void quirk_disable_edp_panel_replay(struct intel_dp *intel_dp) ++static void quirk_panel_replay_alpm_cursor_lag(struct intel_dp *intel_dp) + { + struct intel_display *display = to_intel_display(intel_dp); + +- intel_set_dpcd_quirk(intel_dp, QUIRK_DISABLE_EDP_PANEL_REPLAY); +- drm_info(display->drm, "Applying disable Panel Replay quirk\n"); ++ intel_set_dpcd_quirk(intel_dp, QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG); ++ drm_info(display->drm, ++ "Applying Panel Replay ALPM cursor lag workaround\n"); + } + + static void quirk_disable_psr2(struct intel_display *display) +@@ -276,7 +277,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_alpm_cursor_lag, + }, + /* Dell XPS 16 DA16260 */ + { +@@ -284,7 +285,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_alpm_cursor_lag, + }, + }; + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h b/drivers/gpu/drm/i915/display/intel_quirks.h +index 970a4fe52faf..50ef333b87a9 100644 +--- 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_ALPM_CURSOR_LAG, + }; + + void intel_init_quirks(struct intel_display *display); diff --git a/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch.sig b/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch.sig new file mode 100644 index 0000000..6e91496 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch b/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch new file mode 100644 index 0000000..3e88cea --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch @@ -0,0 +1,20 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +index beaa1d626..5fdf4594c 100644 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -714,8 +714,14 @@ static void _psr_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *co + * To support PSR version 02h and PSR version 03h without + * Y-coordinate requirement panels we would need to enable + * GTC first. ++ * ++ * Early Transport (version 04h) implies Y-coordinate ++ * support. Accept it without explicit Y-coordinate ++ * requirement bit. + */ +- connector->dp.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-bore/0402-psr2-early-transport-panels.patch.sig b/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch.sig new file mode 100644 index 0000000..ff5430c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0411-drm-xe-display-no-stolen-framebuffers.patch b/pkgbuilds/linux-omarchy-bore/0411-drm-xe-display-no-stolen-framebuffers.patch new file mode 100644 index 0000000..c1f3ad5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0411-drm-xe-display-no-stolen-framebuffers.patch.sig b/pkgbuilds/linux-omarchy-bore/0411-drm-xe-display-no-stolen-framebuffers.patch.sig new file mode 100644 index 0000000..67e82c0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0411-drm-xe-display-no-stolen-framebuffers.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch b/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch new file mode 100644 index 0000000..21017c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch @@ -0,0 +1,23 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c +--- a/drivers/gpu/drm/i915/display/intel_display.c ++++ b/drivers/gpu/drm/i915/display/intel_display.c +@@ -2452,6 +2452,19 @@ static int intel_crtc_set_context_latency(struct intel_crtc_state *crtc_state) + set_context_latency = max(set_context_latency, + intel_psr_min_set_context_latency(crtc_state)); + ++ /* ++ * From PTL onwards, the set context latency can be in the vactive ++ * region, letting the safe window start some lines before the vblank ++ * start. With modes that have a smaller vblank region, the computed ++ * guardband is clamped to the vblank length, making the undelayed and ++ * delayed vblank coincide. If the SCL is also 0, the 'safe window' ++ * becomes effectively 0, and the DSB configured to wait for it gets ++ * stalled, since the hardware never signals the safe window. Keep the ++ * set context latency at a minimum of 1 to avoid this. ++ */ ++ if (DISPLAY_VER(display) >= 30) ++ set_context_latency = max(1, set_context_latency); ++ + return set_context_latency; + } + diff --git a/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch.sig b/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch.sig new file mode 100644 index 0000000..33f021c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0420-safe-window.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0430-fbc.patch b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch new file mode 100644 index 0000000..4548866 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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 +@@ -1937,10 +1937,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); +@@ -1948,10 +1948,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-bore/0430-fbc.patch.sig b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch.sig new file mode 100644 index 0000000..f0db1f3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0440-xe3-peak-bandwidth.patch b/pkgbuilds/linux-omarchy-bore/0440-xe3-peak-bandwidth.patch new file mode 100644 index 0000000..a13b165 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0440-xe3-peak-bandwidth.patch.sig b/pkgbuilds/linux-omarchy-bore/0440-xe3-peak-bandwidth.patch.sig new file mode 100644 index 0000000..99d5c08 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0440-xe3-peak-bandwidth.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch b/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch new file mode 100644 index 0000000..ebe9a36 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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" + +@@ -10068,6 +10069,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, +@@ -10079,6 +10083,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, +@@ -10645,9 +10679,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; ++ } + } + } + +@@ -12049,6 +12086,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( +@@ -14011,6 +14056,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 */ +@@ -14036,7 +14090,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; +@@ -14052,6 +14108,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) +@@ -14093,6 +14202,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-bore/0450-amd-hdmi-vrr-allm.patch.sig b/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch.sig new file mode 100644 index 0000000..ee35537 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch b/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch new file mode 100644 index 0000000..f61c400 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch @@ -0,0 +1,15 @@ +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 +@@ -10069,7 +10069,10 @@ static void update_freesync_state_on_stream( + &vrr_infopacket, + pack_sdp_v1_3); + +- if (new_stream->sink->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-bore/0451-amd-vtem-tmds-links.patch.sig b/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch.sig new file mode 100644 index 0000000..7077a0e Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch b/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch new file mode 100644 index 0000000..e392ccc --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +index 1aed121f4ddb..081de37c2083 100644 +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -216,8 +216,9 @@ int amdgpu_smu_pptable_id = -1; + * DISABLE_FRACTIONAL_PWM (bit 2) disabled by default + * PSR (bit 3) disabled by default + * EDP NO POWER SEQUENCING (bit 4) disabled by default ++ * FRL (bit 10) enabled by default + */ +-uint amdgpu_dc_feature_mask = 2; ++uint amdgpu_dc_feature_mask = DC_MULTI_MON_PP_MCLK_SWITCH_MASK | DC_FRL_MASK; + uint amdgpu_dc_debug_mask; + uint amdgpu_dc_visual_confirm; + int amdgpu_async_gfx_ring = 1; +diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h +index 3fd38323a88b..bdd80c5e378e 100644 +--- a/drivers/gpu/drm/amd/include/amd_shared.h ++++ b/drivers/gpu/drm/amd/include/amd_shared.h +@@ -287,7 +287,7 @@ enum DC_FEATURE_MASK { + */ + DC_REPLAY_MASK = (1 << 9), + /** +- * @DC_FRL_MASK: (0x400) disabled by default ++ * @DC_FRL_MASK: (0x400) enabled by default + */ + DC_FRL_MASK = (1 << 10), + }; diff --git a/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch.sig b/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch.sig new file mode 100644 index 0000000..c879559 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0452-amd-hdmi-frl-default.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch b/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch new file mode 100644 index 0000000..1ba6ec7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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 +@@ -7096,6 +7096,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, +@@ -7616,6 +7621,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; +@@ -7707,6 +7713,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-bore/0460-vesa-displayid-dsc-bpp.patch.sig b/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch.sig new file mode 100644 index 0000000..f76e652 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.patch b/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.patch new file mode 100644 index 0000000..e27b493 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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 +@@ -7622,6 +7622,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; +@@ -7688,6 +7689,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 +@@ -7713,16 +7730,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-bore/0461-vesa-dsc-passthru-mode-match-fix.patch.sig b/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.patch.sig new file mode 100644 index 0000000..d303e52 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0472-amdgpu-userq-post-reset-error.patch b/pkgbuilds/linux-omarchy-bore/0472-amdgpu-userq-post-reset-error.patch new file mode 100644 index 0000000..942772c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0472-amdgpu-userq-post-reset-error.patch @@ -0,0 +1,26 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +@@ -1426,7 +1426,7 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) + struct amdgpu_usermode_queue *queue; + const struct amdgpu_userq_funcs *userq_funcs; + unsigned long queue_id; +- int r = 0; ++ int ret = 0, r; + + xa_for_each(&adev->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-bore/0472-amdgpu-userq-post-reset-error.patch.sig b/pkgbuilds/linux-omarchy-bore/0472-amdgpu-userq-post-reset-error.patch.sig new file mode 100644 index 0000000..a79ea43 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0472-amdgpu-userq-post-reset-error.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch b/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch new file mode 100644 index 0000000..08d39ec --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c +--- a/drivers/gpu/drm/i915/display/intel_cdclk.c ++++ b/drivers/gpu/drm/i915/display/intel_cdclk.c +@@ -2366,8 +2366,10 @@ static void bxt_sanitize_cdclk(struct intel_display *display) + * dividers both syncing to an active pipe, or asynchronously + * (PIPE_NONE). + */ +- cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); +- cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ if (DISPLAY_VER(display) < 30) { ++ cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ } + + if (cdctl != expected) { + if (DISPLAY_VER(display) < 20) { diff --git a/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch.sig b/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch.sig new file mode 100644 index 0000000..1048bf7 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch b/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch new file mode 100644 index 0000000..590d8a0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch @@ -0,0 +1,18 @@ +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 +@@ -4160,8 +4160,12 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) + else if (!IS_ERR_OR_NULL(panel_backlight_quirk) && + panel_backlight_quirk->force_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-bore/0474-amd-display-oled-vesa-backlight.patch.sig b/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch.sig new file mode 100644 index 0000000..14aa526 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch b/pkgbuilds/linux-omarchy-bore/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch new file mode 100644 index 0000000..a58a962 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig b/pkgbuilds/linux-omarchy-bore/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig new file mode 100644 index 0000000..3ddfad0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch new file mode 100644 index 0000000..29d72a3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch @@ -0,0 +1,50784 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -1752,6 +1752,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/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/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/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/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/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/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/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_fdl.h b/include/sound/sdca_fdl.h +--- a/include/sound/sdca_fdl.h ++++ b/include/sound/sdca_fdl.h +@@ -83,6 +83,10 @@ static inline int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) + return 0; + } + ++static inline void sdca_fdl_free_state(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_fdl_process(struct sdca_interrupt *interrupt) + { + return 0; +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/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/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/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 +@@ -44,6 +44,18 @@ + + struct asoc_sdw_codec_info; + ++struct asoc_sdw_mc_private { ++ struct snd_soc_card card; ++ struct snd_soc_jack sdw_headset; ++ struct device *headset_codec_dev; /* only one headset per card */ ++ struct device *amp_dev1, *amp_dev2; ++ bool append_dai_type; ++ bool ignore_internal_dmic; ++ void *private; ++ unsigned long mc_quirk; ++ int codec_info_list_count; ++}; ++ + struct asoc_sdw_dai_info { + const bool direction[2]; /* playback & capture support */ + const char *codec_name; +@@ -88,25 +100,13 @@ struct asoc_sdw_codec_info { + + int (*codec_card_late_probe)(struct snd_soc_card *card); + +- int (*count_sidecar)(struct snd_soc_card *card, ++ int (*count_sidecar)(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs); + int (*add_sidecar)(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, + struct snd_soc_codec_conf **codec_conf); + }; + +-struct asoc_sdw_mc_private { +- struct snd_soc_card card; +- struct snd_soc_jack sdw_headset; +- struct device *headset_codec_dev; /* only one headset per card */ +- struct device *amp_dev1, *amp_dev2; +- bool append_dai_type; +- bool ignore_internal_dmic; +- void *private; +- unsigned long mc_quirk; +- int codec_info_list_count; +-}; +- + struct asoc_sdw_endpoint { + struct list_head list; + +@@ -182,7 +182,8 @@ struct asoc_sdw_dailink *asoc_sdw_find_dailink(struct asoc_sdw_dailink *dailinks + const struct snd_soc_acpi_endpoint *new); + int asoc_sdw_get_dai_type(u32 type); + +-int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, ++int asoc_sdw_parse_sdw_endpoints(struct device *dev, ++ struct asoc_sdw_mc_private *ctx, + struct snd_soc_aux_dev *soc_aux, + struct asoc_sdw_dailink *soc_dais, + struct asoc_sdw_endpoint *soc_ends, +@@ -235,7 +236,7 @@ int asoc_sdw_es9356_amp_init(struct snd_soc_card *card, + int asoc_sdw_es9356_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); + + /* CS AMP support */ +-int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card, ++int asoc_sdw_bridge_cs35l56_count_sidecar(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs); + int asoc_sdw_bridge_cs35l56_add_sidecar(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, +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/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/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/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 + }; + + /** +@@ -584,12 +583,17 @@ EXPORT_SYMBOL_GPL(snd_card_disconnect_sync); + + static int snd_card_do_free(struct snd_card *card) + { ++ bool managed = card->managed; ++ + card->releasing = true; + #if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + 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 +@@ -601,7 +605,7 @@ static int snd_card_do_free(struct snd_card *card) + } + if (card->release_completion) + complete(card->release_completion); +- if (!card->managed) ++ if (!managed) + kfree(card); + return 0; + } +@@ -722,7 +726,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; + +@@ -1031,21 +1035,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) +@@ -4241,29 +4245,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. +@@ -528,7 +540,7 @@ static int bounce_error_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop) + { +- struct snd_seq_event bounce_ev; ++ struct snd_seq_event bounce_ev, quoted; + int result; + + if (client == NULL || +@@ -548,15 +560,19 @@ static int bounce_error_event(struct snd_seq_client *client, + * For user clients, send SNDRV_SEQ_EVENT_BOUNCE with the + * original event embedded as variable-length data. This + * avoids exposing data.quote.event (a kernel pointer) to +- * userspace. The variable-length path in snd_seq_event_dup() +- * copies the event data from data.ext.ptr into chained cells, +- * and snd_seq_expand_var_event() copies only the data content +- * -- never the pointer -- to userspace. ++ * userspace. Sanitise the embedded copy too - a queued ++ * variable-length event carries the address of its own ++ * extension cell, and the payload goes out verbatim. + */ ++ quoted = *event; ++ if (snd_seq_ev_is_variable("ed)) { ++ quoted.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; ++ quoted.data.ext.ptr = NULL; ++ } + bounce_ev.type = SNDRV_SEQ_EVENT_BOUNCE; + bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + bounce_ev.data.ext.len = sizeof(struct snd_seq_event); +- bounce_ev.data.ext.ptr = (char *)event; ++ bounce_ev.data.ext.ptr = (char *)"ed; + } else { + /* + * For kernel clients, quote the event pointer directly. +@@ -720,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; +@@ -744,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); +@@ -1941,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) = +@@ -1968,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; +@@ -2532,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", +@@ -2666,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_midi.c b/sound/core/seq/seq_midi.c +--- a/sound/core/seq/seq_midi.c ++++ b/sound/core/seq/seq_midi.c +@@ -42,9 +42,11 @@ struct seq_midisynth { + struct snd_rawmidi *rmidi; + int device; + int subdevice; ++ struct snd_rawmidi_substream __rcu *input_substream; ++ snd_use_lock_t input_use_lock; /* in-flight event_input users */ + struct snd_rawmidi_file input_rfile; +- spinlock_t output_lock; /* protects output_rfile publication */ + snd_use_lock_t output_use_lock; /* in-flight event_input users */ ++ struct snd_rawmidi_substream __rcu *output_substream; + struct snd_rawmidi_file output_rfile; + int seq_client; + int seq_port; +@@ -76,6 +78,14 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) + msynth = runtime->private_data; + if (msynth == NULL) + return; ++ ++ scoped_guard(rcu) { ++ if (rcu_dereference(msynth->input_substream) != substream) ++ return; ++ ++ snd_use_lock_use(&msynth->input_use_lock); ++ } ++ + memset(&ev, 0, sizeof(ev)); + while (runtime->avail > 0) { + res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); +@@ -95,6 +105,8 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) + memset(&ev, 0, sizeof(ev)); + } + } ++ ++ snd_use_lock_free(&msynth->input_use_lock); + } + + static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count) +@@ -134,8 +146,8 @@ static int event_process_midi(struct snd_seq_event *ev, int direct, + if (snd_BUG_ON(!msynth)) + return -EINVAL; + +- scoped_guard(spinlock_irqsave, &msynth->output_lock) { +- substream = msynth->output_rfile.output; ++ scoped_guard(rcu) { ++ substream = rcu_dereference(msynth->output_substream); + if (!substream) + return -ENODEV; + snd_use_lock_use(&msynth->output_use_lock); +@@ -177,7 +189,7 @@ static int snd_seq_midisynth_new(struct seq_midisynth *msynth, + msynth->card = card; + msynth->device = device; + msynth->subdevice = subdevice; +- spin_lock_init(&msynth->output_lock); ++ snd_use_lock_init(&msynth->input_use_lock); + snd_use_lock_init(&msynth->output_use_lock); + return 0; + } +@@ -188,28 +200,31 @@ static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_runtime *runtime; ++ struct snd_rawmidi_file rfile = {}; + struct snd_rawmidi_params params; + + /* open midi port */ + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, + SNDRV_RAWMIDI_LFLG_INPUT, +- &msynth->input_rfile); ++ &rfile); + if (err < 0) { + pr_debug("ALSA: seq_midi: midi input open failed!!!\n"); + return err; + } +- runtime = msynth->input_rfile.input->runtime; ++ runtime = rfile.input->runtime; + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = input_buffer_size; +- err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms); ++ err = snd_rawmidi_input_params(rfile.input, ¶ms); + if (err < 0) { +- snd_rawmidi_kernel_release(&msynth->input_rfile); ++ snd_rawmidi_kernel_release(&rfile); + return err; + } + snd_midi_event_reset_encode(msynth->parser); + runtime->event = snd_midi_input_event; + runtime->private_data = msynth; ++ msynth->input_rfile = rfile; ++ rcu_assign_pointer(msynth->input_substream, rfile.input); + snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); + return 0; + } +@@ -219,10 +234,19 @@ static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscri + { + int err; + struct seq_midisynth *msynth = private_data; ++ struct snd_rawmidi_file rfile; + +- if (snd_BUG_ON(!msynth->input_rfile.input)) ++ rcu_assign_pointer(msynth->input_substream, NULL); ++ synchronize_rcu(); ++ snd_use_lock_sync(&msynth->input_use_lock); ++ ++ rfile = msynth->input_rfile; ++ msynth->input_rfile = (struct snd_rawmidi_file){}; ++ ++ if (snd_BUG_ON(!rfile.input)) + return -EINVAL; +- err = snd_rawmidi_kernel_release(&msynth->input_rfile); ++ ++ err = snd_rawmidi_kernel_release(&rfile); + return err; + } + +@@ -252,8 +276,8 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info + return err; + } + snd_midi_event_reset_decode(msynth->parser); +- scoped_guard(spinlock_irqsave, &msynth->output_lock) +- msynth->output_rfile = rfile; ++ msynth->output_rfile = rfile; ++ rcu_assign_pointer(msynth->output_substream, rfile.output); + return 0; + } + +@@ -261,17 +285,16 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info + static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info) + { + struct seq_midisynth *msynth = private_data; +- struct snd_rawmidi_file rfile = {}; ++ struct snd_rawmidi_file rfile; + +- scoped_guard(spinlock_irqsave, &msynth->output_lock) { +- rfile = msynth->output_rfile; +- msynth->output_rfile = (struct snd_rawmidi_file){}; +- } ++ rcu_assign_pointer(msynth->output_substream, NULL); ++ synchronize_rcu(); ++ snd_use_lock_sync(&msynth->output_use_lock); ++ rfile = msynth->output_rfile; ++ msynth->output_rfile = (struct snd_rawmidi_file){}; + + if (snd_BUG_ON(!rfile.output)) + return -EINVAL; +- +- snd_use_lock_sync(&msynth->output_use_lock); + snd_rawmidi_drain_output(rfile.output); + return snd_rawmidi_kernel_release(&rfile); + } +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), +@@ -3524,6 +3579,16 @@ static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, + } + } + ++static void alc_fixup_yoga_pro7_audio(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* Reuse the DAC routing selected for ThinkPad X1 Gen7 */ ++ alc285_fixup_thinkpad_x1_gen7(codec, fix, action); ++ ++ /* Limit the internal mic boost to 0 or 1 to avoid noise */ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++} ++ + static void alc295_fixup_dell_inspiron_top_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3929,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, +@@ -3997,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, +@@ -4066,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, +@@ -4091,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, +@@ -4108,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, +@@ -4139,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, +@@ -4183,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, +@@ -4197,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, +@@ -4206,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, +@@ -4226,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; +@@ -4263,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[]) { +@@ -4703,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, +@@ -5078,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, +@@ -5143,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 + }, +@@ -5662,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, +@@ -6209,11 +6294,20 @@ static const struct hda_fixup alc269_fixups[] = { + }, + [ALC287_FIXUP_LENOVO_YOGA_PRO7] = { + .type = HDA_FIXUP_FUNC, +- /* Reuse the DAC routing selected for ThinkPad X1 Gen7 */ +- .v.func = alc285_fixup_thinkpad_x1_gen7, ++ .v.func = alc_fixup_yoga_pro7_audio, + .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, +@@ -6653,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, +@@ -6729,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[]) { +@@ -6749,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, +@@ -6776,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, +@@ -6898,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[] = { +@@ -6951,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), +@@ -6961,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), +@@ -7145,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), +@@ -7259,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), +@@ -7350,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), +@@ -7502,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), +@@ -7509,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), +@@ -7516,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), +@@ -7574,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), +@@ -7585,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), +@@ -7614,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), +@@ -7625,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), +@@ -7837,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), +@@ -7853,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), +@@ -7928,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), +@@ -7963,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), +@@ -7980,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 + */ +@@ -7990,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), +@@ -7998,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), +@@ -8018,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), +@@ -8032,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), +@@ -8069,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), +@@ -8086,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), +@@ -8159,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), + {} + }; + +@@ -8298,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 \ +@@ -8903,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; +@@ -173,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; + } + +@@ -722,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; + } + +@@ -752,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. +@@ -785,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 +@@ -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; + } + +@@ -400,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); + } + } +@@ -848,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; +@@ -865,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; + } +@@ -883,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; + } +@@ -918,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/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; +@@ -2967,8 +2973,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) +@@ -3370,7 +3379,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/common/proc.c b/sound/hda/common/proc.c +--- a/sound/hda/common/proc.c ++++ b/sound/hda/common/proc.c +@@ -624,7 +624,7 @@ static void print_conn_list(struct snd_info_buffer *buffer, + /* Get Cache connections info */ + cache_len = snd_hda_get_conn_list(codec, nid, &list); + if (cache_len >= 0 && (cache_len != conn_len || +- memcmp(list, conn, conn_len) != 0)) { ++ memcmp(list, conn, conn_len * sizeof(*conn)) != 0)) { + snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len); + if (cache_len > 0) { + snd_iprintf(buffer, " "); +diff --git a/sound/hda/controllers/Kconfig b/sound/hda/controllers/Kconfig +--- a/sound/hda/controllers/Kconfig ++++ b/sound/hda/controllers/Kconfig +@@ -33,6 +33,7 @@ config SND_HDA_TEGRA + config SND_HDA_CIX_IPBLOQ + tristate "CIX IPBLOQ HD Audio" + depends on ARCH_CIX || COMPILE_TEST ++ depends on ARCH_DMA_ADDR_T_64BIT + select SND_HDA + select SND_HDA_ALIGNED_MMIO + help +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 +@@ -663,6 +663,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x1af4, "QEMU" }, + { 0x1fa8, "Senarytech" }, + { 0x434d, "C-Media" }, ++ { 0x4c54, "Lisuan" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +@@ -765,7 +766,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/hpi6000.c b/sound/pci/asihpi/hpi6000.c +--- a/sound/pci/asihpi/hpi6000.c ++++ b/sound/pci/asihpi/hpi6000.c +@@ -537,6 +537,11 @@ static short create_adapter_obj(struct hpi_adapter_obj *pao, + hr1.size = sizeof(hr1); + + error = hpi6000_message_response_sequence(pao, 0, &hm, &hr0); ++ if (error) { ++ HPI_DEBUG_LOG(ERROR, "message transport error %d\n", ++ error); ++ return error; ++ } + if (hr0.error) { + HPI_DEBUG_LOG(DEBUG, "message error %d\n", hr0.error); + return hr0.error; +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/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c +--- a/sound/pci/via82xx_modem.c ++++ b/sound/pci/via82xx_modem.c +@@ -573,24 +573,18 @@ static inline unsigned int calc_linear_pos(struct via82xx_modem *chip, + viadev->bufsize2, viadev->idx_table[idx].offset, + viadev->idx_table[idx].size, count); + #endif +- if (count && size < count) { ++ if (! count) ++ /* bogus count 0 on the DMA boundary? */ ++ res = viadev->idx_table[idx].offset; ++ else ++ /* count register returns full size ++ * when end of buffer is reached ++ */ ++ res = viadev->idx_table[idx].offset + size; ++ if (check_invalid_pos(viadev, res)) { + dev_dbg(chip->card->dev, +- "invalid via82xx_cur_ptr, using last valid pointer\n"); ++ "invalid via82xx_cur_ptr (2), using last valid pointer\n"); + res = viadev->lastpos; +- } else { +- if (! count) +- /* bogus count 0 on the DMA boundary? */ +- res = viadev->idx_table[idx].offset; +- else +- /* count register returns full size +- * when end of buffer is reached +- */ +- res = viadev->idx_table[idx].offset + size; +- if (check_invalid_pos(viadev, res)) { +- dev_dbg(chip->card->dev, +- "invalid via82xx_cur_ptr (2), using last valid pointer\n"); +- res = viadev->lastpos; +- } + } + } + viadev->lastpos = res; /* remember the last position */ +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/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 +@@ -432,7 +432,7 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) + if (!soc_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(card, soc_aux, soc_dais, soc_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, soc_aux, soc_dais, soc_ends, &num_confs); + if (ret < 0) + return ret; + +@@ -521,15 +521,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; +@@ -546,12 +546,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); +@@ -560,9 +560,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; + } +@@ -580,8 +580,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 +@@ -303,7 +303,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_devs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + if (ret < 0) + return ret; + +@@ -388,12 +388,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 */ +@@ -412,15 +412,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; + } +@@ -438,8 +438,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; +@@ -210,16 +215,22 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, + SERDES_STATUS_EN | SERDES_STATUS_RST, + SERDES_STATUS_RST); + /* +- * Experiments suggest that it takes at most ~1 us +- * for the bit to clear, so wait 2 us for good measure. ++ * The SERDES cluster needs a bit of time to reset itself ++ * and settle before we start poking it. This is... slow... + */ +- udelay(2); ++ udelay(25); + WARN_ON(readl_relaxed(cl->base + serdes_unit + REG_SERDES_STATUS) & + SERDES_STATUS_RST); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + 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" +@@ -726,6 +728,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 +739,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 +@@ -889,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 +@@ -1201,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 +@@ -1888,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 +@@ -1978,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 +@@ -2297,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 +@@ -2771,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 +@@ -2894,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 +@@ -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 +@@ -284,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 +@@ -426,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 +@@ -496,6 +499,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 +@@ -724,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 +@@ -876,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,6 +14,7 @@ + * for ST-Ericsson. + */ + ++#include + #include + #include + #include +@@ -989,9 +990,8 @@ static int sid_status_control_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + +- mutex_lock(&drvdata->ctrl_lock); ++ guard(mutex)(&drvdata->ctrl_lock); + ucontrol->value.enumerated.item[0] = drvdata->sid_status; +- mutex_unlock(&drvdata->ctrl_lock); + + return 0; + } +@@ -1014,7 +1014,7 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + return -EIO; + } + +- mutex_lock(&drvdata->ctrl_lock); ++ guard(mutex)(&drvdata->ctrl_lock); + + sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); + if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { +@@ -1025,7 +1025,8 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + } else { + status = -EBUSY; + } +- goto out; ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ return status; + } + + snd_soc_component_write(component, AB8500_SIDFIRADR, 0); +@@ -1043,9 +1044,6 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + + drvdata->sid_status = SID_FIR_CONFIGURED; + +-out: +- mutex_unlock(&drvdata->ctrl_lock); +- + dev_dbg(component->dev, "%s: Exit\n", __func__); + + return status; +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 +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -0,0 +1,1409 @@ ++// 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 __free(firmware) = 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) ++ return -ENOMEM; ++ ++ aw88399->aw_cfg->len = (int)cont->size; ++ memcpy(aw88399->aw_cfg->data, cont->data, cont->size); ++ ++ 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 +@@ -7,1220 +7,16 @@ + // Author: Weidong Wang + // + ++#include + #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", +@@ -1702,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; + } + +@@ -1715,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + aw88399_start(aw88399, AW88399_SYNC_START); + } + +- mutex_unlock(&aw88399->lock); +- + return 1; + } + +@@ -1869,86 +662,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, +@@ -1975,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); +@@ -1986,7 +699,6 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88399->lock); + + return 0; + } +@@ -2040,70 +752,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/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-i2c.c b/sound/soc/codecs/cs35l56-i2c.c +--- a/sound/soc/codecs/cs35l56-i2c.c ++++ b/sound/soc/codecs/cs35l56-i2c.c +@@ -51,15 +51,7 @@ static int cs35l56_i2c_probe(struct i2c_client *client) + return dev_err_probe(cs35l56->base.dev, ret, "Failed to allocate register map\n"); + } + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- ret = cs35l56_irq_request(&cs35l56->base, client->irq); +- if (ret < 0) +- cs35l56_remove(cs35l56); +- +- return ret; ++ return cs35l56_common_probe(cs35l56, client->irq); + } + + static void cs35l56_i2c_remove(struct i2c_client *client) +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,25 +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); + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- return 0; ++ 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 */ + }; +@@ -509,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-spi.c b/sound/soc/codecs/cs35l56-spi.c +--- a/sound/soc/codecs/cs35l56-spi.c ++++ b/sound/soc/codecs/cs35l56-spi.c +@@ -40,15 +40,7 @@ static int cs35l56_spi_probe(struct spi_device *spi) + if (ret) + return ret; + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- ret = cs35l56_irq_request(&cs35l56->base, spi->irq); +- if (ret < 0) +- cs35l56_remove(cs35l56); +- +- return ret; ++ return cs35l56_common_probe(cs35l56, spi->irq); + } + + static void cs35l56_spi_remove(struct spi_device *spi) +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 +@@ -37,48 +37,49 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + +-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); +@@ -828,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); +@@ -1402,6 +1399,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; +@@ -1941,7 +1939,7 @@ static int cs35l56_try_get_broken_sdca_spkid_gpio(struct cs35l56_private *cs35l5 + return ret; + } + +-int cs35l56_common_probe(struct cs35l56_private *cs35l56) ++int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) + { + int ret; + +@@ -2018,16 +2016,24 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56) + goto err_remove_wm_adsp; + } + ++ ret = cs35l56_irq_request(&cs35l56->base, irq); ++ if (ret) ++ goto err_remove_wm_adsp; ++ + ret = snd_soc_register_component(cs35l56->base.dev, + &soc_component_dev_cs35l56, + cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); + if (ret < 0) { + dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n"); +- goto err_remove_wm_adsp; ++ goto err_free_irq; + } + + return 0; + ++err_free_irq: ++ if (cs35l56->base.irq) ++ devm_free_irq(cs35l56->base.dev, cs35l56->base.irq, &cs35l56->base); ++ + err_remove_wm_adsp: + wm_adsp2_remove(&cs35l56->dsp); + +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 +@@ -39,8 +39,6 @@ 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; + struct completion init_completion; +@@ -65,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); +@@ -76,9 +72,8 @@ 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 cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); + int cs35l56_init(struct cs35l56_private *cs35l56); + void cs35l56_remove(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 +@@ -452,31 +453,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); +@@ -484,8 +484,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + + hcp->busy = true; + +-err: +- mutex_unlock(&hcp->lock); + return ret; + } + +@@ -503,9 +501,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; + } +@@ -458,10 +458,8 @@ static void rt700_sdw_remove(struct sdw_slave *slave) + { + struct rt700_priv *rt700 = dev_get_drvdata(&slave->dev); + +- if (rt700->hw_init) { +- cancel_delayed_work_sync(&rt700->jack_detect_work); +- cancel_delayed_work_sync(&rt700->jack_btn_check_work); +- } ++ cancel_delayed_work_sync(&rt700->jack_detect_work); ++ cancel_delayed_work_sync(&rt700->jack_btn_check_work); + + pm_runtime_disable(&slave->dev); + } +@@ -501,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 +@@ -916,10 +916,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 +@@ -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; + } +@@ -2243,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; +@@ -2253,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"); + +@@ -2399,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, +@@ -2407,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"); + +@@ -2797,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 +@@ -2818,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 = { +@@ -1098,7 +1147,13 @@ static s32 tas2783_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(tas_dev->regmap, false); +- regcache_sync(tas_dev->regmap); ++ ret = regcache_sync(tas_dev->regmap); ++ if (ret) { ++ regcache_cache_only(tas_dev->regmap, true); ++ regcache_mark_dirty(tas_dev->regmap); ++ return ret; ++ } ++ + return 0; + } + +@@ -1224,9 +1279,23 @@ static s32 tas_update_status(struct sdw_slave *slave, + if (tas_dev->hw_init || tas_dev->status != SDW_SLAVE_ATTACHED) + return 0; + +- /* updated the cache data to device */ + regcache_cache_only(tas_dev->regmap, false); +- regcache_sync(tas_dev->regmap); ++ ++ /* ++ * The device is attaching uninitialized: either this is the first ++ * attach, or it lost power (and with it all register and DSP state) ++ * while the controller was power-gated during system suspend. The ++ * cache still holds the pre-suspend values, and tas_io_init() below ++ * resets the device via TAS2783_SW_RESET anyway, so syncing it back ++ * is both useless and harmful: later read-modify-write updates would ++ * compare against stale data and skip the hardware write. ++ * ++ * Drop the cache instead, so that subsequent accesses see the real ++ * hardware state. Syncing after the reset is not an option either: ++ * the cache accepts registers for which tas2783_sdca_mbq_size() ++ * returns 0, and writing those back fails with -EINVAL. ++ */ ++ regcache_drop_region(tas_dev->regmap, 0, UINT_MAX); + + /* perform I/O transfers required for Slave initialization */ + return tas_io_init(&slave->dev, slave); +@@ -1329,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/tas675x.c b/sound/soc/codecs/tas675x.c +--- a/sound/soc/codecs/tas675x.c ++++ b/sound/soc/codecs/tas675x.c +@@ -1924,15 +1924,6 @@ static const struct reg_default tas675x_reg_defaults[] = { + { TAS675X_PWM_PHASE_M_CTRL_CH2_REG, 0x00 }, + { TAS675X_PWM_PHASE_M_CTRL_CH3_REG, 0x00 }, + { TAS675X_PWM_PHASE_M_CTRL_CH4_REG, 0x00 }, +- { TAS675X_DC_LDG_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_LO_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 }, +- { TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 }, +- { TAS675X_AC_LDG_CTRL_REG, 0x10 }, +- { TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 }, +- { TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 }, +- { TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 }, + { TAS675X_REPORT_ROUTING_1_REG, 0x00 }, + { TAS675X_OTSD_RECOVERY_EN_REG, 0x00 }, + { TAS675X_REPORT_ROUTING_2_REG, 0xA2 }, +@@ -1943,6 +1934,15 @@ static const struct reg_default tas675x_reg_defaults[] = { + { TAS675X_GPIO1_OUTPUT_SEL_REG, 0x00 }, + { TAS675X_GPIO2_OUTPUT_SEL_REG, 0x00 }, + { TAS675X_GPIO_CTRL_REG, TAS675X_GPIO_CTRL_RSTVAL }, ++ { TAS675X_DC_LDG_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_LO_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 }, ++ { TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 }, ++ { TAS675X_AC_LDG_CTRL_REG, 0x10 }, ++ { TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 }, ++ { TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 }, ++ { TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 }, + { TAS675X_OTW_CTRL_CH1_CH2_REG, 0x11 }, + { TAS675X_OTW_CTRL_CH3_CH4_REG, 0x11 }, + }; +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) { +- dev_err(&pdev->dev, "failed to find CPU DAI device\n"); +- ret = -EINVAL; +- 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); + +@@ -1118,7 +1349,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", +@@ -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); + } + + /** +@@ -1358,7 +1352,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.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_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_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; +@@ -457,6 +454,9 @@ static const struct of_device_id fsl_audmix_ids[] = { + }; + MODULE_DEVICE_TABLE(of, fsl_audmix_ids); + ++static int fsl_audmix_runtime_resume(struct device *dev); ++static int fsl_audmix_runtime_suspend(struct device *dev); ++ + static int fsl_audmix_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +@@ -488,13 +488,25 @@ static int fsl_audmix_probe(struct platform_device *pdev) + spin_lock_init(&priv->lock); + platform_set_drvdata(pdev, priv); + pm_runtime_enable(dev); ++ if (!pm_runtime_enabled(dev)) { ++ ret = fsl_audmix_runtime_resume(dev); ++ if (ret) ++ goto err_disable_pm; ++ } ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err_pm_get_sync; ++ ++ /* To enable regmap cache only when runtime PM enabled */ ++ pm_runtime_put(dev); + + ret = devm_snd_soc_register_component(dev, &fsl_audmix_component, + fsl_audmix_dai, + ARRAY_SIZE(fsl_audmix_dai)); + if (ret) { + dev_err(dev, "failed to register ASoC DAI\n"); +- goto err_disable_pm; ++ goto err_pm_get_sync; + } + + /* +@@ -506,12 +518,15 @@ static int fsl_audmix_probe(struct platform_device *pdev) + if (IS_ERR(priv->pdev)) { + ret = PTR_ERR(priv->pdev); + dev_err(dev, "failed to register platform: %d\n", ret); +- goto err_disable_pm; ++ goto err_pm_get_sync; + } + } + + return 0; + ++err_pm_get_sync: ++ if (!pm_runtime_status_suspended(dev)) ++ fsl_audmix_runtime_suspend(dev); + err_disable_pm: + pm_runtime_disable(dev); + return ret; +@@ -522,6 +537,8 @@ static void fsl_audmix_remove(struct platform_device *pdev) + struct fsl_audmix *priv = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); ++ if (!pm_runtime_status_suspended(&pdev->dev)) ++ fsl_audmix_runtime_suspend(&pdev->dev); + + if (priv->pdev) + platform_device_unregister(priv->pdev); +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); + } + + /* +@@ -2227,7 +2219,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; +@@ -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_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/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-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/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,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -251,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; + } + +@@ -1305,7 +1303,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) +@@ -1314,8 +1312,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, +@@ -1334,13 +1330,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) { +@@ -1348,10 +1344,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; + } + +@@ -1496,15 +1488,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) +@@ -1560,7 +1550,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) +@@ -1572,8 +1562,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 +@@ -958,7 +958,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) +@@ -1387,25 +1387,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 +@@ -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); +@@ -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 +@@ -2222,7 +2222,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); +@@ -2235,7 +2235,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 +@@ -1288,7 +1288,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + goto err_dai; + } + +- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + if (ret < 0) + goto err_end; + +@@ -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/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-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/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); + +@@ -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/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 +@@ -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), +@@ -180,7 +178,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/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 +@@ -50,25 +50,20 @@ int meson_card_reallocate_links(struct snd_soc_card *card, + num_links * sizeof(*priv->card.dai_link), + GFP_KERNEL | __GFP_ZERO); + if (!links) +- goto err_links; ++ return -ENOMEM; ++ ++ priv->card.dai_link = links; ++ priv->card.num_links = num_links; + + ldata = krealloc(priv->link_data, + num_links * sizeof(*priv->link_data), + GFP_KERNEL | __GFP_ZERO); ++ /* meson_card_clean_references() will free the links on this error path */ + if (!ldata) +- goto err_ldata; ++ return -ENOMEM; + +- priv->card.dai_link = links; + priv->link_data = ldata; +- priv->card.num_links = num_links; + return 0; +- +-err_ldata: +- kfree(links); +-err_links: +- dev_err(priv->card.dev, "failed to allocate links\n"); +- return -ENOMEM; +- + } + EXPORT_SYMBOL_GPL(meson_card_reallocate_links); + +@@ -76,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) +@@ -83,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; + } +@@ -94,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; +@@ -137,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; + +@@ -163,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; + } +@@ -194,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; + } + +@@ -224,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/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/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/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c +--- a/sound/soc/pxa/pxa-ssp.c ++++ b/sound/soc/pxa/pxa-ssp.c +@@ -769,13 +769,10 @@ static int pxa_ssp_probe(struct snd_soc_dai *dai) + goto err_priv; + } + +- priv->extclk = devm_clk_get(dev, "extclk"); ++ priv->extclk = devm_clk_get_optional(dev, "extclk"); + if (IS_ERR(priv->extclk)) { + ret = PTR_ERR(priv->extclk); +- if (ret == -EPROBE_DEFER) +- goto err_priv; +- +- priv->extclk = NULL; ++ goto err_priv; + } + } else { + priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio"); +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/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c +--- a/sound/soc/qcom/qdsp6/q6apm.c ++++ b/sound/soc/qcom/qdsp6/q6apm.c +@@ -802,14 +802,17 @@ EXPORT_SYMBOL_GPL(q6apm_graph_prepare); + int q6apm_graph_start(struct q6apm_graph *graph) + { + struct audioreach_graph *ar_graph = graph->ar_graph; +- int ret = 0; ++ int ret; + +- if (ar_graph->start_count == 0) ++ if (ar_graph->start_count == 0) { + ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START); ++ if (ret) ++ return ret; ++ } + + ar_graph->start_count++; + +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(q6apm_graph_start); + +@@ -817,6 +820,9 @@ int q6apm_graph_stop(struct q6apm_graph *graph) + { + struct audioreach_graph *ar_graph = graph->ar_graph; + ++ if (ar_graph->start_count == 0) ++ return 0; ++ + if (--ar_graph->start_count > 0) + return 0; + +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 +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -512,7 +513,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + u32 mod, mask, val = 0; + int ret = 0; + +- pm_runtime_get_sync(dai->dev); ++ guard(pm_runtime_active)(dai->dev); + + scoped_guard(spinlock_irqsave, &priv->lock) + mod = readl(priv->addr + I2SMOD); +@@ -537,8 +538,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + && (mod & cdcon_mask))))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); +- ret = -EAGAIN; +- goto err; ++ return -EAGAIN; + } + + if (dir == SND_SOC_CLOCK_IN) +@@ -566,7 +566,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + } else { + priv->rclk_srcrate = + clk_get_rate(priv->op_clk); +- goto done; ++ return 0; + } + } + +@@ -580,14 +580,14 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + if (WARN_ON(IS_ERR(priv->op_clk))) { + ret = PTR_ERR(priv->op_clk); + priv->op_clk = NULL; +- goto err; ++ return ret; + } + + ret = clk_prepare_enable(priv->op_clk); + if (ret) { + clk_put(priv->op_clk); + priv->op_clk = NULL; +- goto err; ++ return ret; + } + priv->rclk_srcrate = clk_get_rate(priv->op_clk); + +@@ -595,11 +595,10 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + || (clk_id && !(mod & rsrc_mask))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); +- ret = -EAGAIN; +- goto err; ++ return -EAGAIN; + } else { + /* Call can't be on the active DAI */ +- goto done; ++ return 0; + } + + if (clk_id == 1) +@@ -607,8 +606,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + break; + default: + dev_err(&i2s->pdev->dev, "We don't serve that!\n"); +- ret = -EINVAL; +- goto err; ++ return -EINVAL; + } + + scoped_guard(spinlock_irqsave, &priv->lock) { +@@ -616,13 +614,8 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + mod = (mod & ~mask) | val; + writel(mod, priv->addr + I2SMOD); + } +-done: +- pm_runtime_put(dai->dev); + + return 0; +-err: +- pm_runtime_put(dai->dev); +- return ret; + } + + static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +@@ -1123,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), + +@@ -1448,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) { +@@ -1657,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, + }; +@@ -1682,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_bridge_cs35l56.c b/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c +--- a/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c ++++ b/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c +@@ -99,11 +99,9 @@ static const struct snd_soc_dai_link bridge_dai_template = { + SND_SOC_DAILINK_REG(asoc_sdw_bridge_dai), + }; + +-int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card, ++int asoc_sdw_bridge_cs35l56_count_sidecar(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs) + { +- struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); +- + if (ctx->mc_quirk & SOC_SDW_SIDECAR_AMPS) { + (*num_dais)++; + (*num_devs) += ARRAY_SIZE(bridge_cs35l56_name_prefixes); +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,21 +1990,16 @@ 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 snd_soc_card *card, ++int asoc_sdw_parse_sdw_endpoints(struct device *dev, ++ struct asoc_sdw_mc_private *ctx, + struct snd_soc_aux_dev *soc_aux, + struct asoc_sdw_dailink *soc_dais, + struct asoc_sdw_endpoint *soc_ends, + int *num_devs) + { +- struct device *dev = card->dev; +- struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); + struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; + const struct snd_soc_acpi_link_adr *adr_link; +@@ -2045,7 +2061,7 @@ int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, + ctx->ignore_internal_dmic |= codec_info->ignore_internal_dmic; + + if (codec_info->count_sidecar && codec_info->add_sidecar) { +- ret = codec_info->count_sidecar(card, &num_dais, num_devs); ++ ret = codec_info->count_sidecar(ctx, &num_dais, num_devs); + if (ret) + return ret; + +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); +@@ -1127,6 +1168,7 @@ static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget) + "Invalid number of formats: input: %d, output: %d\n", + src->available_fmt.num_input_formats, + src->available_fmt.num_output_formats); ++ ret = -EINVAL; + goto err; + } + +@@ -1179,6 +1221,7 @@ static int sof_ipc4_widget_setup_comp_asrc(struct snd_sof_widget *swidget) + "Invalid number of formats: input: %d, output: %d\n", + asrc->available_fmt.num_input_formats, + asrc->available_fmt.num_output_formats); ++ ret = -EINVAL; + goto err; + } + +@@ -1369,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); + +@@ -1686,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) { +@@ -2063,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; +@@ -2538,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, +@@ -2551,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, +@@ -2596,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, +@@ -2642,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, +@@ -2808,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, +@@ -2820,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; + +@@ -2866,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; + } +@@ -3083,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) +@@ -3111,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; +@@ -3154,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; +@@ -3340,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; + +@@ -3353,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; +@@ -3410,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) +@@ -846,6 +846,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + struct snd_soc_tplg_mixer_control *mc = + container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); + int tlv[SOF_TLV_ITEMS]; ++ u32 min, max; + unsigned int mask; + int ret; + +@@ -853,6 +854,11 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + ++ min = le32_to_cpu(mc->min); ++ max = le32_to_cpu(mc->max); ++ if (min > max || max >= INT_MAX) ++ return -EINVAL; ++ + /* + * If control has more than 2 channels we need to override the info. This is because even if + * ASoC layer has defined topology's max channel count to SND_SOC_TPLG_MAX_CHAN = 8, the +@@ -863,12 +869,12 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + kc->info = snd_sof_volume_info; + + scontrol->comp_id = sdev->next_comp_id; +- scontrol->min_volume_step = le32_to_cpu(mc->min); +- scontrol->max_volume_step = le32_to_cpu(mc->max); ++ scontrol->min_volume_step = min; ++ scontrol->max_volume_step = max; + scontrol->num_channels = le32_to_cpu(mc->num_channels); + +- scontrol->max = le32_to_cpu(mc->max); +- if (le32_to_cpu(mc->max) == 1) ++ scontrol->max = max; ++ if (max == 1) + goto skip; + + /* extract tlv data */ +@@ -878,7 +884,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + } + + /* set up volume table */ +- ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); ++ ret = set_up_volume_table(scontrol, tlv, max + 1); + if (ret < 0) { + dev_err(scomp->dev, "error: setting up volume table\n"); + return ret; +@@ -911,7 +917,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + return 0; + + err: +- if (le32_to_cpu(mc->max) > 1) ++ if (max > 1) + kfree(scontrol->volume_table); + + return ret; +@@ -1102,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)) { +@@ -1167,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)) +@@ -1943,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; +@@ -1995,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; +@@ -2013,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) { +@@ -2070,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; +@@ -2505,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; + +@@ -2537,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; +- } + } + + /* +@@ -2551,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; +@@ -2567,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) { + /* +@@ -2585,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); +@@ -2605,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) { + /* +@@ -2629,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); +@@ -2649,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 +@@ -52,7 +52,7 @@ struct spacemit_i2s_dev { + + struct clk *sysclk; + struct clk *bclk; +- struct clk *sspa_clk; ++ struct clk *func_clk; + struct clk *sysclk_div; + struct clk *c_sysclk; + struct clk *c_bclk; +@@ -221,7 +221,7 @@ static int spacemit_i2s_hw_params(struct snd_pcm_substream *substream, + if (ret) + return ret; + +- return clk_set_rate(i2s->sspa_clk, bclk_rate); ++ return clk_set_rate(i2s->func_clk, bclk_rate); + } + + static int spacemit_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, +@@ -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,26 +348,12 @@ 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 = { + .ops = &spacemit_i2s_dai_ops, +- .playback = { +- .channels_min = 1, +- .channels_max = 2, +- .rates = SPACEMIT_PCM_RATES, +- .rate_min = SNDRV_PCM_RATE_8000, +- .rate_max = SNDRV_PCM_RATE_48000, +- .formats = SPACEMIT_PCM_FORMATS, +- }, +- .capture = { +- .channels_min = 1, +- .channels_max = 2, +- .rates = SPACEMIT_PCM_RATES, +- .rate_min = SNDRV_PCM_RATE_8000, +- .rate_max = SNDRV_PCM_RATE_48000, +- .formats = SPACEMIT_PCM_FORMATS, +- }, + .symmetric_rate = 1, + }; + +@@ -392,6 +383,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, + dai->playback.channels_min = 1; + dai->playback.channels_max = 2; + dai->playback.rates = SPACEMIT_PCM_RATES; ++ dai->playback.rate_min = SNDRV_PCM_RATE_8000; ++ dai->playback.rate_max = SNDRV_PCM_RATE_48000; + dai->playback.formats = SPACEMIT_PCM_FORMATS; + + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +@@ -404,6 +397,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, + dai->capture.channels_min = 1; + dai->capture.channels_max = 2; + dai->capture.rates = SPACEMIT_PCM_RATES; ++ dai->capture.rate_min = SNDRV_PCM_RATE_8000; ++ dai->capture.rate_max = SNDRV_PCM_RATE_48000; + dai->capture.formats = SPACEMIT_PCM_FORMATS; + + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +@@ -445,14 +440,14 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + if (IS_ERR(i2s->bclk)) + return dev_err_probe(i2s->dev, PTR_ERR(i2s->bclk), "failed to enable bit clock\n"); + +- clk = devm_clk_get_enabled(i2s->dev, "sspa_bus"); ++ clk = devm_clk_get_enabled(i2s->dev, "bus"); + if (IS_ERR(clk)) +- return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable sspa_bus clock\n"); ++ return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable bus clock\n"); + +- i2s->sspa_clk = devm_clk_get_enabled(i2s->dev, "sspa"); +- if (IS_ERR(i2s->sspa_clk)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->sspa_clk), +- "failed to enable sspa clock\n"); ++ i2s->func_clk = devm_clk_get_enabled(i2s->dev, "func"); ++ if (IS_ERR(i2s->func_clk)) ++ return dev_err_probe(i2s->dev, PTR_ERR(i2s->func_clk), ++ "failed to enable func clock\n"); + + i2s->sysclk_div = devm_clk_get_optional_enabled(i2s->dev, "sysclk_div"); + if (IS_ERR(i2s->sysclk_div)) +@@ -471,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)) +@@ -488,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 +@@ -4,6 +4,7 @@ + * Author: Peter Ujfalusi + */ + ++#include + #include + #include + #include +@@ -263,7 +264,7 @@ static int j721e_audio_startup(struct snd_pcm_substream *substream) + int ret = 0; + int i; + +- guard(mutex)(&priv->mutex); ++ mutex_lock(&priv->mutex); + + domain->active++; + +@@ -303,6 +304,7 @@ static int j721e_audio_startup(struct snd_pcm_substream *substream) + out: + if (ret) + domain->active--; ++ mutex_unlock(&priv->mutex); + + return ret; + } +@@ -868,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 +@@ -384,7 +382,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err) { + dev_err(component->dev, + "Unable to set constraint on period bytes\n"); +- return err; ++ goto error; + } + + /* Resize the buffer bytes as divisible by 64 */ +@@ -394,7 +392,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err) { + dev_err(component->dev, + "Unable to set constraint on buffer bytes\n"); +- return err; ++ goto error; + } + + /* Set periods as integer multiple */ +@@ -403,7 +401,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err < 0) { + dev_err(component->dev, + "Unable to set constraint on periods to be integer\n"); +- return err; ++ goto error; + } + + /* enable DMA IOC irq */ +@@ -412,6 +410,14 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + writel(val, stream_data->mmio + XLNX_AUD_CTRL); + + return 0; ++ ++error: ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ++ adata->play_stream = NULL; ++ else ++ adata->capture_stream = NULL; ++ kfree(stream_data); ++ return err; + } + + static int xlnx_formatter_pcm_close(struct snd_soc_component *component, +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/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 +@@ -1236,7 +1236,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); + } + + /* allocate and initialize data urbs */ +@@ -1260,6 +1263,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-bore/0510-sound-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch.sig new file mode 100644 index 0000000..cfb31fc Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch b/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch new file mode 100644 index 0000000..c32b5c3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch @@ -0,0 +1,1655 @@ +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/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/control_led.c b/sound/core/control_led.c +--- a/sound/core/control_led.c ++++ b/sound/core/control_led.c +@@ -255,6 +255,8 @@ static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, + kctl = snd_ctl_find_id(card, id); + if (!kctl) + return -ENOENT; ++ if (!kctl->info || !kctl->get) ++ return -EINVAL; + ioff = snd_ctl_get_ioff(kctl, id); + vd = &kctl->vd[ioff]; + access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; +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/drivers/mtpav.c b/sound/drivers/mtpav.c +--- a/sound/drivers/mtpav.c ++++ b/sound/drivers/mtpav.c +@@ -642,9 +642,7 @@ static void snd_mtpav_free(struct snd_card *card) + { + struct mtpav *crd = card->private_data; + +- guard(spinlock_irqsave)(&crd->spinlock); +- if (crd->istimer > 0) +- snd_mtpav_remove_output_timer(crd); ++ timer_shutdown_sync(&crd->timer); + } + + /* +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/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; ++ struct snd_card *card __free(snd_card_unref) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; +@@ -2535,8 +2535,8 @@ static int snd_ice1712_probe(struct pci_dev *pci, + return -ENOENT; + } + +- err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, +- sizeof(*ice), &card); ++ err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, ++ sizeof(*ice), &card); + if (err < 0) + return err; + ice = card->private_data; +@@ -2640,6 +2640,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + if (err < 0) + return err; + pci_set_drvdata(pci, card); ++ card = NULL; /* probe succeeded, don't release as error */ + dev++; + 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 +@@ -54,7 +54,7 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), +- DMI_MATCH(DMI_PRODUCT_NAME, "TUF Gaming A14 FA401EA"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "FA401EA"), + }, + }, + { +@@ -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/fsl/Kconfig b/sound/soc/fsl/Kconfig +--- a/sound/soc/fsl/Kconfig ++++ b/sound/soc/fsl/Kconfig +@@ -244,7 +244,7 @@ config SND_SOC_MPC5200_AC97 + config SND_MPC52xx_SOC_PCM030 + tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" + depends on PPC_MPC5200_SIMPLE +- select SND_SOC_MPC5200_AC97 ++ depends on SND_SOC_MPC5200_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for sound on the Phytec pcm030 +@@ -253,7 +253,7 @@ config SND_MPC52xx_SOC_PCM030 + config SND_MPC52xx_SOC_EFIKA + tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" + depends on PPC_EFIKA +- select SND_SOC_MPC5200_AC97 ++ depends on SND_SOC_MPC5200_AC97 + select SND_SOC_STAC9766 + help + Say Y if you want to add support for sound on the Efika. +diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c +--- a/sound/soc/fsl/efika-audio-fabric.c ++++ b/sound/soc/fsl/efika-audio-fabric.c +@@ -22,8 +22,6 @@ + #include + #include + +-#include "mpc5200_dma.h" +- + #define DRV_NAME "efika-audio-fabric" + + SND_SOC_DAILINK_DEFS(analog, +diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c +--- a/sound/soc/fsl/pcm030-audio-fabric.c ++++ b/sound/soc/fsl/pcm030-audio-fabric.c +@@ -13,8 +13,6 @@ + + #include + +-#include "mpc5200_dma.h" +- + #define DRV_NAME "pcm030-audio-fabric" + + struct pcm030_audio_data { +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-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -3614,7 +3614,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + mask = e->mask << e->shift_l; + if (e->shift_l != e->shift_r) { +- if (item[1] > e->items) ++ if (item[1] >= e->items) + return -EINVAL; + val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; + mask |= e->mask << e->shift_r; +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/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-bore/0511-sound-updates-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch.sig new file mode 100644 index 0000000..09b8d69 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch new file mode 100644 index 0000000..9566376 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch @@ -0,0 +1,507 @@ +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 +@@ -4023,20 +4023,33 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + return -EINVAL; + } + runtime = substream->runtime; +- if (runtime->state == SNDRV_PCM_STATE_OPEN) +- return -EBADFD; +- if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) +- return -ENXIO; ++ /* don't race with buffer reallocation in hw_params/hw_free */ ++ if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) ++ return -EBUSY; ++ if (runtime->state == SNDRV_PCM_STATE_OPEN) { ++ err = -EBADFD; ++ goto out; ++ } ++ if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) { ++ err = -ENXIO; ++ goto out; ++ } + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || +- runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) +- return -EINVAL; ++ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { ++ err = -EINVAL; ++ goto out; ++ } + size = area->vm_end - area->vm_start; + offset = area->vm_pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); +- if ((size_t)size > dma_bytes) +- return -EINVAL; +- if (offset > dma_bytes - size) +- return -EINVAL; ++ if ((size_t)size > dma_bytes) { ++ err = -EINVAL; ++ goto out; ++ } ++ if (offset > dma_bytes - size) { ++ err = -EINVAL; ++ goto out; ++ } + + area->vm_ops = &snd_pcm_vm_ops_data; + area->vm_private_data = substream; +@@ -4046,6 +4059,8 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + err = snd_pcm_lib_default_mmap(substream, area); + if (!err) + atomic_inc(&substream->mmap_count); ++out: ++ atomic_dec(&runtime->buffer_accessing); + return err; + } + EXPORT_SYMBOL(snd_pcm_mmap_data); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1335,6 +1335,8 @@ static void update_legacy_names(struct snd_ump_endpoint *ump) + { + struct snd_rawmidi *rmidi = ump->legacy_rmidi; + ++ if (!rmidi) ++ return; + update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT); + update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT); + } +@@ -1343,6 +1345,8 @@ static void ump_legacy_set_rawmidi_name(struct snd_ump_endpoint *ump) + { + struct snd_rawmidi *rmidi = ump->legacy_rmidi; + ++ if (!rmidi) ++ return; + snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)", + ump->core.name); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -807,7 +807,7 @@ static int snd_dummy_capsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_el + left = ucontrol->value.integer.value[0] & 1; + right = ucontrol->value.integer.value[1] & 1; + guard(spinlock_irq)(&dummy->mixer_lock); +- change = dummy->capture_source[addr][0] != left && ++ change = dummy->capture_source[addr][0] != left || + dummy->capture_source[addr][1] != right; + dummy->capture_source[addr][0] = left; + dummy->capture_source[addr][1] = right; +diff --git a/sound/hda/codecs/cirrus/cs420x.c b/sound/hda/codecs/cirrus/cs420x.c +--- a/sound/hda/codecs/cirrus/cs420x.c ++++ b/sound/hda/codecs/cirrus/cs420x.c +@@ -571,6 +571,7 @@ static const struct hda_model_fixup cs4208_models[] = { + + static const struct hda_quirk cs4208_fixup_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS4208_MAC_AUTO), ++ SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MAC_AUTO), + {} /* terminator */ + }; + +@@ -583,6 +584,7 @@ static const struct hda_quirk cs4208_mac_fixup_tbl[] = { + SND_PCI_QUIRK(0x106b, 0x7800, "MacPro 6,1", CS4208_MACMINI), + SND_PCI_QUIRK(0x106b, 0x7b00, "MacBookPro 12,1", CS4208_MBP11), + SND_PCI_QUIRK(0x106b, 0x7f00, "iMac 16,1", CS4208_MBP11), ++ SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MBA6), + {} /* terminator */ + }; + +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 +@@ -249,6 +249,25 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + } + } + ++#define SN6140_S3_AFG_D0_DELAY_MS 1000 ++ ++static void cx_set_power_state(struct hda_codec *codec, hda_nid_t fg, ++ unsigned int power_state) ++{ ++ snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); ++ ++ /* ++ * SN6140 may not respond to AFG D0 immediately after S3. ++ * Wait before the D0 verb so the power-state command itself succeeds. ++ */ ++ if (codec->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/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -404,6 +404,7 @@ static void setup_fg_nodes(struct hdac_device *codec) + */ + int snd_hdac_refresh_widgets(struct hdac_device *codec) + { ++ hda_nid_t fg = codec->afg ? codec->afg : codec->mfg; + hda_nid_t start_nid; + int nums, err = 0; + +@@ -412,10 +413,10 @@ int snd_hdac_refresh_widgets(struct hdac_device *codec) + * widgets array. + */ + guard(mutex)(&codec->widget_lock); +- nums = snd_hdac_get_sub_nodes(codec, codec->afg, &start_nid); ++ nums = snd_hdac_get_sub_nodes(codec, fg, &start_nid); + if (!start_nid || nums <= 0 || nums >= 0xff) { + dev_err(&codec->dev, "cannot read sub nodes for FG 0x%02x\n", +- codec->afg); ++ fg); + return -EINVAL; + } + +diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c +--- a/sound/usb/caiaq/audio.c ++++ b/sound/usb/caiaq/audio.c +@@ -828,16 +828,13 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) + + cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret); + if (ret < 0) { +- kfree(cdev->data_cb_info); +- free_urbs(cdev->data_urbs_in); ++ snd_usb_caiaq_audio_free(cdev); + return ret; + } + + cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret); + if (ret < 0) { +- kfree(cdev->data_cb_info); +- free_urbs(cdev->data_urbs_in); +- free_urbs(cdev->data_urbs_out); ++ snd_usb_caiaq_audio_free(cdev); + return ret; + } + +@@ -858,6 +855,9 @@ void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + free_urbs(cdev->data_urbs_in); ++ cdev->data_urbs_in = NULL; + free_urbs(cdev->data_urbs_out); ++ cdev->data_urbs_out = NULL; + kfree(cdev->data_cb_info); ++ cdev->data_cb_info = NULL; + } +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-bore/0512-sound-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch.sig new file mode 100644 index 0000000..c02c1df Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch b/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch new file mode 100644 index 0000000..6a5a07c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0513-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-bore/0513-xps13-sof-quirk.patch.sig b/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch.sig new file mode 100644 index 0000000..3ded939 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch b/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch new file mode 100644 index 0000000..25fee81 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch @@ -0,0 +1,30 @@ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +--- a/sound/soc/codecs/rt766-sdca.c ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -936,9 +936,8 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + 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_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-bore/0514-rt766-stream-config-type.patch.sig b/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch.sig new file mode 100644 index 0000000..47fe225 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch b/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch new file mode 100644 index 0000000..47dae22 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0516-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-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig b/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig new file mode 100644 index 0000000..edb27fd Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch b/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch new file mode 100644 index 0000000..0d8e619 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0517-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-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig b/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig new file mode 100644 index 0000000..a16f394 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch b/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch new file mode 100644 index 0000000..3762b2e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch @@ -0,0 +1,41 @@ +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 +@@ -173,6 +173,19 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC10E1" }, /* PTL */ + }; + ++/* ++ * The subset of ivsc_acpi_ids[] which are IVSC, rather than CVS, devices. The ++ * CVS IDs are deliberately not listed here: new ones keep being added, whereas ++ * this list is complete. ++ */ ++static const struct acpi_device_id ivsc_only_acpi_ids[] = { ++ { "INTC1059" }, ++ { "INTC1095" }, ++ { "INTC100A" }, ++ { "INTC10CF" }, ++ { } ++}; ++ + static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) + { + unsigned int i; +@@ -224,6 +237,17 @@ static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev) + return csi_dev; + } + ++ /* ++ * The lookups below match on the ACPI companion alone. That is fine for ++ * CVS, which binds a driver to that very device, but not for IVSC: there ++ * the ACPI device also has a driverless platform device, which would be ++ * returned instead of the mei-csi client. Return NULL for IVSC so that ++ * the caller fails and the probe is retried once the IVSC device shows ++ * up. ++ */ ++ if (!acpi_match_device_ids(adev, ivsc_only_acpi_ids)) ++ return NULL; ++ + /* Try to locate CVS device on the I2C bus */ + csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev); + if (csi_dev) diff --git a/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch.sig b/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch.sig new file mode 100644 index 0000000..e516289 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch b/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch new file mode 100644 index 0000000..39ccf4d --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch @@ -0,0 +1,33 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -862,6 +862,7 @@ static const char * const acpi_honor_dep_ids[] = { + "INTC10DE", /* CVS (LNL) driver must be loaded to allow camera streaming */ + "INTC10E0", /* CVS (ARL) driver must be loaded to allow camera streaming */ + "INTC10E1", /* CVS (PTL) driver must be loaded to allow camera streaming */ ++ "INTC10FA", /* CVS (NVL) driver must be loaded to allow camera streaming */ + "RSCV0001", /* RISC-V PLIC */ + "RSCV0002", /* RISC-V APLIC */ + "RSCV0005", /* RISC-V SBI MPXY MBOX */ +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 +@@ -962,6 +962,7 @@ static const struct acpi_device_id intel_cvs_acpi_match[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ ++ { "INTC10FA" }, /* NVL */ + { } + }; + MODULE_DEVICE_TABLE(acpi, intel_cvs_acpi_match); +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 +@@ -171,6 +171,7 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ ++ { "INTC10FA" }, /* NVL */ + }; + + /* diff --git a/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch.sig b/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch.sig new file mode 100644 index 0000000..92c4d7b Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0542-media-cvs-wake-irq-without-claiming-gpio.patch b/pkgbuilds/linux-omarchy-bore/0542-media-cvs-wake-irq-without-claiming-gpio.patch new file mode 100644 index 0000000..6a78d2e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig b/pkgbuilds/linux-omarchy-bore/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig new file mode 100644 index 0000000..57011b1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0560-input.patch b/pkgbuilds/linux-omarchy-bore/0560-input.patch new file mode 100644 index 0000000..0d58569 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0560-input.patch @@ -0,0 +1,76 @@ +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 +@@ -267,7 +267,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: +@@ -1466,6 +1474,9 @@ static void psmouse_disconnect(struct serio *serio) + psmouse_deactivate(parent); + } + ++ scoped_guard(serio_pause_rx, serio) ++ psmouse->protocol_handler = NULL; ++ + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + diff --git a/pkgbuilds/linux-omarchy-bore/0560-input.patch.sig b/pkgbuilds/linux-omarchy-bore/0560-input.patch.sig new file mode 100644 index 0000000..409e402 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0560-input.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0565-i2c-asue140d-touchpad-100khz.patch b/pkgbuilds/linux-omarchy-bore/0565-i2c-asue140d-touchpad-100khz.patch new file mode 100644 index 0000000..01748b9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0565-i2c-asue140d-touchpad-100khz.patch.sig b/pkgbuilds/linux-omarchy-bore/0565-i2c-asue140d-touchpad-100khz.patch.sig new file mode 100644 index 0000000..8742aa9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0565-i2c-asue140d-touchpad-100khz.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch b/pkgbuilds/linux-omarchy-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch new file mode 100644 index 0000000..e569f4c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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 +@@ -1294,12 +1294,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-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig b/pkgbuilds/linux-omarchy-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig new file mode 100644 index 0000000..f1805db Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch b/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch new file mode 100644 index 0000000..5fe836e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch @@ -0,0 +1,47 @@ +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +index 4cc86d8d6491..43d29d69dcdb 100644 +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -512,8 +512,10 @@ tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf, + dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size, + DMA_TO_DEVICE); + if (pdf == TBSTREAM_DATA) { +- if (copy_page_from_iter(sf->page, 0, size, from) != size) ++ if (copy_page_from_iter(sf->page, 0, size, from) != size) { ++ sdev->tx_ring.cons--; + return ERR_PTR(-EFAULT); ++ } + } else { + memset(page_address(sf->page), 0, size); + } +@@ -671,7 +673,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + } + + nbytes = 0; +- while (nbytes < iov_iter_count(to)) { ++ while (iov_iter_count(to)) { + struct tbstream_frame *sf; + size_t size, sf_size; + +@@ -693,7 +695,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + } + + sf_size = tb_ring_frame_size(&sf->frame); +- size = min(iov_iter_count(to) - nbytes, sf_size); ++ size = min(iov_iter_count(to), sf_size); + + if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) { + ret = -EFAULT; +@@ -763,10 +765,10 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + } + + nbytes = 0; +- while (nbytes < iov_iter_count(from)) { ++ while (iov_iter_count(from)) { + size_t size; + +- size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE); ++ size = min(iov_iter_count(from), TB_MAX_FRAME_SIZE); + ret = tbstream_dev_send_data(sdev, from, size); + if (ret) { + /* diff --git a/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch.sig new file mode 100644 index 0000000..33208c2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch b/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch new file mode 100644 index 0000000..0cb7d2a --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch @@ -0,0 +1,477 @@ +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +index 35e3c119d5ee..a4816db5cacd 100644 +--- 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); +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +index 43d29d69dcdb..a7d9fda66875 100644 +--- 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, +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +index 557288c0274b..8367d106d81b 100644 +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -592,6 +592,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-bore/0601-usb4stream-busy-poll.patch.sig b/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch.sig new file mode 100644 index 0000000..8f8f07d Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch b/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch new file mode 100644 index 0000000..3db7549 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch @@ -0,0 +1,132 @@ +diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c +index 263a89c5f324..5ee33a69b9cf 100644 +--- a/drivers/usb/typec/altmodes/displayport.c ++++ b/drivers/usb/typec/altmodes/displayport.c +@@ -790,7 +790,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 +810,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 +index 32250b94262a..2eccdddf1b1f 100644 +--- 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 +index 0595e8cb83aa..54c7810b563b 100644 +--- 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/include/linux/usb/typec.h b/include/linux/usb/typec.h +index d61ec38216fa..10a783b738ef 100644 +--- 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/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch.sig b/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch.sig new file mode 100644 index 0000000..fd0bd60 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0620-usb-string-sanitize.patch b/pkgbuilds/linux-omarchy-bore/0620-usb-string-sanitize.patch new file mode 100644 index 0000000..d2ac981 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0620-usb-string-sanitize.patch.sig b/pkgbuilds/linux-omarchy-bore/0620-usb-string-sanitize.patch.sig new file mode 100644 index 0000000..9ed271f Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0620-usb-string-sanitize.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0650-wireguard-tstamp-type.patch b/pkgbuilds/linux-omarchy-bore/0650-wireguard-tstamp-type.patch new file mode 100644 index 0000000..87743c9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0650-wireguard-tstamp-type.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h +--- a/drivers/net/wireguard/queueing.h ++++ b/drivers/net/wireguard/queueing.h +@@ -78,12 +78,14 @@ static inline void wg_reset_packet(struct sk_buff *skb, bool encapsulating) + u8 l4_hash = skb->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-bore/0650-wireguard-tstamp-type.patch.sig b/pkgbuilds/linux-omarchy-bore/0650-wireguard-tstamp-type.patch.sig new file mode 100644 index 0000000..fa5148e Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0650-wireguard-tstamp-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch b/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch new file mode 100644 index 0000000..8789452 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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 +@@ -801,6 +801,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-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig b/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig new file mode 100644 index 0000000..ceec933 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch b/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch new file mode 100644 index 0000000..539fe9a --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch @@ -0,0 +1,32 @@ +diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c +--- a/drivers/net/wireless/realtek/rtw89/fw.c ++++ b/drivers/net/wireless/realtek/rtw89/fw.c +@@ -11780,7 +11780,7 @@ static void rtw89_fw_cmd_ofld_write_rf(struct rtw89_dev *rtwdev, + static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) + { + struct rtw89_fw_cmd_ofld_arg cmd = { +- .src = RTW89_FW_CMD_OFLD_SRC_OTHER, ++ .src = RTW89_FW_CMD_OFLD_SRC_BB, + .type = RTW89_FW_CMD_OFLD_DELAY, + .value = us, + }; +@@ -11794,7 +11794,7 @@ static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) + static void rtw89_fw_cmd_ofld_mdelay(struct rtw89_dev *rtwdev, u32 ms) + { + struct rtw89_fw_cmd_ofld_arg cmd = { +- .src = RTW89_FW_CMD_OFLD_SRC_OTHER, ++ .src = RTW89_FW_CMD_OFLD_SRC_BB, + .type = RTW89_FW_CMD_OFLD_DELAY, + .value = ms * 1000, + }; +diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h +--- a/drivers/net/wireless/realtek/rtw89/fw.h ++++ b/drivers/net/wireless/realtek/rtw89/fw.h +@@ -3142,7 +3142,6 @@ enum rtw89_fw_cmd_ofld_arg_src { + RTW89_FW_CMD_OFLD_SRC_RF, + RTW89_FW_CMD_OFLD_SRC_MAC, + RTW89_FW_CMD_OFLD_SRC_RF_DDIE, +- RTW89_FW_CMD_OFLD_SRC_OTHER, + }; + + enum rtw89_fw_cmd_ofld_arg_type { diff --git a/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch.sig b/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch.sig new file mode 100644 index 0000000..a9714f1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch b/pkgbuilds/linux-omarchy-bore/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch new file mode 100644 index 0000000..d3b514c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig b/pkgbuilds/linux-omarchy-bore/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig new file mode 100644 index 0000000..645c20d Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0700-pci-target-speed-quirk.patch b/pkgbuilds/linux-omarchy-bore/0700-pci-target-speed-quirk.patch new file mode 100644 index 0000000..1102b3f --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0700-pci-target-speed-quirk.patch.sig b/pkgbuilds/linux-omarchy-bore/0700-pci-target-speed-quirk.patch.sig new file mode 100644 index 0000000..897fa75 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0700-pci-target-speed-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch b/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch new file mode 100644 index 0000000..bd8dd7c --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch @@ -0,0 +1,31 @@ +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 +@@ -372,7 +373,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 +393,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); diff --git a/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch.sig b/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch.sig new file mode 100644 index 0000000..6a23cb7 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch b/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch new file mode 100644 index 0000000..6eda26b --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -1252,12 +1252,17 @@ static void applesmc_release_light_sensor(void) + + static int applesmc_create_key_backlight(void) + { ++ int ret; ++ + if (!smcreg.has_key_backlight) + return 0; + applesmc_led_wq = create_singlethread_workqueue("applesmc-led"); + if (!applesmc_led_wq) + return -ENOMEM; +- return led_classdev_register(&pdev->dev, &applesmc_backlight); ++ ret = led_classdev_register(&pdev->dev, &applesmc_backlight); ++ if (ret) ++ destroy_workqueue(applesmc_led_wq); ++ return ret; + } + + static void applesmc_release_key_backlight(void) diff --git a/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch.sig b/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch.sig new file mode 100644 index 0000000..d4cd8fb Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch b/pkgbuilds/linux-omarchy-bore/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch new file mode 100644 index 0000000..da85b37 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig b/pkgbuilds/linux-omarchy-bore/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig new file mode 100644 index 0000000..281de72 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch new file mode 100644 index 0000000..bb6a9de --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch @@ -0,0 +1,8751 @@ +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/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c +--- a/drivers/platform/arm64/qcom-hamoa-ec.c ++++ b/drivers/platform/arm64/qcom-hamoa-ec.c +@@ -92,8 +92,10 @@ static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp) + return ret; + else if (ret == 0 || ret == 0xff) + return -EOPNOTSUPP; ++ else if (ret != resp_len) ++ return -EIO; + +- if (resp[0] >= resp_len) ++ if (resp[0] != resp_len - 1) + return -EINVAL; + + return 0; +diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c +--- a/drivers/platform/mellanox/mlxbf-bootctl.c ++++ b/drivers/platform/mellanox/mlxbf-bootctl.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c +--- a/drivers/platform/mellanox/mlxbf-pmc.c ++++ b/drivers/platform/mellanox/mlxbf-pmc.c +@@ -2262,13 +2262,19 @@ static int mlxbf_pmc_map_counters(struct device *dev) + + static int mlxbf_pmc_probe(struct platform_device *pdev) + { +- struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); +- const char *hid = acpi_device_hid(acpi_dev); + struct device *dev = &pdev->dev; ++ struct acpi_device *acpi_dev; + struct arm_smccc_res res; ++ const char *hid; + guid_t guid; + int ret; + ++ acpi_dev = ACPI_COMPANION(&pdev->dev); ++ if (!acpi_dev) ++ return -ENODEV; ++ ++ hid = acpi_device_hid(acpi_dev); ++ + /* Ensure we have the UUID we expect for this service. */ + arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); + guid_parse(mlxbf_pmc_svc_uuid_str, &guid); +diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c +--- a/drivers/platform/surface/surface_acpi_notify.c ++++ b/drivers/platform/surface/surface_acpi_notify.c +@@ -777,12 +777,16 @@ static int san_consumer_links_setup(struct platform_device *pdev) + + static int san_probe(struct platform_device *pdev) + { +- struct acpi_device *san = ACPI_COMPANION(&pdev->dev); + struct ssam_controller *ctrl; ++ struct acpi_device *san; + struct san_data *data; + acpi_status astatus; + int status; + ++ san = ACPI_COMPANION(&pdev->dev); ++ if (!san) ++ return -ENODEV; ++ + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); +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/acer-wmi.c b/drivers/platform/x86/acer-wmi.c +--- a/drivers/platform/x86/acer-wmi.c ++++ b/drivers/platform/x86/acer-wmi.c +@@ -1581,7 +1581,9 @@ static int WMI_gaming_execute_u32_u64(u32 method_id, u32 in, u64 *out) + return -EIO; + + obj = result.pointer; +- if (obj && out) { ++ if (!obj && out) { ++ ret = -ENOMSG; ++ } else if (obj && out) { + switch (obj->type) { + case ACPI_TYPE_INTEGER: + *out = obj->integer.value; +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/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c +--- a/drivers/platform/x86/amd/hsmp/hwmon.c ++++ b/drivers/platform/x86/amd/hsmp/hwmon.c +@@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + if (attr != hwmon_power_cap) + return -EOPNOTSUPP; + ++ if (val < 0) ++ return -EINVAL; ++ + msg.num_args = 1; + msg.args[0] = val / MICROWATT_PER_MILLIWATT; + msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT; +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-wireless.c b/drivers/platform/x86/asus-wireless.c +--- a/drivers/platform/x86/asus-wireless.c ++++ b/drivers/platform/x86/asus-wireless.c +@@ -132,6 +132,10 @@ static int asus_wireless_probe(struct platform_device *pdev) + const struct acpi_device_id *id; + int err; + ++ id = acpi_match_acpi_device(device_ids, adev); ++ if (!id) ++ return -ENODEV; ++ + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; +@@ -139,6 +143,7 @@ static int asus_wireless_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, data); + + data->adev = adev; ++ data->hswc_params = (const struct hswc_params *)id->driver_data; + + data->idev = devm_input_allocate_device(&pdev->dev); + if (!data->idev) +@@ -153,12 +158,6 @@ static int asus_wireless_probe(struct platform_device *pdev) + if (err) + return err; + +- id = acpi_match_acpi_device(device_ids, adev); +- if (!id) +- return 0; +- +- data->hswc_params = (const struct hswc_params *)id->driver_data; +- + data->wq = create_singlethread_workqueue("asus_wireless_workqueue"); + if (!data->wq) + return -ENOMEM; +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) + { +@@ -5244,20 +5268,20 @@ static int asus_wmi_add(struct platform_device *pdev) + return 0; + + fail_wmi_handler: ++ asus_screenpad_exit(asus); ++fail_screenpad: + asus_wmi_backlight_exit(asus); + fail_backlight: + asus_wmi_rfkill_exit(asus); +-fail_screenpad: +- asus_screenpad_exit(asus); + fail_rfkill: + asus_wmi_led_exit(asus); + fail_leds: ++fail_custom_fan_curve: + fail_hwmon: + asus_wmi_input_exit(asus); + fail_input: + asus_wmi_sysfs_exit(asus->platform_device); + fail_sysfs: +-fail_custom_fan_curve: + fail_platform_profile_setup: + fail_fan_boost_mode: + fail_platform: +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-base.c b/drivers/platform/x86/dell/dell-wmi-base.c +--- a/drivers/platform/x86/dell/dell-wmi-base.c ++++ b/drivers/platform/x86/dell/dell-wmi-base.c +@@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l + key++; + used = 1; + } else if (type == 0x0012 && code == 0x000d && remaining > 0) { +- value = (le16_to_cpu(buffer[2]) == 2); ++ value = (le16_to_cpu(buffer[0]) == 2); + used = 1; + } + +@@ -843,9 +843,22 @@ static int __init dell_wmi_init(void) + + err = dell_privacy_register_driver(); + if (err) +- return err; ++ goto out_smbios; + +- return wmi_driver_register(&dell_wmi_driver); ++ err = wmi_driver_register(&dell_wmi_driver); ++ if (err) ++ goto out_privacy; ++ ++ return 0; ++ ++out_privacy: ++ dell_privacy_unregister_driver(); ++ ++out_smbios: ++ if (wmi_requires_smbios_request) ++ dell_wmi_events_set_enabled(false); ++ ++ return err; + } + late_initcall(dell_wmi_init); + +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/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c +--- a/drivers/platform/x86/dell/dell-wmi-privacy.c ++++ b/drivers/platform/x86/dell/dell-wmi-privacy.c +@@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void) + { + struct privacy_wmi_data *priv; + +- mutex_lock(&list_mutex); ++ guard(mutex)(&list_mutex); ++ + priv = list_first_entry_or_null(&wmi_list, + struct privacy_wmi_data, + list); +- mutex_unlock(&list_mutex); + + return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); + } +diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +--- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h ++++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +@@ -107,7 +107,7 @@ enum { + static int get_##type##_instance_id(struct kobject *kobj) \ + { \ + int i; \ +- for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ ++ for (i = 0; i < wmi_priv.type##_instances_count; i++) { \ + if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ + return i; \ + } \ +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-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +--- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c ++++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +@@ -353,6 +353,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor + case PSWD_ENCODINGS: + size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE); + for (pos_values = 0; pos_values < size; pos_values++) { ++ if (elem + pos_values >= password_obj_count) { ++ pr_err("Error elem-objects package is too small\n"); ++ return -EINVAL; ++ } ++ + ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer, + password_obj[elem + pos_values].string.length, + &str_value, &value_len); +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,11 +142,52 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { + .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, + }; + +-/* +- * A generic pointer for the currently-active board's thermal profile +- * parameters. +- */ +-static struct thermal_profile_params *active_thermal_profile_params; ++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; ++ ++static const struct thermal_profile_params *hp_wmi_thermal_profile(void) ++{ ++ if (!active_board_params) ++ return NULL; ++ ++ return active_board_params->thermal_profile; ++} + + /* DMI board names of devices that should use the omen specific path for + * thermal profiles. +@@ -157,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", +@@ -187,83 +237,103 @@ static const char * const victus_thermal_profile_boards[] = { + "8A25", + }; + +-/* DMI Board names of Victus 16-r and Victus 16-s laptops */ +-static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { ++/* DMI board-specific feature data for Omen and Victus laptops. */ ++static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .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_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .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_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .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_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, +- .driver_data = (void *)&omen_v1_no_ec_thermal_params, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, +- .driver_data = (void *)&omen_v1_no_ec_thermal_params, ++ .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") }, +@@ -271,7 +341,7 @@ static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + {}, + }; +@@ -336,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 { +@@ -504,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; + }; + +@@ -833,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) +@@ -867,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) +@@ -1128,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, +@@ -1142,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); +@@ -1793,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, +@@ -1878,7 +2102,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + u8 current_dstate, current_gpu_slowdown_temp, tp; + const struct thermal_profile_params *params; + +- params = active_thermal_profile_params; ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return -ENODEV; ++ + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + *profile = active_platform_profile; +@@ -1890,10 +2117,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + return ret; + + /* +- * We cannot use active_thermal_profile_params here, because boards +- * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically +- * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen +- * Gaming Hub on windows. Hence accept both of these values. ++ * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, ++ * but logically it should have tp == 0x30 || tp == 0x31, as ++ * corrected by the Omen Gaming Hub on windows. Hence accept both ++ * of these values. + */ + if (tp == victus_s_thermal_params.performance || + tp == omen_v1_thermal_params.performance) { +@@ -1928,12 +2155,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + + static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) + { +- struct thermal_profile_params *params; ++ const struct thermal_profile_params *params; + bool gpu_ctgp_enable, gpu_ppab_enable; + u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ + int err, tp; + +- params = active_thermal_profile_params; ++ params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + +@@ -2199,6 +2426,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = { + static int thermal_profile_setup(struct platform_device *device) + { + const struct platform_profile_ops *ops; ++ const struct thermal_profile_params *params; + int err, tp; + + if (is_omen_thermal_profile()) { +@@ -2230,13 +2458,17 @@ static int thermal_profile_setup(struct platform_device *device) + + ops = &platform_profile_victus_ops; + } else if (is_victus_s_thermal_profile()) { ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return -ENODEV; ++ + /* + * For an unknown EC layout board, platform_profile_victus_s_get_ec(), + * behaves like a wrapper around active_platform_profile, to avoid using + * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. + */ +- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || +- active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { ++ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || ++ params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + active_platform_profile = PLATFORM_PROFILE_BALANCED; + } else { + err = platform_profile_victus_s_get_ec(&active_platform_profile); +@@ -2404,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; +@@ -2416,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; +@@ -2449,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) +@@ -2478,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) +@@ -2488,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); +@@ -2518,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) +@@ -2534,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) { +@@ -2542,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: +@@ -2567,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 + }; + +@@ -2608,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, +@@ -2654,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; + } +@@ -2697,24 +2935,25 @@ static int hp_wmi_hwmon_init(void) + return 0; + } + +-static void __init setup_active_thermal_profile_params(void) ++static void __init setup_active_board_params(void) + { + const struct dmi_system_id *id; ++ const struct thermal_profile_params *params; + +- /* +- * Currently only victus_s devices use the +- * active_thermal_profile_params +- */ +- id = dmi_first_match(victus_s_thermal_profile_boards); ++ id = dmi_first_match(hp_wmi_feature_boards); + if (id) { ++ active_board_params = id->driver_data; ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return; ++ + /* + * Marking this boolean is required to ensure that + * is_victus_s_thermal_profile() behaves like a valid + * wrapper. + */ + is_victus_s_board = true; +- active_thermal_profile_params = id->driver_data; +- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { ++ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", + dmi_get_system_info(DMI_BOARD_NAME)); + } +@@ -2749,10 +2988,10 @@ static int __init hp_wmi_init(void) + } + + /* +- * Setup active board's thermal profile parameters before +- * starting platform driver probe. ++ * Setup active board feature data before starting platform ++ * driver probe. + */ +- setup_active_thermal_profile_params(); ++ setup_active_board_params(); + err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); + if (err) + goto err_unregister_device; +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,42 +88,95 @@ 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; + 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]; +@@ -154,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) +@@ -211,72 +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; +- +- 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) { +- led_classdev_notify_brightness_hw_changed( +- &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent)); +- } 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); +@@ -285,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; + } +@@ -296,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, +@@ -361,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, +@@ -403,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, +@@ -453,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; + } + +@@ -471,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); +@@ -485,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; + +@@ -563,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) +@@ -579,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) +@@ -629,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) + { +@@ -860,15 +1036,23 @@ static int acpi_probe(struct platform_device *pdev) + if (year >= 2019) + battery_limit_use_wmbb = 1; + ++ /* LEDs are optional */ ++ ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); ++ if (ret < 0) ++ kbd_backlight_available = false; ++ else ++ kbd_backlight_available = true; ++ ++ devm_led_classdev_register(&pdev->dev, &tpad_led); ++ ++ 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; + +- /* LEDs are optional */ +- led_classdev_register(&pf_device->dev, &kbd_backlight); +- led_classdev_register(&pf_device->dev, &tpad_led); +- +- wmi_input_setup(); + battery_hook_register(&battery_hook); + + return 0; +@@ -884,11 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) + { + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + +- led_classdev_unregister(&tpad_led); +- led_classdev_unregister(&kbd_backlight); +- + 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-bore/0800-platform-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch.sig new file mode 100644 index 0000000..03807cf Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0801-amd-pmf-util-unbind-use-after-free.patch b/pkgbuilds/linux-omarchy-bore/0801-amd-pmf-util-unbind-use-after-free.patch new file mode 100644 index 0000000..029ebd2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0801-amd-pmf-util-unbind-use-after-free.patch.sig b/pkgbuilds/linux-omarchy-bore/0801-amd-pmf-util-unbind-use-after-free.patch.sig new file mode 100644 index 0000000..91d6150 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0801-amd-pmf-util-unbind-use-after-free.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0850-futex-wait-multiple.patch b/pkgbuilds/linux-omarchy-bore/0850-futex-wait-multiple.patch new file mode 100644 index 0000000..d4ab07f --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0850-futex-wait-multiple.patch.sig b/pkgbuilds/linux-omarchy-bore/0850-futex-wait-multiple.patch.sig new file mode 100644 index 0000000..b8c1df3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0850-futex-wait-multiple.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0851-futex-wait-multiple-fixes.patch b/pkgbuilds/linux-omarchy-bore/0851-futex-wait-multiple-fixes.patch new file mode 100644 index 0000000..671934a --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0851-futex-wait-multiple-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0851-futex-wait-multiple-fixes.patch.sig new file mode 100644 index 0000000..f32092d Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0851-futex-wait-multiple-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0852-futex-wait-multiple-abi-fixes.patch b/pkgbuilds/linux-omarchy-bore/0852-futex-wait-multiple-abi-fixes.patch new file mode 100644 index 0000000..b0d6ba3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/0852-futex-wait-multiple-abi-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0852-futex-wait-multiple-abi-fixes.patch.sig new file mode 100644 index 0000000..4a42013 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0852-futex-wait-multiple-abi-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch b/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch new file mode 100644 index 0000000..f340536 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch @@ -0,0 +1,132 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -54,13 +54,14 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea + write_unlock(&shrinker->lock); + } + +-static s64 __xe_shrinker_walk(struct xe_device *xe, ++static int __xe_shrinker_walk(struct xe_device *xe, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +- unsigned long to_scan, unsigned long *scanned) ++ unsigned long to_scan, unsigned long *scanned, ++ unsigned long *freed) + { + unsigned int mem_type; +- s64 freed = 0, lret; ++ s64 lret; + + for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { + struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type); +@@ -82,7 +83,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + if (lret < 0) + return lret; + +- freed += lret; ++ *freed += lret; + if (*scanned >= to_scan) + break; + } +@@ -90,7 +91,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + xe_assert(xe, !IS_ERR(ttm_bo)); + } + +- return freed; ++ return 0; + } + + /* +@@ -99,40 +100,35 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + * add writeback. This avoids stalls and explicit writebacks with light or + * moderate memory pressure. + */ +-static s64 xe_shrinker_walk(struct xe_device *xe, ++static int xe_shrinker_walk(struct xe_device *xe, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +- unsigned long to_scan, unsigned long *scanned) ++ unsigned long to_scan, unsigned long *scanned, ++ unsigned long *freed) + { + bool no_wait_gpu = true; + struct xe_bo_shrink_flags save_flags = flags; +- s64 lret, freed; ++ int ret; + + swap(no_wait_gpu, ctx->no_wait_gpu); + save_flags.writeback = false; +- lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); ++ ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); + swap(no_wait_gpu, ctx->no_wait_gpu); +- if (lret < 0 || *scanned >= to_scan) +- return lret; ++ if (ret || *scanned >= to_scan) ++ return ret; + +- freed = lret; + if (!ctx->no_wait_gpu) { +- lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); +- if (lret < 0) +- return lret; +- freed += lret; +- if (*scanned >= to_scan) +- return freed; ++ ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, ++ freed); ++ if (ret || *scanned >= to_scan) ++ return ret; + } + +- if (flags.writeback) { +- lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned); +- if (lret < 0) +- return lret; +- freed += lret; +- } ++ if (flags.writeback) ++ ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, ++ freed); + +- return freed; ++ return ret; + } + + static unsigned long +@@ -214,7 +210,6 @@ 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); +- s64 lret; + + nr_to_scan = sc->nr_to_scan; + +@@ -225,12 +220,9 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + /* Might need runtime PM. Try to wake early if it looks like it. */ + runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); + +- if (purgeable && nr_scanned < nr_to_scan) { +- lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, +- nr_to_scan, &nr_scanned); +- if (lret >= 0) +- freed += lret; +- } ++ if (purgeable && nr_scanned < nr_to_scan) ++ xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; + if (nr_scanned >= nr_to_scan || !can_backup) +@@ -242,10 +234,8 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + shrink_flags.purge = false; + +- lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, +- nr_to_scan, &nr_scanned); +- if (lret >= 0) +- freed += lret; ++ xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; + out: diff --git a/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch.sig b/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch.sig new file mode 100644 index 0000000..bbe1ce3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch b/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch new file mode 100644 index 0000000..29ccecf --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch @@ -0,0 +1,149 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -54,13 +54,33 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea + write_unlock(&shrinker->lock); + } + +-static int __xe_shrinker_walk(struct xe_device *xe, ++static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) ++{ ++ struct xe_device *xe = shrinker->xe; ++ ++ if (xe_pm_runtime_get_if_active(xe)) ++ return true; ++ ++ if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { ++ xe_pm_runtime_get(xe); ++ return true; ++ } ++ ++ queue_work(xe->unordered_wq, &shrinker->pm_worker); ++ ++ return false; ++} ++ ++static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) + { ++ struct xe_device *xe = shrinker->xe; + unsigned int mem_type; ++ bool rpm = false; ++ int ret = 0; + s64 lret; + + for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { +@@ -75,23 +95,36 @@ static int __xe_shrinker_walk(struct xe_device *xe, + if (!man || !man->use_tt) + continue; + ++ if (mem_type != XE_PL_SYSTEM && !rpm && ++ xe_device_is_l2_flush_optimized(xe)) { ++ if (!__xe_shrinker_runtime_pm_get(shrinker)) ++ break; ++ rpm = true; ++ } ++ + ttm_bo_lru_for_each_reserved_guarded(&curs, man, &arg, ttm_bo) { + if (!ttm_bo_shrink_suitable(ttm_bo, ctx)) + continue; + + lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned); +- if (lret < 0) +- return lret; ++ if (lret < 0) { ++ ret = lret; ++ goto out; ++ } + + *freed += lret; + if (*scanned >= to_scan) +- break; ++ goto out; + } + /* Trylocks should never error, just fail. */ + xe_assert(xe, !IS_ERR(ttm_bo)); + } + +- return 0; ++out: ++ if (rpm) ++ xe_pm_runtime_put(xe); ++ ++ return ret; + } + + /* +@@ -100,7 +133,7 @@ static int __xe_shrinker_walk(struct xe_device *xe, + * add writeback. This avoids stalls and explicit writebacks with light or + * moderate memory pressure. + */ +-static int xe_shrinker_walk(struct xe_device *xe, ++static int xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, + unsigned long to_scan, unsigned long *scanned, +@@ -112,20 +145,21 @@ static int xe_shrinker_walk(struct xe_device *xe, + + swap(no_wait_gpu, ctx->no_wait_gpu); + save_flags.writeback = false; +- ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); ++ ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, ++ freed); + swap(no_wait_gpu, ctx->no_wait_gpu); + if (ret || *scanned >= to_scan) + return ret; + + if (!ctx->no_wait_gpu) { +- ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, ++ ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, + freed); + if (ret || *scanned >= to_scan) + return ret; + } + + if (flags.writeback) +- ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, ++ ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned, + freed); + + return ret; +@@ -176,16 +210,7 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, + return false; + } + +- if (!xe_pm_runtime_get_if_active(xe)) { +- if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { +- xe_pm_runtime_get(xe); +- return true; +- } +- queue_work(xe->unordered_wq, &shrinker->pm_worker); +- return false; +- } +- +- return true; ++ return __xe_shrinker_runtime_pm_get(shrinker); + } + + static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +@@ -221,7 +246,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); + + if (purgeable && nr_scanned < nr_to_scan) +- xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ xe_shrinker_walk(shrinker, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; +@@ -234,7 +259,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + shrink_flags.purge = false; + +- xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ xe_shrinker_walk(shrinker, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; diff --git a/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch.sig b/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch.sig new file mode 100644 index 0000000..0707c41 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch b/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch new file mode 100644 index 0000000..6af456f --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch @@ -0,0 +1,39 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -71,6 +71,12 @@ static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) + return false; + } + ++static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) ++{ ++ if (runtime_pm) ++ xe_pm_runtime_put(shrinker->xe); ++} ++ + static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +@@ -121,8 +127,7 @@ static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + } + + out: +- if (rpm) +- xe_pm_runtime_put(xe); ++ xe_shrinker_runtime_pm_put(shrinker, rpm); + + return ret; + } +@@ -213,12 +218,6 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, + return __xe_shrinker_runtime_pm_get(shrinker); + } + +-static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +-{ +- if (runtime_pm) +- xe_pm_runtime_put(shrinker->xe); +-} +- + static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); diff --git a/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch.sig b/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch.sig new file mode 100644 index 0000000..ddd9329 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch new file mode 100644 index 0000000..0a31aa0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch @@ -0,0 +1,381 @@ +diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h +--- c/include/linux/mmzone.h ++++ i/include/linux/mmzone.h +@@ -1465,6 +1465,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 +@@ -1529,6 +1562,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 c/include/linux/shrinker.h i/include/linux/shrinker.h +--- c/include/linux/shrinker.h ++++ i/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 c/mm/internal.h i/mm/internal.h +--- c/mm/internal.h ++++ i/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 c/mm/shrinker.c i/mm/shrinker.c +--- c/mm/shrinker.c ++++ i/mm/shrinker.c +@@ -474,7 +474,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; +@@ -536,6 +536,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); +@@ -595,7 +596,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; + } +@@ -607,6 +609,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. + * +@@ -622,7 +625,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; +@@ -635,7 +638,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. +@@ -664,6 +668,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 c/mm/vmscan.c i/mm/vmscan.c +--- c/mm/vmscan.c ++++ i/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-bore/8204-mm-opportunistic-compaction.patch.sig b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch.sig new file mode 100644 index 0000000..1b113b3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8205-mm-hint-uses-allocation-order.patch b/pkgbuilds/linux-omarchy-bore/8205-mm-hint-uses-allocation-order.patch new file mode 100644 index 0000000..f579b27 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8205-mm-hint-uses-allocation-order.patch @@ -0,0 +1,236 @@ +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1680,7 +1680,7 @@ enum kswapd_clear_hopeless_reason { + }; + + void wakeup_kswapd(struct zone *zone, gfp_t gfp_mask, int order, +- enum zone_type highest_zoneidx); ++ bool opportunistic, enum zone_type highest_zoneidx); + void kswapd_try_clear_hopeless(struct pglist_data *pgdat, + unsigned int order, int highest_zoneidx); + void kswapd_clear_hopeless(pg_data_t *pgdat, enum kswapd_clear_hopeless_reason reason); +diff --git a/include/linux/swap.h b/include/linux/swap.h +--- a/include/linux/swap.h ++++ b/include/linux/swap.h +@@ -348,7 +348,8 @@ extern void swap_setup(void); + /* linux/mm/vmscan.c */ + extern unsigned long zone_reclaimable_pages(struct zone *zone); + extern unsigned long try_to_free_pages(struct zonelist *zonelist, int order, +- gfp_t gfp_mask, nodemask_t *mask); ++ int alloc_order, gfp_t gfp_mask, ++ nodemask_t *mask); + unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone_idx); + + #define MEMCG_RECLAIM_MAY_SWAP (1 << 1) +diff --git a/mm/internal.h b/mm/internal.h +--- a/mm/internal.h ++++ b/mm/internal.h +@@ -1765,6 +1765,28 @@ void __meminit __init_single_page(struct page *page, unsigned long pfn, + unsigned long zone, int nid); + void __meminit __init_page_from_nid(unsigned long pfn, int nid); + ++/* ++ * Is this allocation eligible for the "opportunistic compaction" treatment ++ * in kswapd and the 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. ++ * ++ * @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. ++ */ ++static inline bool gfp_opportunistic_compaction(gfp_t gfp_flags, int order) ++{ ++ if (!order || (gfp_flags & __GFP_NOFAIL)) ++ return false; ++ ++ return !!(gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)); ++} ++ + /* shrinker related functions */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + int priority, bool opportunistic_compaction); +diff --git a/mm/migrate.c b/mm/migrate.c +--- a/mm/migrate.c ++++ b/mm/migrate.c +@@ -2727,7 +2727,7 @@ int migrate_misplaced_folio_prepare(struct folio *folio, + return -EAGAIN; + + wakeup_kswapd(pgdat->node_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 +@@ -3422,7 +3422,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); +@@ -4441,6 +4441,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; +@@ -4453,7 +4454,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); +@@ -4480,7 +4481,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; + +@@ -4512,19 +4513,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-bore/8205-mm-hint-uses-allocation-order.patch.sig b/pkgbuilds/linux-omarchy-bore/8205-mm-hint-uses-allocation-order.patch.sig new file mode 100644 index 0000000..7c3ef42 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8205-mm-hint-uses-allocation-order.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch b/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch new file mode 100644 index 0000000..92b39ef --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch @@ -0,0 +1,232 @@ +diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h +--- c/include/linux/mmzone.h ++++ i/include/linux/mmzone.h +@@ -1498,6 +1498,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 +@@ -1561,14 +1587,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 c/mm/mm_init.c i/mm/mm_init.c +--- c/mm/mm_init.c ++++ i/mm/mm_init.c +@@ -1553,7 +1553,8 @@ void __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 c/mm/vmscan.c i/mm/vmscan.c +--- c/mm/vmscan.c ++++ i/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-bore/8206-mm-carry-order-and-hint-in-one-word.patch.sig b/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch.sig new file mode 100644 index 0000000..de65251 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8207-mm-classify-huge-page-allocations-as-failable.patch b/pkgbuilds/linux-omarchy-bore/8207-mm-classify-huge-page-allocations-as-failable.patch new file mode 100644 index 0000000..bfa7d1e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/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-bore/8207-mm-classify-huge-page-allocations-as-failable.patch.sig b/pkgbuilds/linux-omarchy-bore/8207-mm-classify-huge-page-allocations-as-failable.patch.sig new file mode 100644 index 0000000..e60df1b Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8207-mm-classify-huge-page-allocations-as-failable.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch b/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch new file mode 100644 index 0000000..8de9983 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch @@ -0,0 +1,21 @@ +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -4476,6 +4476,17 @@ static unsigned long deferred_split_count(struct shrinker *shrink, + { + unsigned long count; + ++ /* ++ * Splitting a partially mapped folio during an opportunistic ++ * high-order pass frees its unmapped tail pages but shatters the ++ * folio, so the pass works against the contiguous block it is ++ * reclaiming for. Report nothing rather than SHRINK_EMPTY: for a ++ * memcg aware shrinker SHRINK_EMPTY clears the memcg shrinker bit, ++ * which would also skip the next normal pass. ++ */ ++ if (sc->opportunistic_compaction) ++ return 0; ++ + count = list_lru_shrink_count(&deferred_split_lru, sc); + return count ?: SHRINK_EMPTY; + } diff --git a/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch.sig b/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch.sig new file mode 100644 index 0000000..88c17d4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8209-xe-shrinker-use-opportunistic-hint.patch b/pkgbuilds/linux-omarchy-bore/8209-xe-shrinker-use-opportunistic-hint.patch new file mode 100644 index 0000000..0552be0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8209-xe-shrinker-use-opportunistic-hint.patch @@ -0,0 +1,39 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -174,10 +174,17 @@ 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; ++ unsigned long num_pages = 0; + bool can_backup = !!(sc->gfp_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-bore/8209-xe-shrinker-use-opportunistic-hint.patch.sig b/pkgbuilds/linux-omarchy-bore/8209-xe-shrinker-use-opportunistic-hint.patch.sig new file mode 100644 index 0000000..7928b4e Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8209-xe-shrinker-use-opportunistic-hint.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8210-xe-shrinker-single-backup-decision.patch b/pkgbuilds/linux-omarchy-bore/8210-xe-shrinker-single-backup-decision.patch new file mode 100644 index 0000000..9daf161 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8210-xe-shrinker-single-backup-decision.patch @@ -0,0 +1,75 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -170,27 +170,28 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + ++static inline bool xe_shrinker_can_backup(const struct shrink_control *sc) ++{ ++ return (sc->gfp_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-bore/8210-xe-shrinker-single-backup-decision.patch.sig b/pkgbuilds/linux-omarchy-bore/8210-xe-shrinker-single-backup-decision.patch.sig new file mode 100644 index 0000000..a823434 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8210-xe-shrinker-single-backup-decision.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch b/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch new file mode 100644 index 0000000..ff35b72 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch @@ -0,0 +1,12 @@ +diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile +--- a/tools/bpf/bpftool/Makefile ++++ b/tools/bpf/bpftool/Makefile +@@ -99,7 +99,7 @@ endif + HOST_LDFLAGS := $(LDFLAGS) + + # Remove warnings for libbpf bootstrap build +-LIBBPF_BOOTSTRAP_CFLAGS := $(filter-out -W -Wall -Wextra -Wformat -Wformat-signedness,$(HOST_CFLAGS)) ++LIBBPF_BOOTSTRAP_CFLAGS := $(filter-out -W -Wall -Wextra -Wformat%,$(HOST_CFLAGS)) + + INSTALL ?= install + RM ?= rm -f diff --git a/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch.sig b/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch.sig new file mode 100644 index 0000000..7b95eee Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/PKGBUILD b/pkgbuilds/linux-omarchy-bore/PKGBUILD new file mode 100644 index 0000000..5fcf831 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/PKGBUILD @@ -0,0 +1,732 @@ +# Maintainer: The Omarchy Authors +# Adopted from the Arch Linux linux package. + +pkgbase=linux-omarchy-bore +pkgver=7.2.5 +pkgrel=4 +pkgdesc='Omarchy Linux (BORE CPU scheduler, ADIOS I/O scheduler)' +url='https://omarchy.org' +arch=( + x86_64 +) +license=(GPL-2.0-only) +makedepends=( + bc + binutils + cpio + gettext + glibc + libelf + libgcc + openssl + pahole + perl + python + rust + rust-bindgen + rust-src + tar + xxhash + xz + zlib + zstd +) +options=( + !debug + !strip +) +_major=${pkgver%%.*} +if [[ $pkgver == *rc* ]]; then + _base=${pkgver%rc*} + _prev=${_base%.*}.$(( ${_base##*.} - 1 )) + _srcname=linux-${_prev%.0} + _rcpatch=patch-${pkgver//rc/-rc} +else + _srcname=linux-$pkgver +fi +source=( + https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/${_srcname}.tar.{xz,sign} + ${_rcpatch:+https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/stable-review/${_rcpatch}.xz} + ${_rcpatch:+https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/stable-review/${_rcpatch}.sign} + 0010-archlinux-base.patch{,.sig} + 0110-bore-6.8.0.patch{,.sig} + 0120-tlbpull.patch{,.sig} + 0121-smp-preempt.patch{,.sig} + 0130-sched-detach-tasks.patch{,.sig} + 0131-sched-avg-idle.patch{,.sig} + 0140-sched-always-inline.patch{,.sig} + 0141-sched-urgent-fixes.patch{,.sig} + 0142-sched-itmt-no-debugfs-dependency.patch{,.sig} + 0143-sched-hybrid-cluster-balancing.patch{,.sig} + 0144-sched-nohz-idle-core.patch{,.sig} + 0150-adios-3.2.0.patch{,.sig} + 0200-idle.patch{,.sig} + 0210-pstate.patch{,.sig} + 0211-amd-pstate-fixes.patch{,.sig} + 0212-amd-pstate-epp-cache.patch{,.sig} + 0250-zsmalloc.patch{,.sig} + 0260-mglru-exec-protect.patch{,.sig} + 0270-ksm-rmap-walk.patch{,.sig} + 0280-mm-updates.patch{,.sig} + 0290-zstd-bmi2-fallback-aliases.patch{,.sig} + 0291-zstd-bmi2-cpu-feature-dispatch.patch{,.sig} + 0292-crypto-zstd-defer-cstream-init.patch{,.sig} + 0293-crypto-zstd-defer-dstream-init.patch{,.sig} + 0295-af-alg-restrict.patch{,.sig} + 0296-x86-mm-pmd-modify-keep-dirty-bit.patch{,.sig} + 0300-btrfs.patch{,.sig} + 0301-btrfs-fixes.patch{,.sig} + 0302-btrfs-zstd-decompress-direct-to-page.patch{,.sig} + 0310-fuse-eof-zeroing.patch{,.sig} + 0311-fuse-perf.patch{,.sig} + 0312-fuse-writethrough-uptodate.patch{,.sig} + 0313-fuse-background-wakeup.patch{,.sig} + 0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch{,.sig} + 0360-gpu-mem-cgroup.patch{,.sig} + 0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch{,.sig} + 0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch{,.sig} + 0402-psr2-early-transport-panels.patch{,.sig} + 0411-drm-xe-display-no-stolen-framebuffers.patch{,.sig} + 0420-safe-window.patch{,.sig} + 0430-fbc.patch{,.sig} + 0440-xe3-peak-bandwidth.patch{,.sig} + 0450-amd-hdmi-vrr-allm.patch{,.sig} + 0451-amd-vtem-tmds-links.patch{,.sig} + 0452-amd-hdmi-frl-default.patch{,.sig} + 0460-vesa-displayid-dsc-bpp.patch{,.sig} + 0461-vesa-dsc-passthru-mode-match-fix.patch{,.sig} + 0472-amdgpu-userq-post-reset-error.patch{,.sig} + 0473-i915-ptl-cdclk-sanitize.patch{,.sig} + 0474-amd-display-oled-vesa-backlight.patch{,.sig} + 0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch{,.sig} + 0510-sound-updates.patch{,.sig} + 0511-sound-updates-fixes.patch{,.sig} + 0512-sound-fixes.patch{,.sig} + 0513-xps13-sof-quirk.patch{,.sig} + 0514-rt766-stream-config-type.patch{,.sig} + 0516-hda-realtek-rog-strix-g733zw-speakers.patch{,.sig} + 0517-asoc-amd-yc-acer-aspire-a314-23p.patch{,.sig} + 0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch{,.sig} + 0541-cvs-nova-lake-acpi-id.patch{,.sig} + 0542-media-cvs-wake-irq-without-claiming-gpio.patch{,.sig} + 0560-input.patch{,.sig} + 0565-i2c-asue140d-touchpad-100khz.patch{,.sig} + 0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch{,.sig} + 0600-usb4stream-fixes.patch{,.sig} + 0601-usb4stream-busy-poll.patch{,.sig} + 0610-typec-cable-altmode-check.patch{,.sig} + 0620-usb-string-sanitize.patch{,.sig} + 0650-wireguard-tstamp-type.patch{,.sig} + 0660-btusb-mediatek-mt7922-13d3-3625.patch{,.sig} + 0661-rtw89-command-offload-source.patch{,.sig} + 0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch{,.sig} + 0700-pci-target-speed-quirk.patch{,.sig} + 0750-applesmc-cache-race.patch{,.sig} + 0751-applesmc-key-backlight-workqueue-leak.patch{,.sig} + 0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch{,.sig} + 0800-platform-updates.patch{,.sig} + 0801-amd-pmf-util-unbind-use-after-free.patch{,.sig} + 0850-futex-wait-multiple.patch{,.sig} + 0851-futex-wait-multiple-fixes.patch{,.sig} + 0852-futex-wait-multiple-abi-fixes.patch{,.sig} + 8201-xe-shrinker-return-freed-page-count.patch{,.sig} + 8202-xe-shrinker-runtime-pm-for-non-system-memory.patch{,.sig} + 8203-xe-shrinker-release-through-the-put-helper.patch{,.sig} + 8204-mm-opportunistic-compaction.patch{,.sig} + 8205-mm-hint-uses-allocation-order.patch{,.sig} + 8206-mm-carry-order-and-hint-in-one-word.patch{,.sig} + 8207-mm-classify-huge-page-allocations-as-failable.patch{,.sig} + 8208-mm-thp-deferred-split-uses-hint.patch{,.sig} + 8209-xe-shrinker-use-opportunistic-hint.patch{,.sig} + 8210-xe-shrinker-single-backup-decision.patch{,.sig} + 9999-bpftool-strip-wformat-bootstrap.patch{,.sig} +) +source_x86_64=(config.x86_64) +validpgpkeys=( + ABAF11C65A2970B130ABE3C479BE3E4300411886 # Linus Torvalds + 647F28654894E3BD457199BE38DBBDC86092693E # Greg Kroah-Hartman + 12D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7 # Krzysztof Wilczynski +) +b2sums=('48551bee71cd02815136fb8abe7da4464c2e17c89ef35cb0c0530c8b969fe12127ca97ab6c656ea8e5b29f2c4a8fe4cc143a626f4cca6b972a8105991e4c6905' + 'SKIP' + '6d92fb81077232b8cd1ae500b3aabc71434792e750ea6126dc095e50d4278d3e42b3cfdc3c8bc693ea14b109f6adfa3a1ed095187fc5501962fbe8f049f867bc' + 'SKIP' + 'a32edb39b4ee9c0378239f4998f477fe5371e6931c188d660874b360ca6d3bdcfff71cf088bed071365627d87458eca0be625e154ca3d1e8150e5e0bdf64175c' + 'SKIP' + 'f6c00ac2400580dffe3605d2693809396e18185f0c59b21b86f1f22a1f8d6529c51e8cc2474bc40f74a96631f415da51521a8b35d5b096e9b9d164c2755fb091' + 'SKIP' + '4651980c9d988ed73dc91fd440467de0a1de21efa3efae0fc006524c88af40173182ce6826c212e8b7faaaae1aa512fc755272ec4663c185533e65c936780db3' + 'SKIP' + '57f88a64a8ec20794c008c9de70b3f01680ce9023925df8e0f2c7be69f2600e10276d54bd264f5b8b258e04d38cf483cbf83bb79df466f17fe4b6b33fec52a76' + 'SKIP' + '050d38240747325e90bb86bb7c47a14573dcb297ee323b3d84f74df9ddf3691cdf32ac9e293bd237c9b09221065b002e297745f4b898a937b9347dacb2de7109' + 'SKIP' + 'd21fa7cb609188720365a8d95a98c3286f2f32a772309e66913c6b16bcf871ed28ea1ff01fc15ecbb47a02def1a557512126e39ac0daa956a1ba89878acf5de5' + 'SKIP' + 'cd744c8f861d2cfdfdc98ea6875419a298dcb5d478444b8e551a52fe7ce5279e2d977f8610783d79144be4927a8bf41b2f80113e6f7aa3ea02f54b9984446017' + 'SKIP' + '3434a5bbcd2bd8b4e2013400bf4d771ad5c8295ec74f67ff7acaf917954a01d25931679a71ddb8995fd7703b0b9904db4e6fe5f384d3705cf4d7b80dbf5dba55' + 'SKIP' + 'c8b8bb1ec676378d862f64b23445f170f2e995f3d89f4ffc727c377128ace87417e50d58c39e62d62795924ab4c62f3defad64139e3d3b472b75f6431484e2dd' + 'SKIP' + 'c9c1be8f72cef610592b1815a81a34706b54f2ef4a188acbd31d98243129e2939fe0c2b679f428a67eac70b069c8a662c3629c598f89430930f5b8bb602aada8' + 'SKIP' + '4f58fc77bc13fe9492fe4662e0a4b879a8a1616e271c2095959dccdffed9b0a866a4eb9dc3ac74fad9eed11c362e43a7bfa56d8ea6f571572a63ff4550c5dc23' + 'SKIP' + 'd1007ee452cdf92967ec7055f1f39a3007a69842040e12f5063a1d2f24fd6d1eeaff2357abf96d0e330541a4781f93fecbcb116a4c73371913582eb72b8f9466' + 'SKIP' + '0d3309140e496ad552d1a15d4fbf04b5ad03877bc73ae689f57554e91b7458578f2c88b6bbd37dd9fdf822a8d48c2cd6f961ec735ae5887f96fa51d908d203ee' + 'SKIP' + '13beae6f8d23a1f075410f50c09bc94f24f505571af1feb3cee58f732c0d649a9ce5939bc11642c4f3c8dfbd967939b2a0a205220abf06f10d559c4c43a946a3' + 'SKIP' + '2f24f88d8f960148046c36222fdaaadfed2ca88c60240fa1d111430898192ca45aeaa93a3afa24ad02b11c4dc8c3904a92f17c0284396f2f43352ad934a35056' + 'SKIP' + '9809e258eb684b80d508fa2f2fa270683b62bd1412ed05a970114aaa7d84c37c0beaea51f9ff8c6ee1bc7318c4aee3725e9a1131fefb51ba700801de6ac414c5' + 'SKIP' + 'f115e8c80d723f7c24e6b1638b801cd664bef20acb1d2f6e6f5612744b916278693011782228cd3f4d0518654b8d53309bc5b06bbe8ed722fab9995f369bf548' + 'SKIP' + '31abdd15ae3f9709d6912c90319a0b62d8380e1ae1169575c0e8e419f8ce264a7cf7c72558a85719cd5a1cae7341007adb3e3936268999ebd5215c2ed288f22c' + 'SKIP' + '4efb9ab5258aee01b07bc322b160d2f6e3571ff82c4f3e317cab152a919c67522629d56fb79d517da034373cd6924bc3a20c98073197c93590feb1a34e902358' + 'SKIP' + '55951c59286df43f069302b0b1036424812f80b45c5ea13219fce1bfc3e831731432c216288d722e44493c736cbf89a3e44801b5a4f1f242c17b5a89b236a5ef' + 'SKIP' + '737db39c2f7f46550de4d6d80659bd6c1fc2bf1ca42bd7bd6fc345fc90af995acd056051e12175ac6c957333629a017051d76d5359c871158c93f0a294fb8de5' + 'SKIP' + '4d0646c8ad139add6abb62d75308d2d7b5b07afbc74a0f0297ffc2148f0db38abaea16b048704187da49fe3ddee8370b10a7a41d6e62b665650cd79e8c392b4e' + 'SKIP' + 'bd7bce1bf0ee24ce201fe0e5f0b8a6c829a84d224800547cf97a893326381e2e6eaed9c70529a6864b0c338276b3a307b14e26fba221e62263b90c02eb5e5f34' + 'SKIP' + 'ad2cfdc3d8de2193110325395e11015577f26ef1081307bb236ed73fbd109822dd44ea026a57c2e17233e1fb7982e78aa986b71c043f42887e6c5d7f51b84ea9' + 'SKIP' + '19c3dd32bbe3ed7caec86e90a6af39b07eb99d5a36515cf061bb6979e5a403ca98a0e00e584a9f746330d663e5fc0868ba7fc98ab2df264bc863bdf22ef3dd5e' + 'SKIP' + 'ed2ee4849b715d80ba103fe090669062fc08c6864c64e8f8291f7f21092f0184ca5528bdd3a9318f077ef00cbe8cf28bacd1e7a9cb9f9ae1047becd930a0c42e' + 'SKIP' + 'f09e80ed25fdda9b2c79bd5ea3701985596d478b0b4e6948a1ea8fb789ef944e71640ae200442afcc23a9f0e07b34d83365e223b620baf776bbd864977e633a0' + 'SKIP' + 'a51e5f2a5e37c350fb4d715ece644c565a508970c3aeb1d28cb6cd03a0c7316a1935e028b64f20a03818926c611b50ae0ffd77d04c676d604d303beed0f96e1f' + 'SKIP' + '25dcd2c4a4287f26c01419b7d6430629d2827b6efff473361c68d6ec44e94e6d2bb8ab8215dbae5da25781a668f571cfc28b60c5693eb4ed629bf80fe2f68ccf' + 'SKIP' + '8d6eeb2330a3ca7c522cef77bf7336fac9444a0884d5d101c933abd3a31a3711529a31f9a181d4795148ecbcc24425750587314ce28e44c9ab2fd2de8365e853' + 'SKIP' + 'fd487cb02212ffdae5d8beb7d895819377686085b95658dcf597a534b017adcb65dfdf06e0276e7322a46dc28118803b05c246c22e7d3c361f1778ed9e12a3c6' + 'SKIP' + 'e18e5086c531a37837f6e7df72ce10a38289b02de4cea376772d45cccb5aabc628fe4f3f9607f646903a7cf59b02692703e6b26800604cf958136987df0b4d5d' + 'SKIP' + '1b340a502dcf5a1680caf5320b3aa72aa4317e4cf1d877c1f6425e281a5668d5f092417e1f282371c0a211344f5c187fb3bdaf3f97c7e122951edf7e4c08ca58' + 'SKIP' + '1124c5c3fd5104c886c841ead1ae9afb42683388b640b1a86523dad9919c6bd59a1f78b07ee5a213e0dca77c636a29676af0592d4ed1735ea35f282f6f23e46f' + 'SKIP' + '505bf01a9d6b0926b557b673bb686d1a4ff73ee70539991e52272fbf58aad3ea25cf5ffc5c2d094a2d5e78b18a1a134f2ab530f595073a2d3eba9ff2c1418e1e' + 'SKIP' + 'b33b46c37f12f7650a67c3731b87ff84add1ac8623cc96b52223a1ef9946bcaa990ed7c7036674e228335f57ec9ed421ea4d6c1dd72d19b516215d9ac2c7c3ee' + 'SKIP' + 'b4bf4c4c0672bad91d7f8d309e741104918dd8ee8688a00d600379eba5828d1b8157953377c73d0b58050b4ff888c43013ede4e9c959be8d9c028a6143bf58ed' + 'SKIP' + 'e403bdc300284c4f0f4302e501b8ccfe5e9af04e73a6070eb6615322f5f40ed6edad53af2ed278741f61a4e3811b92faf1af02f113dbcc2a19bde335a55930d7' + 'SKIP' + '5de29d0eb5264e24ab9228bea2ea4b549b3fe37ea67c045df9f83064f11c2f557b9641e772d28db59f8d258418c77435b66ff471c3695043f0f8275d5e7b1685' + 'SKIP' + '3c2cd960103ad97ce35679f906b3c9be79a9744959c5dfd0ea338d8075b698790934694b2f71fde026b164c5246cba71ffe3cd5dfc1464da338136fbbaea2f1c' + 'SKIP' + '2b33b11e48d8dd8d6743f26170fb99eae38541e3665e2448d91cedf729cca7c0278bfba1078ecbe88201eac4407ec5bd0292a97dbab9cfc67e42646712befc1f' + 'SKIP' + '71934436987ecf6914e5c559445c2b7ee92a31c467552342094c93a59d32563aa9930e710556cc1e271eeb352705a9df9b3702482f87d045d132233cee4ab48b' + 'SKIP' + '9fb1318a3a181644c67b2adcac0cb04d1b88dd0e632751967239d4e8d4698f193795d00194b0f68505964c84572b67168dcbed02ad5bb7abd41e406a62111489' + 'SKIP' + '353b7e89a7044b7426db730d5b900eb347907aa0c20e307d2317925d6021a969a343649ede7a32e698dad60dd4472eb197c9578fb0e36fc354a60266fd680cd7' + 'SKIP' + '803223224f22aca25dfe34d73e2bd49438f0cfa83b71baa545fa6fe93ee1a631b6b62c7324dc1c0d2d2ded94c36185a83af39b4f4a3362f554a1943ec2e2ec3e' + 'SKIP' + 'b0a7ab192066c472f3ebc39bc9afc9fef0528ff7c98bf67c5b9cb679cc6d93eab3d25e91d8d74c9c52f72803949a3ade7940e677b9a3114605b966df94da93bb' + 'SKIP' + '824089f9f45e2ff9e788d874bb9294b9208b4486fd22c56cb59d4d88cb996ff5a81ac22f8ff136ddffa1e40330f02e53ab759ffea7e4535c11d4f8195069966b' + 'SKIP' + '696309582c56fad530a0bf2be12bb744dedac11ed69642b14eade0a26baf1ce52b36074b89b963b40c9c6fbc4857cdc71b2a6c6d64afcbb2df716bbb57579986' + 'SKIP' + '14212fca051d3e9a85f75616ff7f95bbd119ef8e5400dafd4447f70db2c4e9c03d971ec4fdfb4db2319d2613062d74f54dfd5d0a100ab0fa27ac6c05e16bd063' + 'SKIP' + '7228e249d25a99f5106040b8ddb5705979b3a5af21ca60d0d43a9ecaf6eaccef6bde8ea69c896cffbf2b5963ec4f68987e69b184a8885c7f4c98d2b05fd59fbb' + 'SKIP' + 'a54ac1c6d43fe88dee08884ed2e5bad0a0b6d11f34a46dc9801e675860afb7641b57384248a144a621e66a73541d73da11f71671116f1cc7f21534526411439a' + 'SKIP' + '2e486cabad0d45aeb760d031c7946e9887c710b29dbe9c16e5f4bae6a230031dba4d817f1ee25a0bbca849d1c71b2f84052ffb73c4a6c4350a6ea5ee83c5a40e' + 'SKIP' + '23e2a5def526fa886d1f1b05607bfe6b1ecbb2d6009d023145adff9eadc3135f9078faca6638966b586e81e4ab9d48d23b5758504d5a043d5f46e7a701d185fa' + 'SKIP' + '5d82f32f7c04b084530722b9848254aac289c2a754a10e16821083a871ec14065dd9ee83e72b8e9e8ba2915f3fbaf17b0874df724002eb08a5f4f84d1b6aecd8' + 'SKIP' + '4928cc008dc91fddb89b154b89b1ce2bbff14d8186c0ec1f81971fb9d3b622475e93e42015916699c3442e2268680779f545d183112b57dedae96fc48a75844c' + 'SKIP' + 'f93494f5f3196cc35d017ff9719d146bf3e5e7bfe32a7f5e75d96468ce30ef367428ba83b1c99b665064d3edaec84cf3bf18211d8f7b4f118b3bdee495eb8ef9' + 'SKIP' + 'eb79554ef0d014b94bc72d4d559c2cc5419df96486acb3b6a09d5e8e9dc48db626ce0f90338c67fe873b2cc3c45c3f308c8080532b60f74a07c9e39e01d34197' + 'SKIP' + 'f657143cdd9c937d78315f4617392e60f7c46f5cec882e060f740f77a27dd4c045878cb66e568a6d9ae74e9c706ffb3052817db6f327c87b85ff2f46eea678e2' + 'SKIP' + '0acbd87a09a62af860d14f342d61c82c29ec1711038d4abf4ae473df187fbda6aa49386d88928c5dc4c3379e097478b1d40d9ebe9360e64d02e59161164b2a3f' + 'SKIP' + 'bb06066e90132f0172333d46be54ca816eb7d3230a193a6cae765d8edc50df4a6ba9fb7c0647cd6befc8c3bbbe1d96c285ee202812477d7295c1130ea8cdd6f4' + 'SKIP' + 'f1a94986b4b3f310fecf981e3619f9787a0c8cb0b4fb3f39e5fe85ee3400e7e50278d41bf5468f65e5bd890358d2aabe28d3e11ee838ccbda2ac7af16be568d9' + 'SKIP' + '44dcaad2ab326ebee62d3e63207a161dcb1b63bdb21412b5624ca7875bb6e99997528683a633d7dd526dc5ae9408e73bd066e2a1e5b34ba5c829162df2265346' + 'SKIP' + '162a2c2104d5f036657ace6eef2ac2d533c0934376fab2521ff1d3fccd704e3d08fa4f6162e9100a844db2aeb57fa3ce214b8058bd227f38b058ab7606ee7816' + 'SKIP' + 'd50d5a69087a5ba6c416a6391232764c8281a8094645175fdb2cfc1bb011e33335ae4e96d79364681bea61edcb58ba9acb491e08c301d0667481759d0ee1e183' + 'SKIP' + '539b1d0e764f0ace3e572ac49f20a790183155ddc0bcd3a8d040f44c9891dd36b1a0accb8c0288267afbd33e6ceadf6fc3a5e3db40491c695e0c1d3dd64af40c' + 'SKIP' + 'f5cae6eabf2066d5deef737a15967d5d11340de34b181f3b3d841023507fc58af2b68926eb04bb342e1adb857165400f862f2b35ffec4204a107b3b22a1571e7' + 'SKIP' + 'ab28a1f4f1756b8451840566d8100cb3d0b498b13d006593c72829b9783e94900bae78ab47c576d156e1d08c56602274b3f7d5bd885de17336623b23c3e4f255' + 'SKIP' + '8481f23c33a84d8efb798f4b83f78011f74cd5a3dec428b177d7e600922836c1eec61c4238724038221d8ca2e2cdf20462c5911085da3461aa315f7dd19d96d8' + 'SKIP' + '19ebb39f3e53ce77dc3522d15c88270c5ca3fdd76fa8bd417852d08a1d85b30bc6eed3e11c95392b694dfc59bb8b43e41d43df4954f00593c8fbe50bd9e199cc' + 'SKIP' + '4dc42e9bfb5ee0f952bfd7befc9d333044aec11fc3a3852d4636f2e38ab139eb92c497eb3e30921b6ee8ae7d6e5834dc334ad25419bac2c875a9fd7eb7945f9b' + 'SKIP' + 'fea498b5c5ada6d7f57caf652b68f687081aae8552d2d04cd9e96764efd1b896208671f4f501210bb557710cca3ceb0c02ac2b5da6d4c04c210e87ec2d715384' + 'SKIP' + '4a4af88fc8a8c37ae6d20b35d19e60a17672569d5489be120edbcc62ab3ba4bbc43b78cd0799b9fd73c84b089364320bcbad6fcb7128c2cb06d15ed984d6fc8c' + 'SKIP' + '0959ca78ff31299adf1091f4aa1b32cd2e9066aba5e44f0658c91bcb93fe8714f7b671ffa2cd498ecbb6c6e643ac6571bfb193034af206599122fd7c2a3c23a8' + 'SKIP' + '03ca740048e48c5b6948f972f7a826240cf51c9b5c8645d1b1c50366e78cbf45f5b45b7b47f87f231b50aab46ef2f8fd78bc53899ba1c319a19f9f61c7cecb90' + 'SKIP' + '1b63569b589e994d1869e2f8eaf5d4ee225fe68862ac5c848e46544b827ca5fddb4bf060f5f39001608fd9bb284eec005f5f9b095307d3a008c9ea8957c2fad1' + 'SKIP' + '073514dabc88f0206911eb27eb36157e425849221056438340b22ee7bfa6997b58ce7dc4470b09d7ee96f6374f117d91cc19d9032401b2355d1c0aba6fc75771' + 'SKIP' + 'd7dffb68fd22db7ef41d1ac193dd297fb6150f034f257a44e6cb0a4848dc4a94e328c2e26d5296a72993e2db75d09e3ac46456d337a5bae767fb266082d3cc66' + 'SKIP' + 'aca0527f5630671a319e7ecff958a3a64f29df73b427c67c1d29763cc1801fa82e96761c3a61c22d936913943a49261da64d490a0752ba9ffc8e0c1d92f99835' + 'SKIP' + '803736a8e836560b3e50b7c0c8cf1f9ebb16b8642cc7bc6e5167d19037bd757431565fe4581776e50eb2693c49f778a24fecff41a553612e7e498f755b8f2f67' + 'SKIP' + '4bc132d9c34deaae9b13d08dcff8ea83aa8f9ce4398531e0e0a80df30da3808815f3a587fe5627f6553b8e9c2aea6cc1749b752250a91c94f1cf8ba4f3eb5e8d' + 'SKIP' + '2b007e58c0541c6ae71ac07cc8c0762343a50e1f3eb52db5631ce725c4f074f41c639fbf6d8695daa78ee1d6a8cb9a0c31d0a9d9ea2a3a10c05f7f6421dda184' + 'SKIP' + 'd3b2c0e4fd9f26a0eded375522478f8e9403232799d233eed4668920f8a9942ec6bafbc928f14459e4736f63a9a66d3ae2e9f70dc810145acb5fc522b13ecbd9' + 'SKIP' + 'c7b0be7a05a304e9993fe8ebccefa44944bf9d27dad9f7b3cc7111221bf50b2db71ca51cafc89dc03b237aef3aba7114543c8475cc393d13e61726e68898d3af' + 'SKIP' + '4008c7443bc221a142a939deac86918bef8f09ff1ca99a899586b13120edb8007450daf0b9d143770a9f04d616fe96f527b6aac160be45e5cfa66c0da898039c' + 'SKIP' + '029d9e899dd9e7bdbcacee34ee9b468e2e2a8f726e38d014891c8353f6afcc52a58c7d9289fb0487cce4d90c8d51f578c27b1dbcdab42de7308f50b7baf27877' + 'SKIP' + '9ede433f4e01a5a313a133a8029d64250596582b93a33e434b8e2ee5bc53ba7f91947e1be2aaf57776028cc89e7ca62cc90067ee64650b3e503acad858777a8a' + 'SKIP' + '9ac15b3542629c5333cc7d7f53b4f2995d807a31331b9e04d3da051be0ddde7e8c3cbc79837ffb49a4d364db1680cdc6d763465fa56bd4d26b16b765985babe1' + 'SKIP' + '25fc5f03732a584c864ee2ad20a74e16777998b0b34f880bbe9d646cf1944bac1f276bbd62515475337eaa81c0f398d79888aca6dc18bcd06ebb7cee9e4a1d8e' + 'SKIP' + '70e35154f9385156a11280294cc254cb314fd89653daedb46546a6bd64c550e3fc7e7a8df3e653c516687f0317bcc710fce82b4d649472333c501ed85fcd6cf6' + 'SKIP' + '576fd826f28d945782840865f3e5b7c87d7f6f91f08e473ff0d06a37caa2f89cdd9cb4a7aafef5551363aab9c60e887ee794d5b6292874c12baf88c23ea5668e' + 'SKIP' + 'b69cf36cc5633e507866e67f59557646dda7d6ea436b145f3f3356c4b12e1300c94977a7d1214835f2476c94a30dd672076241b920d96d744fbc199539df506f' + 'SKIP') +b2sums_x86_64=('54148fc55a2b611f84f62d0b1c19caf911e029cd29bda6f032b15cc2f3484a1cc648af63e101eb293b048b54df4a0efacdb39cbbaa5b13d8ea1dfc22b981ec6b') + +# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc +sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' + 'SKIP' + '95f3e9209629044028373af987dc0e270a5a15acb8c8e49c5f05057220c75fe2' + 'SKIP' + 'daf0aaebff3cf4679d0bb8137ace6c9a2de62f75aa4655a0a02ddf759f3c7f26' + 'SKIP' + 'a9171e731d08a454a50174889af8936fab962b33c37c67169b0cdf21b0b80821' + 'SKIP' + '413e9994d35bc722cfe45752ab60a1bd8454443170d80987e6a5be6b7b745940' + 'SKIP' + '01b9087c0cebf17d0ffbbbdd6025c9937fcdfa00c8672d310786ca3c3ce6daf9' + 'SKIP' + '5c776365d391a15c23afb3969183449852f795f42355629ac563abf7ae5e4735' + 'SKIP' + '3a6aaf3b29eb5b4018c5703626311cd1c7a61bd7100789633efc6a79a9b3247b' + 'SKIP' + 'b28d9b548407f0da4280f4f9a6c0e92db6f3a32a35c7668471b92096eb7b093c' + 'SKIP' + '9492d7db11fd327ff06a52cad945bf8ff6dfcabec72c435bf02b4432ee67475e' + 'SKIP' + '66af19c43c068144699183a17b67bcf4df03578937c4cffc5d8b24e2e2828f7b' + 'SKIP' + '267506a02ac723ea37adcc7c851051edbd1ed731c6aaea4c9e590aadc207855c' + 'SKIP' + '07baad05112a47cb0aea81191502c87e496e13b4f23a6ec36ea65bb4219c35a8' + 'SKIP' + '720720067a21e8c57d2619f4c0b46846ac060ec6db97088634d62ab8628c70a9' + 'SKIP' + '3d1299d19aa9fbc96d25f67154009813e05e92340340953ac8355649cff837a4' + 'SKIP' + 'dac26fb42f5df71b30c46b3c5894c6016af5a283c57b45173386767ca53b1bf8' + 'SKIP' + '66eb2c49dbf708cc781d411d0e44ed613480f0ef66d522272929d9cb7bb21e93' + 'SKIP' + 'b7ba949eea77e169aceb2c401d4eb83a7413aab6f15ee2a7ab1a96352f0aa12d' + 'SKIP' + '341424b925d506869793686e22ea8f095a34048595ebebec409c0a7bbd346ca2' + 'SKIP' + '7c25d0a53a115bc1f072ff0cfdd9aa88858e754ae8b15026a2582656e8bd9c43' + 'SKIP' + '79799f12cdf42de1cf6c5d483896b439d77bc2e582e10c7215f5375a76612a72' + 'SKIP' + '5e3b455024fab2855b590091c9d14b0d12d5363f3d4bf025b31b9aa88b65eba5' + 'SKIP' + 'abd8c9326cecc9bb85db4b3dc98ddcb66306b34a6d84fb05d0805aed537e2dc4' + 'SKIP' + '60a9df54821cd98581e7476f8a7bb6cb0819762bdbed4d356332e41d164a8182' + 'SKIP' + '929108ae0a8eb4782cafdd89e3d4426d02d86f2c2102ed99e760e31f0bc3794a' + 'SKIP' + 'e1226c836a9c2fb6daff109e56ba21c13fd198e109cbb181de920af04fe153e6' + 'SKIP' + '8d60f7dd26ef904419aa966427a9af2f2c9a079bb092ea0b58274ab905f6047a' + 'SKIP' + '258597006eb96e1fb185ec9303ceb48148eedce95608b57900383f6df59d2989' + 'SKIP' + 'f935409f9aeba3314adfad7db1b3d895557a07029a80d10969804e49c0e270ca' + 'SKIP' + 'd8e64b9da3beab7c33832c019da50d0977b39dc32ec1f649a7c3bc93a2fdb9a5' + 'SKIP' + '1780f66b157de6d249301331e31b7505f6e6fa26686fa4c618623567e4b758ae' + 'SKIP' + '2501c98aaea6a53eeaa18d696f60b677e710d0f2d89f7525e47ba806de1878b3' + 'SKIP' + 'e6b770d37e80509ed7ec5d8b59b4d2e5d6dba54ba659d7392de3b567f1eb6e54' + 'SKIP' + 'b8596b5b5b546fdc309e10326b0e2ccea5a0538d4919630ed2845cded78580aa' + 'SKIP' + '5cc215215fed4247c2d6b9732deff586331a07d54c3b00538a81921093a3a1a1' + 'SKIP' + 'e303da14a3c8a15fd1cb45394138f03dd16e66d2ac0d1ebcd933977df937d771' + 'SKIP' + '1f0c958b64ba48b8dc8b5548e1ad1934b8d4903fc16fec15014759e3681ea275' + 'SKIP' + '47994d576008a377de612ed77e6b2e7bc6b24ad3a30a6d157646d380ae323142' + 'SKIP' + 'd9eb3717ba98e270b0a998f8dd90cbda072f63b6a6ea8f66763bde3ea64e591d' + 'SKIP' + '1e2fddb2e0c5d184a2e8c774886cf9be2f2f292e4f05832b50e5b49c48a7bacc' + 'SKIP' + 'b9b8771b6a8bd7128f4344ad189e32c155abf72abd3ff646dcf5c78b08eb94c4' + 'SKIP' + 'f62837e8ee51070f8139f718e0e80a4ce0c0dea9ce2328c10c63403aca5b3d2e' + 'SKIP' + 'cae59282e3d1afd69f4f5dc00a180c712ef464c98b8fd7ccc9b03de944dc79b9' + 'SKIP' + '5a74c8b2ba11e5876eeeb5ba530ae97e384b48943fa7a31e13396ecb82c5a8ce' + 'SKIP' + '8b954ea37a2190022064c82688bc8cb1cf79a90598f078035be7d4041003cda9' + 'SKIP' + '40ac31789399a5964c4fd820d4cdc7214add91109bbcf4a614619e5d8b367441' + 'SKIP' + '707460d08c15f451d0a0e567c6e35522f3e1409bbe3bfbcf7160f33af06391b7' + 'SKIP' + 'eebbe50deab379d09b5149ef30769ad6dfa11109696934ee66b108ec406da942' + 'SKIP' + 'dab92dbc6eac02771c72801d94e6a53aabcf629a9cd79e85e0ce2ba44a65b06b' + 'SKIP' + '30e2fc90c950869ab2fe1535b609f35dc1d46d6b6816de4afcbf687dbc224f0a' + 'SKIP' + '1fdf8c4d8ee07c02883b827a11a84c1e5f70570545b8a4a232f6b59dd02049fa' + 'SKIP' + '0057dff07a3ce9c7b7085edd1f912958e01ce8f7400151d55dfb74c0c6a084e2' + 'SKIP' + '2762f72620781a672d9a21bcc591b862b04a756b271cd1009a7390eb1b77635e' + 'SKIP' + '541325476efee83232ab8a6c62c14cf3c3c957c49cf053ed2869f7780f86d032' + 'SKIP' + '8a8eb0934424ab6312f26e03a06be5d0401360ee60a20a87b11f7c3c6590103c' + 'SKIP' + 'f7ff13ea46359217d696c6ccd49bc3d74c441ac5893d5151d95138017b0d10aa' + 'SKIP' + 'd17493b3579c730d27dc723447175b70d991f50253c9fd419df5e923bdf7a244' + 'SKIP' + '92971bb4dbde209cf170838cfcd75d8ac9978239821d3493c28cc12623c2bb60' + 'SKIP' + '7245c6e2cb5c0b0ddf800894f087770cbf8ccc6f8b3f53b9a21c704e9dfa42c2' + 'SKIP' + '135140bce40644079be7743b51f8618fc829a8868ac53137050f76c45eca4061' + 'SKIP' + 'e6ec816ae309445c6c11e3433c04d0c9100e16c0e23268226d13c19e7be49edb' + 'SKIP' + '29c61ec984bd342113b3c4310a3d5fddfd6d7db83847cfb26b84cc730385320a' + 'SKIP' + '53425998005a4115bdf6001b9ffbe62c66bb2db205994b2e13005a3417ea6cbb' + 'SKIP' + 'c28fca9ba015e96599627acf42e52737a6a846bdcbfd3d4af4147eda3109e12d' + 'SKIP' + '5ce97dadb9eb1a8b0da41a8446ee74e01a5a7e7ab439bbea7a711318f04ae24f' + 'SKIP' + '44f1c7bd18ea7f8772ce41e24dddd5f314e416ec8dfac6afe1ce8680c1bb4903' + 'SKIP' + '0bb8ae5b6f23e13c60953eb5bbc8939e8f6c8faf2b216a20203af3b6f0217134' + 'SKIP' + '907c92ef681154d10959a5f0cbe2a636688036990ba42e059af00cc2311b4be4' + 'SKIP' + '534e68a989d36823f14a10299803f294eef39391619b4c329d78c20539d9fcd5' + 'SKIP' + '516e6ec51ca0330989128a4eb2a8f6785e5d45d930899cbbbf37b8147ddfd854' + 'SKIP' + 'bda3d6842f15998ce3f3e33d0e6e420df9c9c2abcba13716af478720c5933753' + 'SKIP' + '3e7e7dba1ca88930c2c3f0d5411ddcee8c2e12fca59d859e2383dccaf9a08022' + 'SKIP' + '3a271bc6b0219152047fb2b78d9e695eb791b796ea6d8233096912c77d62a5c0' + 'SKIP' + '4e80f2d05591175d4ed0a1a144509e97260e5593472aae586c07956215d99162' + 'SKIP' + '8ebce8f38e38bd66879d040028c2448e5bc7dcde9df0ccee2b4221015591b162' + 'SKIP' + '0b0e05b615ae6eb825bf7d7fa568f551b0dff6c2cee62c9285c1b0d63224ea1c' + 'SKIP' + '2e1e2a0eb039ce61c1612bf6c7ab5227b99df053bcd5592da7758715dd333fda' + 'SKIP' + '17de1f76da3db42dc3c7829a22953cd2c6de916999d809e3fb0bbb741753ec8f' + 'SKIP' + 'd90cc6570a47c8754e4c5d45834e0434c0a1fcabda9b8933b1fba84e3dabc083' + 'SKIP' + '028bf538c870465e1752514725372fa9eef6b7178d9b8d1e2ecc15edc5a63cc8' + 'SKIP' + 'a92a84405ee9845738c6b52d8f0f0eca16eb40afe0ebe0f4e9e1168597675311' + 'SKIP' + '44a0b10a6dc83465ea86f5fba936b2bfbeffb1b3f0b2e04013bfed99ba4f6c5a' + 'SKIP' + '229b28cee6f2b8afb5888eed453ce7f82de033900383bd91957339007c9e6cbf' + 'SKIP' + 'c5d61db05b19fde06102b0b3dbd76e1989c8f07f3b7d175176bd2d73f1a71e5c' + 'SKIP' + '5988d7a37b4b71a64ed09fea872f08515a3c7029d33fd053a6a1822bb811ddb5' + 'SKIP' + '1ec4fb93700b0696ba2c2a371f218f13b32dcc745523c7ee26a5c3cd7af253fc' + 'SKIP' + 'c67fee35a42866dd3c9056ff099fc504cdad9530c107d959331b5572b21af818' + 'SKIP' + '36cb306751c57de3e61c7a4cd18a7f131eef0dc1de15a4e28751d1e5b08bace8' + 'SKIP' + '07683e522d6dea3d3ce658ee80bf4317f6c3caf248259da960bc4610f4d1e807' + 'SKIP' + '66a0933d2d4f2edc8999b6fdf0fe10b5b607646fd6f1a1d534bf26270543f8f1' + 'SKIP' + 'c5279468d93e562f509a6b599a2b7ca7ee0032d3fe35314c5769fc6c9a9234ab' + 'SKIP' + 'c0111614ec44014cbf621eedf5a4ef302a855423bc1633ee692392950e971d0e' + 'SKIP' + '1cbe1ddd3b37cd0c7e4bad4af08e681eda2f7a61698004322bb87f320aa95dbb' + 'SKIP') + +export KBUILD_BUILD_HOST=omarchy +export KBUILD_BUILD_USER=$pkgbase +export KBUILD_BUILD_TIMESTAMP="$(date -Ru${SOURCE_DATE_EPOCH:+d @$SOURCE_DATE_EPOCH})" + +prepare() { + cd $_srcname + + echo "Setting version..." + echo "-$pkgrel" > 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 $( + ++#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 + */ +@@ -2093,6 +2099,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 +@@ -3163,6 +3174,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; + } +@@ -3398,6 +3413,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/0010-archlinux-base.patch.sig b/pkgbuilds/linux-omarchy/0010-archlinux-base.patch.sig new file mode 100644 index 0000000..c790810 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0010-archlinux-base.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0120-tlbpull.patch b/pkgbuilds/linux-omarchy/0120-tlbpull.patch new file mode 100644 index 0000000..712b9b7 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0120-tlbpull.patch @@ -0,0 +1,763 @@ +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/pkeys.h b/arch/x86/include/asm/pkeys.h +--- a/arch/x86/include/asm/pkeys.h ++++ b/arch/x86/include/asm/pkeys.h +@@ -88,6 +88,9 @@ int mm_pkey_alloc(struct mm_struct *mm) + u16 all_pkeys_mask = ((1U << arch_max_pkey()) - 1); + int ret; + ++ if (!arch_pkeys_enabled()) ++ return -1; ++ + /* + * Are we out of pkeys? We must handle this specially + * because ffz() behavior is undefined if there are no +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 +@@ -62,18 +62,18 @@ enum cpa_warn { + static const int cpa_warn_level = CPA_PROTECT; + + /* +- * Serialize cpa() (for !DEBUG_PAGEALLOC which uses large identity mappings) +- * using cpa_lock. So that we don't allow any other cpu, with stale large tlb +- * entries change the page attribute in parallel to some other cpu +- * splitting a large page entry along with changing the attribute. ++ * Serialize cpa() using cpa_lock so that we don't allow any other cpu, with ++ * stale large tlb entries, to change the page attribute in parallel to some ++ * other cpu splitting a large page entry along with changing the attribute. + */ + static DEFINE_SPINLOCK(cpa_lock); + +-#define CPA_FLUSHTLB 1 +-#define CPA_ARRAY 2 +-#define CPA_PAGES_ARRAY 4 +-#define CPA_NO_CHECK_ALIAS 8 /* Do not search for aliases */ +-#define CPA_COLLAPSE 16 /* try to collapse large pages */ ++#define CPA_FLUSHTLB 0x01 ++#define CPA_ARRAY 0x02 ++#define CPA_PAGES_ARRAY 0x04 ++#define CPA_NO_CHECK_ALIAS 0x08 /* Do not search for aliases */ ++#define CPA_COLLAPSE 0x10 /* try to collapse large pages */ ++#define CPA_DEBUG_PAGEALLOC 0x20 + + static inline pgprot_t cachemode2pgprot(enum page_cache_mode pcm) + { +@@ -86,9 +86,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) +@@ -418,6 +417,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) + int collapsed = 0; + int i; + ++ guard(spinlock)(&cpa_lock); ++ + if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) { + for (i = 0; i < cpa->numpages; i++) + collapsed += collapse_large_pages(__cpa_addr(cpa, i), +@@ -888,24 +889,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) +@@ -1075,16 +1075,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, +@@ -1135,16 +1130,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)); + +@@ -1177,7 +1170,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + break; + + default: +- spin_unlock(&pgd_lock); + return 1; + } + +@@ -1225,7 +1217,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; + } +@@ -1235,11 +1226,9 @@ static int split_large_page(struct cpa_data *cpa, pte_t *kpte, + { + struct ptdesc *ptdesc; + +- if (!debug_pagealloc_enabled()) +- spin_unlock(&cpa_lock); ++ spin_unlock(&cpa_lock); + ptdesc = pagetable_alloc(GFP_KERNEL, 0); +- if (!debug_pagealloc_enabled()) +- spin_lock(&cpa_lock); ++ spin_lock(&cpa_lock); + if (!ptdesc) + return -ENOMEM; + +@@ -1298,11 +1287,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); +@@ -1376,7 +1365,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; +@@ -1384,26 +1373,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; + } + +@@ -2004,6 +1991,7 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + { + unsigned long numpages = cpa->numpages; + unsigned long rempages = numpages; ++ bool lock = true; + int ret = 0; + + /* +@@ -2013,6 +2001,29 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + !cpa->force_split) + return ret; + ++ /* ++ * DEBUG_PAGEALLOC is special; it is called from any context the ++ * page-allocator is, which violates the normal cpa_lock locking ++ * rules. ++ * ++ * However, since it is part of the page-allocator, things are still ++ * properly serialized by the page-allocator locking and the fact that ++ * when a page is owned by the page-allocator, it isn't owned by ++ * anybody else. That is, you *SHOULD NOT* be calling cpa() on memory ++ * that isn't allocated. ++ * ++ * Additionally, DEBUG_PAGEALLOC ensures (per probe_page_size_mask()) ++ * that the kernel mapping is 4k pages, therefore there are no large ++ * pages to split/collapse. ++ * ++ * Furthermore, the page-allocator strictly manages pages that ++ * *exist*, avoiding pgd_lock. ++ * ++ * Therefore, it is safe to not take cpa_lock. ++ */ ++ if (debug_pagealloc_enabled() && (cpa->flags & CPA_DEBUG_PAGEALLOC)) ++ lock = false; ++ + while (rempages) { + /* + * Store the remaining nr of pages for the large page +@@ -2023,11 +2034,12 @@ static int __change_page_attr_set_clr(struct cpa_data *cpa, int primary) + if (cpa->flags & (CPA_ARRAY | CPA_PAGES_ARRAY)) + cpa->numpages = 1; + +- if (!debug_pagealloc_enabled()) +- spin_lock(&cpa_lock); +- ret = __change_page_attr(cpa, primary); +- if (!debug_pagealloc_enabled()) +- spin_unlock(&cpa_lock); ++ if (lock) { ++ guard(spinlock)(&cpa_lock); ++ ret = __change_page_attr(cpa, primary); ++ } else { ++ ret = __change_page_attr(cpa, primary); ++ } + if (ret) + goto out; + +@@ -2606,7 +2618,7 @@ int set_pages_rw(struct page *page, int numpages) + return set_memory_rw(addr, numpages); + } + +-static int __set_pages_p(struct page *page, int numpages) ++static int __set_pages_p(struct page *page, int numpages, unsigned int cpa_flags) + { + unsigned long tempaddr = (unsigned long) page_address(page); + struct cpa_data cpa = { .vaddr = &tempaddr, +@@ -2614,7 +2626,7 @@ static int __set_pages_p(struct page *page, int numpages) + .numpages = numpages, + .mask_set = __pgprot(_PAGE_PRESENT | _PAGE_RW), + .mask_clr = __pgprot(0), +- .flags = CPA_NO_CHECK_ALIAS }; ++ .flags = CPA_NO_CHECK_ALIAS | cpa_flags }; + + /* + * No alias checking needed for setting present flag. otherwise, +@@ -2625,7 +2637,7 @@ static int __set_pages_p(struct page *page, int numpages) + return __change_page_attr_set_clr(&cpa, 1); + } + +-static int __set_pages_np(struct page *page, int numpages) ++static int __set_pages_np(struct page *page, int numpages, unsigned int cpa_flags) + { + unsigned long tempaddr = (unsigned long) page_address(page); + struct cpa_data cpa = { .vaddr = &tempaddr, +@@ -2633,7 +2645,7 @@ static int __set_pages_np(struct page *page, int numpages) + .numpages = numpages, + .mask_set = __pgprot(0), + .mask_clr = __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY), +- .flags = CPA_NO_CHECK_ALIAS }; ++ .flags = CPA_NO_CHECK_ALIAS | cpa_flags }; + + /* + * No alias checking needed for setting not present flag. otherwise, +@@ -2646,20 +2658,20 @@ static int __set_pages_np(struct page *page, int numpages) + + int set_direct_map_invalid_noflush(struct page *page) + { +- return __set_pages_np(page, 1); ++ return __set_pages_np(page, 1, 0); + } + + int set_direct_map_default_noflush(struct page *page) + { +- return __set_pages_p(page, 1); ++ return __set_pages_p(page, 1, 0); + } + + int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid) + { + if (valid) +- return __set_pages_p(page, nr); ++ return __set_pages_p(page, nr, 0); + +- return __set_pages_np(page, nr); ++ return __set_pages_np(page, nr, 0); + } + + #ifdef CONFIG_DEBUG_PAGEALLOC +@@ -2678,15 +2690,23 @@ void __kernel_map_pages(struct page *page, int numpages, int enable) + * and hence no memory allocations during large page split. + */ + if (enable) +- __set_pages_p(page, numpages); ++ __set_pages_p(page, numpages, CPA_DEBUG_PAGEALLOC); + else +- __set_pages_np(page, numpages); ++ __set_pages_np(page, numpages, CPA_DEBUG_PAGEALLOC); + + /* +- * We should perform an IPI and flush all tlbs, +- * but that can deadlock->flush only current cpu. +- * Preemption needs to be disabled around __flush_tlb_all() due to +- * CR3 reload in __native_flush_tlb(). ++ * We should perform an IPI and flush all tlbs, but that can ++ * deadlock, settle for a local flush. ++ * ++ * Not doing a global TLB flush means that remote CPUs will retain ++ * stale TLB entries. In case of P->NP (on free) this means the remote ++ * CPUs will not take the faults, making the debug scheme less ++ * reliable. On the NP->P (on alloc) this means the remote CPUs can ++ * take a spurious fault. However spurious_kernel_fault() will observe ++ * *_present() and fix it up. ++ * ++ * Preemption needs to be disabled around __flush_tlb_all() due to CR3 ++ * reload in __native_flush_tlb(). + */ + preempt_disable(); + __flush_tlb_all(); +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/0120-tlbpull.patch.sig b/pkgbuilds/linux-omarchy/0120-tlbpull.patch.sig new file mode 100644 index 0000000..b8fcd47 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0120-tlbpull.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0121-smp-preempt.patch b/pkgbuilds/linux-omarchy/0121-smp-preempt.patch new file mode 100644 index 0000000..59d8c69 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0121-smp-preempt.patch @@ -0,0 +1,514 @@ +diff --git a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -823,6 +823,17 @@ struct kmap_ctrl { + #endif + }; + ++#if defined(CONFIG_SMP) && defined(CONFIG_PREEMPTION) ++struct task_ipi_mask { ++ union { ++ cpumask_t *ipi_mask_ptr; ++ unsigned long ipi_mask_val; ++ }; ++}; ++#else ++struct task_ipi_mask { }; ++#endif ++ + struct task_struct { + #ifdef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -1359,6 +1370,7 @@ struct task_struct { + struct list_head perf_event_list; + struct perf_ctx_data __rcu *perf_ctx_data; + #endif ++ struct task_ipi_mask __private ipi_mask; + #ifdef CONFIG_DEBUG_PREEMPT + unsigned long preempt_disable_ip; + #endif +diff --git a/include/linux/smp.h b/include/linux/smp.h +--- a/include/linux/smp.h ++++ b/include/linux/smp.h +@@ -47,8 +47,7 @@ extern void __smp_call_single_queue(int cpu, struct llist_node *node); + /* total number of cpus in this system (may exceed NR_CPUS) */ + extern unsigned int total_cpus; + +-int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, +- int wait); ++int smp_call_function_single(int cpuid, smp_call_func_t func, void *info, bool wait); + + void on_each_cpu_cond_mask(smp_cond_func_t cond_func, smp_call_func_t func, + void *info, bool wait, const struct cpumask *mask); +@@ -239,6 +238,18 @@ static inline int get_boot_cpu_id(void) + + #endif /* !SMP */ + ++#if defined(CONFIG_PREEMPTION) && defined(CONFIG_SMP) ++int smp_task_ipi_mask_alloc(struct task_struct *task); ++void smp_task_ipi_mask_free(struct task_struct *task); ++#else ++static inline int smp_task_ipi_mask_alloc(struct task_struct *task) ++{ ++ return 0; ++} ++ ++static inline void smp_task_ipi_mask_free(struct task_struct *task) { } ++#endif ++ + /* + * raw_smp_processor_id() - get the current (unstable) CPU id + * +diff --git a/kernel/fork.c b/kernel/fork.c +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -543,6 +543,7 @@ void free_task(struct task_struct *tsk) + #endif + release_user_cpus_ptr(tsk); + scs_release(tsk); ++ smp_task_ipi_mask_free(tsk); + + #ifndef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -941,10 +942,14 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) + #endif + account_kernel_stack(tsk, 1); + +- err = scs_prepare(tsk, node); ++ err = smp_task_ipi_mask_alloc(tsk); + if (err) + goto free_stack; + ++ err = scs_prepare(tsk, node); ++ if (err) ++ goto free_ipi_mask; ++ + #ifdef CONFIG_SECCOMP + /* + * We must handle setting up seccomp filters once we're under +@@ -1022,6 +1027,8 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) + #endif + return tsk; + ++free_ipi_mask: ++ smp_task_ipi_mask_free(tsk); + free_stack: + exit_task_stack_account(tsk); + free_thread_stack(tsk); +diff --git a/kernel/scftorture.c b/kernel/scftorture.c +--- a/kernel/scftorture.c ++++ b/kernel/scftorture.c +@@ -348,6 +348,8 @@ static void scftorture_invoke_one(struct scf_statistics *scfp, struct torture_ra + int ret = 0; + struct scf_check *scfcp = NULL; + struct scf_selector *scfsp = scf_sel_rand(trsp); ++ bool is_single = (scfsp->scfs_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/0121-smp-preempt.patch.sig b/pkgbuilds/linux-omarchy/0121-smp-preempt.patch.sig new file mode 100644 index 0000000..81da846 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0121-smp-preempt.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch b/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch new file mode 100644 index 0000000..296b430 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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 +@@ -13338,12 +13338,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/0130-sched-detach-tasks.patch.sig b/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch.sig new file mode 100644 index 0000000..88fd720 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch b/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch new file mode 100644 index 0000000..5f8bb20 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch @@ -0,0 +1,23 @@ +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -3743,11 +3743,17 @@ static inline void ttwu_do_wakeup(struct task_struct *p) + + void update_rq_avg_idle(struct rq *rq) + { +- u64 delta = rq_clock(rq) - rq->idle_stamp; +- u64 max = 2*rq->max_idle_balance_cost; ++ u64 idle_stamp = rq->idle_stamp; ++ u64 delta, max; ++ ++ if (!idle_stamp) ++ return; ++ ++ delta = rq_clock(rq) - idle_stamp; + + update_avg(&rq->avg_idle, delta); + ++ max = 2 * rq->max_idle_balance_cost; + if (rq->avg_idle > max) + rq->avg_idle = max; + rq->idle_stamp = 0; diff --git a/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch.sig b/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch.sig new file mode 100644 index 0000000..a9f0fa6 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch b/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch new file mode 100644 index 0000000..ee0da9b --- /dev/null +++ b/pkgbuilds/linux-omarchy/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 +@@ -5091,7 +5091,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 +@@ -5135,7 +5135,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; +@@ -5170,7 +5170,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; +@@ -5244,7 +5244,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)) + { + /* +@@ -5278,7 +5278,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)) +@@ -5332,7 +5332,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 +@@ -1460,12 +1460,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); + } +@@ -1474,7 +1474,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; +@@ -1482,7 +1482,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) +@@ -1585,12 +1585,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; +@@ -1650,33 +1650,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; +@@ -1687,7 +1687,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/0140-sched-always-inline.patch.sig b/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch.sig new file mode 100644 index 0000000..2e7bcd9 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch b/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch new file mode 100644 index 0000000..9bc941f --- /dev/null +++ b/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch @@ -0,0 +1,125 @@ +diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c +--- a/kernel/sched/deadline.c ++++ b/kernel/sched/deadline.c +@@ -3026,8 +3026,8 @@ static struct task_struct *pick_next_pushable_dl_task(struct rq *rq) + next_node = rb_first_cached(&rq->dl.pushable_dl_tasks_root); + while (next_node) { + i = __node_2_pdl(next_node); +- /* make sure task isn't on_cpu (possible with proxy-exec) */ +- if (!task_on_cpu(rq, i)) { ++ /* skip tasks that cannot be migrated */ ++ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { + p = i; + break; + } +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -10565,17 +10565,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; +@@ -10632,6 +10655,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; + +@@ -10639,7 +10670,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; + } +@@ -10679,8 +10711,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; +@@ -11743,6 +11774,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/kernel/sched/rt.c b/kernel/sched/rt.c +--- a/kernel/sched/rt.c ++++ b/kernel/sched/rt.c +@@ -1871,8 +1871,8 @@ static struct task_struct *pick_next_pushable_task(struct rq *rq) + return NULL; + + plist_for_each_entry(i, head, pushable_tasks) { +- /* make sure task isn't on_cpu (possible with proxy-exec) */ +- if (!task_on_cpu(rq, i)) { ++ /* skip tasks that cannot be migrated */ ++ if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { + p = i; + break; + } diff --git a/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch.sig b/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch.sig new file mode 100644 index 0000000..4983403 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch b/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch new file mode 100644 index 0000000..a0f39ea --- /dev/null +++ b/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch @@ -0,0 +1,24 @@ +diff --git a/arch/x86/kernel/itmt.c b/arch/x86/kernel/itmt.c +--- a/arch/x86/kernel/itmt.c ++++ b/arch/x86/kernel/itmt.c +@@ -110,18 +110,14 @@ int sched_set_itmt_support(void) + arch_debugfs_dir, + &sysctl_sched_itmt_enabled, + &dfs_sched_itmt_fops); +- if (IS_ERR_OR_NULL(dfs_sched_itmt)) { ++ if (IS_ERR(dfs_sched_itmt)) + dfs_sched_itmt = NULL; +- return -ENOMEM; +- } + + dfs_sched_core_prio = debugfs_create_file("sched_core_priority", 0644, + arch_debugfs_dir, NULL, + &sched_core_priority_fops); +- if (IS_ERR_OR_NULL(dfs_sched_core_prio)) { ++ if (IS_ERR(dfs_sched_core_prio)) + dfs_sched_core_prio = NULL; +- return -ENOMEM; +- } + + sched_itmt_capable = true; + diff --git a/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch.sig b/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch.sig new file mode 100644 index 0000000..d96b7f9 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch b/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch new file mode 100644 index 0000000..9c10fcd --- /dev/null +++ b/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch @@ -0,0 +1,124 @@ +diff --git a/include/linux/sched/sd_flags.h b/include/linux/sched/sd_flags.h +--- a/include/linux/sched/sd_flags.h ++++ b/include/linux/sched/sd_flags.h +@@ -146,8 +146,7 @@ SD_FLAG(SD_ASYM_PACKING, SDF_NEEDS_GROUPS) + /* + * Prefer to place tasks in a sibling domain + * +- * Set up until domains start spanning NUMA nodes. Close to being a SHARED_CHILD +- * flag, but cleared below domains with SD_ASYM_CPUCAPACITY. ++ * Set up until domains start spanning NUMA nodes. + * + * NEEDS_GROUPS: Load balancing flag. + */ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -11902,10 +11902,25 @@ static inline void update_sg_lb_stats(struct lb_env *env, + continue; + + if (sd_flags & SD_ASYM_CPUCAPACITY) { +- /* Check for a misfit task on the cpu */ +- if (sgs->group_misfit_task_load < rq->misfit_task_load) { +- sgs->group_misfit_task_load = rq->misfit_task_load; +- *sg_overloaded = 1; ++ 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 */ +@@ -11984,6 +11999,17 @@ static bool update_sd_pick_busiest(struct lb_env *env, + sds->local_stat.group_type != group_has_spare)) + return false; + ++ /* ++ * Candidate sg has no more than one task per CPU and has higher ++ * per-CPU capacity. Migrating tasks to less capable CPUs may harm ++ * throughput. Maximize throughput, power/energy consequences are not ++ * considered. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && ++ (sgs->group_type <= group_fully_busy) && ++ (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu)))) ++ return false; ++ + if (sgs->group_type > busiest->group_type) + return true; + +@@ -12090,17 +12116,6 @@ static bool update_sd_pick_busiest(struct lb_env *env, + break; + } + +- /* +- * Candidate sg has no more than one task per CPU and has higher +- * per-CPU capacity. Migrating tasks to less capable CPUs may harm +- * throughput. Maximize throughput, power/energy consequences are not +- * considered. +- */ +- if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && +- (sgs->group_type <= group_fully_busy) && +- (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu)))) +- return false; +- + return true; + } + +@@ -13018,9 +13033,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/0143-sched-hybrid-cluster-balancing.patch.sig b/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch.sig new file mode 100644 index 0000000..b191450 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch b/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch new file mode 100644 index 0000000..1fe001c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch @@ -0,0 +1,77 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -13924,29 +13924,62 @@ static inline int on_null_domain(struct rq *rq) + */ + static inline int find_new_ilb(void) + { +- int this_cpu = smp_processor_id(); +- const struct cpumask *hk_mask; +- int ilb_cpu; ++ struct cpumask *ilb_cpus; ++ int ilb_cpu, fallback = -1; + +- hk_mask = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE); ++ lockdep_assert_irqs_disabled(); + +- for_each_cpu_and(ilb_cpu, nohz.idle_cpus_mask, hk_mask) { +- if (ilb_cpu == this_cpu) ++ /* ++ * Reuse the per-CPU select_rq_mask, which is protected from concurrent ++ * use on this CPU by having interrupts disabled. ++ */ ++ ilb_cpus = this_cpu_cpumask_var_ptr(select_rq_mask); ++ cpumask_and(ilb_cpus, nohz.idle_cpus_mask, ++ housekeeping_cpumask(HK_TYPE_KERNEL_NOISE)); ++ ++ for_each_cpu(ilb_cpu, ilb_cpus) { ++ if (!idle_cpu(ilb_cpu)) { ++ /* ++ * Once an idle fallback exists, a busy CPU proves that ++ * this core cannot be fully idle. Skip its siblings. ++ */ ++ if (sched_smt_active() && fallback >= 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/0144-sched-nohz-idle-core.patch.sig b/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch.sig new file mode 100644 index 0000000..0a059ef Binary files /dev/null and b/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch b/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch new file mode 100644 index 0000000..f949578 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch @@ -0,0 +1,45 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -67,19 +67,19 @@ + * SCHED_TUNABLESCALING_LOG - scaled logarithmically, *1+ilog(ncpus) + * SCHED_TUNABLESCALING_LINEAR - scaled linear, *ncpus + * +- * (default SCHED_TUNABLESCALING_LOG = *(1+ilog(ncpus)) ++ * (default SCHED_TUNABLESCALING_NONE = *1 constant) + */ +-unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; ++unsigned int sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_NONE; + + /* + * Minimal preemption granularity for CPU-bound tasks: + * +- * (default: 0.70 msec * (1 + ilog(ncpus)), units: nanoseconds) ++ * (default: 1.6 msec constant, units: nanoseconds) + */ +-unsigned int sysctl_sched_base_slice = 700000ULL; +-static unsigned int normalized_sysctl_sched_base_slice = 700000ULL; ++unsigned int sysctl_sched_base_slice = 1600000ULL; ++static unsigned int normalized_sysctl_sched_base_slice = 1600000ULL; + +-__read_mostly unsigned int sysctl_sched_migration_cost = 500000UL; ++__read_mostly unsigned int sysctl_sched_migration_cost = 400000UL; + + static int __init setup_sched_thermal_decay_shift(char *str) + { +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -3103,11 +3103,7 @@ + attach_task(rq, p); + } + +-#ifdef CONFIG_PREEMPT_RT +-# define SCHED_NR_MIGRATE_BREAK 8 +-#else +-# define SCHED_NR_MIGRATE_BREAK 32 +-#endif ++#define SCHED_NR_MIGRATE_BREAK 8 + + extern __read_mostly unsigned int sysctl_sched_nr_migrate; + extern __read_mostly unsigned int sysctl_sched_migration_cost; diff --git a/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch.sig b/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch.sig new file mode 100644 index 0000000..efd3bb2 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0200-idle.patch b/pkgbuilds/linux-omarchy/0200-idle.patch new file mode 100644 index 0000000..dc499e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0200-idle.patch @@ -0,0 +1,61 @@ +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 +@@ -53,6 +53,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2697,6 +2698,9 @@ static void __init cmdline_table_adjust(struct cpuidle_driver *drv) + pr_info("Failed to adjust C-states with data from 'intel_idle.table'\n"); + } + ++#define INTEL_IDLE_INIT_QOS 20 ++static struct pm_qos_request qos_req __initdata; ++ + static int __init intel_idle_init(void) + { + const struct x86_cpu_id *id; +@@ -2766,6 +2770,13 @@ static int __init intel_idle_init(void) + if (retval) + pr_warn("failed to initialized sysfs"); + ++ /* ++ * Some platforms, in particular the Intel S1200BTL motherboard, have a ++ * problem with using package idle states too early, so prevent that ++ * from taking place until the device_initcall() phase is over. ++ */ ++ cpu_latency_qos_add_request(&qos_req, INTEL_IDLE_INIT_QOS); ++ + retval = cpuidle_register_driver(&intel_idle_driver); + if (retval) { + struct cpuidle_driver *drv = cpuidle_get_driver(); +@@ -2790,6 +2801,9 @@ static int __init intel_idle_init(void) + intel_idle_cpuidle_devices_uninit(); + cpuidle_unregister_driver(&intel_idle_driver); + init_driver_fail: ++ if (cpu_latency_qos_request_active((&qos_req))) ++ cpu_latency_qos_remove_request(&qos_req); ++ + intel_idle_sysfs_uninit(); + free_percpu(intel_idle_cpuidle_devices); + return retval; +@@ -2797,6 +2811,15 @@ static int __init intel_idle_init(void) + } + subsys_initcall_sync(intel_idle_init); + ++static int __init intel_idle_init_complete(void) ++{ ++ if (cpu_latency_qos_request_active((&qos_req))) ++ cpu_latency_qos_remove_request(&qos_req); ++ ++ return 0; ++} ++device_initcall_sync(intel_idle_init_complete); ++ + /* + * We are not really modular, but we used to support that. Meaning we also + * support "intel_idle.max_cstate=..." at boot and also a read-only export of diff --git a/pkgbuilds/linux-omarchy/0200-idle.patch.sig b/pkgbuilds/linux-omarchy/0200-idle.patch.sig new file mode 100644 index 0000000..5998b39 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0200-idle.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0210-pstate.patch b/pkgbuilds/linux-omarchy/0210-pstate.patch new file mode 100644 index 0000000..3d4eefc --- /dev/null +++ b/pkgbuilds/linux-omarchy/0210-pstate.patch @@ -0,0 +1,480 @@ +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 +@@ -782,6 +782,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; +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,19 +2322,13 @@ 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(); + + if (pstate_funcs.get_vid) + pstate_funcs.get_vid(cpu); +- +- intel_pstate_set_min_pstate(cpu); + } + + /* +@@ -2884,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) +@@ -2901,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); + + /* +@@ -2910,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); + +@@ -2971,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]; +@@ -3063,6 +3061,7 @@ static int __intel_pstate_cpu_init(struct cpufreq_policy *policy) + static int intel_pstate_cpu_init(struct cpufreq_policy *policy) + { + int ret = __intel_pstate_cpu_init(policy); ++ struct cpudata *cpu; + + if (ret) + return ret; +@@ -3073,11 +3072,11 @@ static int intel_pstate_cpu_init(struct cpufreq_policy *policy) + */ + policy->policy = CPUFREQ_POLICY_POWERSAVE; + +- if (hwp_active) { +- struct cpudata *cpu = all_cpu_data[policy->cpu]; +- ++ cpu = all_cpu_data[policy->cpu]; ++ if (hwp_active) + cpu->epp_cached = intel_pstate_get_epp(cpu, 0); +- } ++ else ++ intel_pstate_set_min_pstate(cpu); + + return 0; + } +@@ -3243,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]; +@@ -3273,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; + +@@ -3301,8 +3307,6 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) + return ret; + + policy->cpuinfo.transition_latency = INTEL_CPUFREQ_TRANSITION_LATENCY; +- /* This reflects the intel_pstate_get_cpu_pstates() setting. */ +- policy->cur = policy->cpuinfo.min_freq; + + req = kzalloc_objs(*req, 2); + if (!req) { +@@ -3323,9 +3327,15 @@ static int intel_cpufreq_cpu_init(struct cpufreq_policy *policy) + WRITE_ONCE(cpu->hwp_req_cached, value); + + cpu->epp_cached = intel_pstate_get_epp(cpu, value); ++ ++ intel_cpufreq_hwp_update(cpu, cpu->pstate.min_pstate, ++ cpu->pstate.max_pstate, ++ cpu->pstate.min_pstate, false); + } else { + policy->transition_delay_us = INTEL_CPUFREQ_TRANSITION_DELAY; ++ intel_pstate_set_min_pstate(cpu); + } ++ policy->cur = policy->cpuinfo.min_freq; + + freq = DIV_ROUND_UP(cpu->pstate.turbo_freq * global.min_perf_pct, 100); + +@@ -3676,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), + {} + }; +@@ -3808,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; +@@ -3816,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/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/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; +diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs +--- a/rust/kernel/cpufreq.rs ++++ b/rust/kernel/cpufreq.rs +@@ -792,7 +792,13 @@ fn fast_switch(_policy: &mut Policy, _target_freq: u32) -> u32 { + } + + /// 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) + } + +@@ -1263,12 +1269,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/0210-pstate.patch.sig b/pkgbuilds/linux-omarchy/0210-pstate.patch.sig new file mode 100644 index 0000000..1d30612 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0210-pstate.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch b/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch new file mode 100644 index 0000000..67a6cf4 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch @@ -0,0 +1,127 @@ +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 +@@ -560,6 +560,11 @@ static int amd_pstate_ut_check_freq_attrs(u32 index) + static int __init amd_pstate_ut_init(void) + { + u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); ++ enum amd_pstate_mode mode = amd_pstate_get_status(); ++ ++ /* don't test if no running amd-pstate driver */ ++ if (mode == AMD_PSTATE_UNDEFINED || mode == AMD_PSTATE_DISABLE) ++ return -EOPNOTSUPP; + + for (i = 0; i < arr_size; i++) { + int ret; +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 +@@ -199,7 +199,7 @@ static inline int get_mode_idx_from_str(const char *str, size_t size) + + static DEFINE_MUTEX(amd_pstate_driver_lock); + +-static u8 msr_get_epp(struct amd_cpudata *cpudata) ++static int msr_get_epp(struct amd_cpudata *cpudata) + { + u64 value; + int ret; +@@ -215,12 +215,12 @@ static u8 msr_get_epp(struct amd_cpudata *cpudata) + + DEFINE_STATIC_CALL(amd_pstate_get_epp, msr_get_epp); + +-static inline s16 amd_pstate_get_epp(struct amd_cpudata *cpudata) ++static inline int amd_pstate_get_epp(struct amd_cpudata *cpudata) + { + return static_call(amd_pstate_get_epp)(cpudata); + } + +-static u8 shmem_get_epp(struct amd_cpudata *cpudata) ++static int shmem_get_epp(struct amd_cpudata *cpudata) + { + u64 epp; + int ret; +@@ -526,9 +526,6 @@ static int shmem_init_perf(struct amd_cpudata *cpudata) + WRITE_ONCE(cpudata->perf, perf); + WRITE_ONCE(cpudata->prefcore_ranking, cppc_perf.highest_perf); + +- if (cppc_state == AMD_PSTATE_ACTIVE) +- return 0; +- + ret = cppc_get_auto_sel(cpudata->cpu, &auto_sel); + if (ret) { + pr_warn("failed to get auto_sel, ret: %d\n", ret); +@@ -1174,6 +1171,9 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, + if (cpudata->current_profile != PLATFORM_PROFILE_BALANCED) + return 0; + ++ if (!policy) ++ return NOTIFY_OK; ++ + epp = amd_pstate_get_balanced_epp(policy); + + ret = amd_pstate_set_epp(policy, epp); +@@ -1209,6 +1209,9 @@ static int amd_pstate_profile_set(struct device *dev, + struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); + int ret; + ++ if (!policy) ++ return -ENODEV; ++ + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); +@@ -1877,6 +1880,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + struct amd_cpudata *cpudata; + union perf_cached perf; + struct device *dev; ++ int default_epp; + int ret; + + /* +@@ -1925,6 +1929,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 */ ++ default_epp = amd_pstate_get_epp(cpudata); ++ if (default_epp < 0) { ++ ret = default_epp; ++ goto free_cpudata1; ++ } ++ + /* + * Set the policy to provide a valid fallback value in case + * the default cpufreq governor is neither powersave nor performance. +@@ -1932,7 +1943,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + if (amd_pstate_acpi_pm_profile_server() || + amd_pstate_acpi_pm_profile_undefined()) { + policy->policy = CPUFREQ_POLICY_PERFORMANCE; +- cpudata->epp_default_ac = cpudata->epp_default_dc = amd_pstate_get_epp(cpudata); ++ cpudata->epp_default_ac = cpudata->epp_default_dc = default_epp; + cpudata->current_profile = PLATFORM_PROFILE_PERFORMANCE; + } else { + policy->policy = CPUFREQ_POLICY_POWERSAVE; +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 +@@ -32,6 +32,7 @@ + * @min_limit_perf: Cached value of the performance corresponding to policy->min + * @max_limit_perf: Cached value of the performance corresponding to policy->max + * @bios_min_perf: Cached perf value corresponding to the "Requested CPU Min Frequency" BIOS option ++ * @val: Raw 64-bit value for atomic access via READ_ONCE()/WRITE_ONCE() + */ + union perf_cached { + struct { +@@ -89,7 +90,12 @@ struct amd_aperf_mperf { + * @epp_default_ac: Default EPP value for AC power source + * @epp_default_dc: Default EPP value for DC power source + * @dynamic_epp: Whether dynamic EPP is enabled ++ * @raw_epp: Whether the last EPP write was a raw numeric value rather than a ++ * named preference + * @power_nb: Notifier block for power events ++ * @current_profile: Currently selected platform profile option ++ * @ppdev: Device registered with the platform profile handler ++ * @profile_name: Name under which @ppdev is registered + * + * The amd_cpudata is key private data for each CPU thread in AMD P-State, and + * represents all the attributes and goals that AMD P-State requests at runtime. diff --git a/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch.sig b/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch.sig new file mode 100644 index 0000000..7f218b5 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch b/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch new file mode 100644 index 0000000..61ea14c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch @@ -0,0 +1,18 @@ +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 +@@ -1929,12 +1929,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 diff --git a/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch.sig b/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch.sig new file mode 100644 index 0000000..7371ce6 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0250-zsmalloc.patch b/pkgbuilds/linux-omarchy/0250-zsmalloc.patch new file mode 100644 index 0000000..43793e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0250-zsmalloc.patch @@ -0,0 +1,396 @@ +diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c +--- a/mm/zsmalloc.c ++++ b/mm/zsmalloc.c +@@ -21,6 +21,10 @@ + * pool->lock + * 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/0250-zsmalloc.patch.sig b/pkgbuilds/linux-omarchy/0250-zsmalloc.patch.sig new file mode 100644 index 0000000..d6828f5 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0250-zsmalloc.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch b/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch new file mode 100644 index 0000000..f3b0e56 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0260-mglru-exec-protect.patch.sig b/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch.sig new file mode 100644 index 0000000..7023e21 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch b/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch new file mode 100644 index 0000000..afe8241 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0270-ksm-rmap-walk.patch.sig b/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch.sig new file mode 100644 index 0000000..3262347 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0280-mm-updates.patch b/pkgbuilds/linux-omarchy/0280-mm-updates.patch new file mode 100644 index 0000000..46a53c5 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0280-mm-updates.patch @@ -0,0 +1,1270 @@ +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 +@@ -270,10 +270,15 @@ struct mem_cgroup { + #endif + int kmemcg_id; + +- struct memcg_vmstats_percpu __percpu *vmstats_percpu; +- + #ifdef CONFIG_CGROUP_WRITEBACK + struct list_head cgwb_list; ++#endif ++ ++ /* Keep the hot per-CPU stats pointer away from memory event counters. */ ++ struct memcg_vmstats_percpu __percpu *vmstats_percpu ++ ____cacheline_aligned_in_smp; ++ ++#ifdef CONFIG_CGROUP_WRITEBACK + struct wb_domain cgwb_domain; + struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT]; + #endif +@@ -947,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); + +@@ -1399,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; +@@ -3793,35 +3825,40 @@ int mas_alloc_cyclic(struct ma_state *mas, unsigned long *startp, + void *entry, unsigned long range_lo, unsigned long range_hi, + unsigned long *next, gfp_t gfp) + { +- unsigned long min = range_lo; +- int ret = 0; ++ int ret; ++ unsigned long min; ++ ++ min = range_lo; ++ do { ++ range_lo = max(min, *next); ++ ret = mas_empty_area(mas, range_lo, range_hi, 1); ++ if (ret < 0 && range_lo > min) { ++ mas_reset(mas); ++ ret = mas_empty_area(mas, min, range_hi, 1); ++ if (ret == 0) ++ ret = 1; ++ } ++ if (ret < 0) ++ goto out; ++ ++ mas_insert(mas, entry); ++ } while (mas_nomem(mas, gfp)); ++ ++ if (mas_is_err(mas)) { ++ ret = xa_err(mas->node); ++ goto out; ++ } + +- range_lo = max(min, *next); +- ret = mas_empty_area(mas, range_lo, range_hi, 1); + if ((mas->tree->ma_flags & MT_FLAGS_ALLOC_WRAPPED) && ret == 0) { + mas->tree->ma_flags &= ~MT_FLAGS_ALLOC_WRAPPED; + ret = 1; + } +- if (ret < 0 && range_lo > min) { +- mas_reset(mas); +- ret = mas_empty_area(mas, min, range_hi, 1); +- if (ret == 0) +- ret = 1; +- } +- if (ret < 0) +- return ret; +- +- do { +- mas_insert(mas, entry); +- } while (mas_nomem(mas, gfp)); +- if (mas_is_err(mas)) +- return xa_err(mas->node); +- + *startp = mas->index; + *next = *startp + 1; + if (*next == 0) + mas->tree->ma_flags |= MT_FLAGS_ALLOC_WRAPPED; + ++out: + mas_destroy(mas); + return ret; + } +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); +@@ -1153,7 +1086,16 @@ static void lruvec_reparent_lru(struct lruvec *child_lruvec, + for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { + unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + ++ if (!size) ++ continue; ++ ++ /* ++ * The folios are accounted to the parent from now on, so the ++ * size has to be moved, not just copied. Leaving it behind ++ * makes the dying child describe folios it no longer owns. ++ */ + mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); ++ mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); + } + } + +@@ -1164,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; +@@ -4571,7 +4629,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, + for_each_managed_zone_pgdat(zone, NODE_DATA(nid), zid, MAX_NR_ZONES - 1) { + unsigned long size = mem_cgroup_get_zone_lru_size(child_lruvec, lru, zid); + ++ if (!size) ++ continue; ++ ++ /* Move the accounting, do not duplicate it. */ + mem_cgroup_update_lru_size(parent_lruvec, lru, zid, size); ++ mem_cgroup_update_lru_size(child_lruvec, lru, zid, -(long)size); + } + } + } +@@ -4814,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); +@@ -4826,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) +@@ -4838,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); +@@ -4887,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/0280-mm-updates.patch.sig b/pkgbuilds/linux-omarchy/0280-mm-updates.patch.sig new file mode 100644 index 0000000..4e1b338 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0280-mm-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch b/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch new file mode 100644 index 0000000..a14613c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch @@ -0,0 +1,97 @@ +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 +@@ -202,6 +202,8 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_readNCount_body_bmi2( + { + return FSE_readNCount_body(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } ++#else ++#define FSE_readNCount_body_bmi2 FSE_readNCount_body_default + #endif + + size_t FSE_readNCount_bmi2( +@@ -323,6 +325,8 @@ static BMI2_TARGET_ATTRIBUTE size_t HUF_readStats_body_bmi2(BYTE* huffWeight, si + { + return HUF_readStats_body(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize, 1); + } ++#else ++#define HUF_readStats_body_bmi2 HUF_readStats_body_default + #endif + + size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, +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 +@@ -300,6 +300,8 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, siz + { + return FSE_decompress_wksp_body(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, 1); + } ++#else ++#define FSE_decompress_wksp_body_bmi2 FSE_decompress_wksp_body_default + #endif + + 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) +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 +@@ -415,6 +415,10 @@ ZSTD_encodeSequences_bmi2( + sequences, nbSeq, longOffsets); + } + ++#else ++ ++#define ZSTD_encodeSequences_bmi2 ZSTD_encodeSequences_default ++ + #endif + + size_t ZSTD_encodeSequences( +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 +@@ -700,6 +700,8 @@ size_t HUF_decompress4X1_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } ++#else ++#define HUF_decompress4X1_usingDTable_internal_bmi2 HUF_decompress4X1_usingDTable_internal_default + #endif + + static +@@ -1503,6 +1505,8 @@ size_t HUF_decompress4X2_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo + size_t cSrcSize, HUF_DTable const* DTable) { + return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable); + } ++#else ++#define HUF_decompress4X2_usingDTable_internal_bmi2 HUF_decompress4X2_usingDTable_internal_default + #endif + + static +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 +@@ -622,6 +622,8 @@ BMI2_TARGET_ATTRIBUTE static void ZSTD_buildFSETable_body_bmi2(ZSTD_seqSymbol* d + ZSTD_buildFSETable_body(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); + } ++#else ++#define ZSTD_buildFSETable_body_bmi2 ZSTD_buildFSETable_body_default + #endif + + void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, +@@ -1934,6 +1936,16 @@ ZSTD_decompressSequencesLong_bmi2(ZSTD_DCtx* dctx, + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ + ++#else ++ ++#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG ++#define ZSTD_decompressSequences_bmi2 ZSTD_decompressSequences_default ++#define ZSTD_decompressSequencesSplitLitBuffer_bmi2 ZSTD_decompressSequencesSplitLitBuffer_default ++#endif ++#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT ++#define ZSTD_decompressSequencesLong_bmi2 ZSTD_decompressSequencesLong_default ++#endif ++ + #endif /* DYNAMIC_BMI2 */ + + #ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG diff --git a/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch.sig b/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch.sig new file mode 100644 index 0000000..6707301 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch b/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch new file mode 100644 index 0000000..9f0b872 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig b/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig new file mode 100644 index 0000000..e39aa6c Binary files /dev/null and b/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch b/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch new file mode 100644 index 0000000..4007e61 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch @@ -0,0 +1,44 @@ +diff --git a/crypto/zstd.c b/crypto/zstd.c +--- a/crypto/zstd.c ++++ b/crypto/zstd.c +@@ -96,6 +96,7 @@ static int zstd_compress_one(struct acomp_req *req, struct zstd_ctx *ctx, + + static int zstd_compress(struct acomp_req *req) + { ++ bool stream_initialized = false; + struct crypto_acomp_stream *s; + unsigned int pos, scur, dcur; + unsigned int total_out = 0; +@@ -115,12 +116,6 @@ static int zstd_compress(struct acomp_req *req) + if (ret) + goto out; + +- ctx->cctx = 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/0292-crypto-zstd-defer-cstream-init.patch.sig b/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch.sig new file mode 100644 index 0000000..eb7a182 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch b/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch new file mode 100644 index 0000000..224f1ad --- /dev/null +++ b/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch @@ -0,0 +1,44 @@ +diff --git a/crypto/zstd.c b/crypto/zstd.c +--- a/crypto/zstd.c ++++ b/crypto/zstd.c +@@ -215,6 +215,7 @@ static int zstd_decompress_one(struct acomp_req *req, struct zstd_ctx *ctx, + + static int zstd_decompress(struct acomp_req *req) + { ++ bool stream_initialized = false; + struct crypto_acomp_stream *s; + unsigned int total_out = 0; + unsigned int scur, dcur; +@@ -232,12 +233,6 @@ static int zstd_decompress(struct acomp_req *req) + if (ret) + goto out; + +- ctx->dctx = 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/0293-crypto-zstd-defer-dstream-init.patch.sig b/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch.sig new file mode 100644 index 0000000..697fca0 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch b/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch new file mode 100644 index 0000000..2700fc9 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch @@ -0,0 +1,363 @@ +--- a/crypto/af_alg.c ++++ b/crypto/af_alg.c +@@ -8,6 +8,7 @@ + */ + + #include ++#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/0295-af-alg-restrict.patch.sig b/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch.sig new file mode 100644 index 0000000..0a98371 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch b/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch new file mode 100644 index 0000000..602ef86 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch @@ -0,0 +1,12 @@ +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 +@@ -806,7 +806,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) + pmdval_t val = pmd_val(pmd), oldval = val; + pmd_t pmd_result; + +- val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY); ++ val &= _HPAGE_CHG_MASK; + val |= check_pgprot(newprot) & ~_HPAGE_CHG_MASK; + val = flip_protnone_guard(oldval, val, PHYSICAL_PMD_PAGE_MASK); + diff --git a/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch.sig b/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch.sig new file mode 100644 index 0000000..b01f81d Binary files /dev/null and b/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0300-btrfs.patch b/pkgbuilds/linux-omarchy/0300-btrfs.patch new file mode 100644 index 0000000..0cb9f8d --- /dev/null +++ b/pkgbuilds/linux-omarchy/0300-btrfs.patch @@ -0,0 +1,5023 @@ +diff --git a/fs/btrfs/acl.c b/fs/btrfs/acl.c +--- a/fs/btrfs/acl.c ++++ b/fs/btrfs/acl.c +@@ -15,6 +15,7 @@ + #include "xattr.h" + #include "acl.h" + #include "misc.h" ++#include "btrfs_inode.h" + + struct posix_acl *btrfs_get_acl(struct inode *inode, int type, bool rcu) + { +@@ -107,6 +108,9 @@ int btrfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, + struct inode *inode = d_inode(dentry); + umode_t old_mode = inode->i_mode; + ++ if (btrfs_root_readonly(BTRFS_I(inode)->root)) ++ return -EROFS; ++ + if (type == ACL_TYPE_ACCESS && acl) { + ret = posix_acl_update_mode(idmap, inode, + &inode->i_mode, &acl); +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 +@@ -2047,6 +2047,11 @@ static int btrfs_reclaim_block_group(struct btrfs_block_group *bg, int *reclaime + + trace_btrfs_reclaim_block_group(bg); + ret = btrfs_relocate_chunk(fs_info, bg->start, false); ++ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) { ++ btrfs_dec_block_group_ro(bg); ++ btrfs_debug(fs_info, "deferring reclaim of chunk %llu", bg->start); ++ return ret; ++ } + if (ret) { + btrfs_dec_block_group_ro(bg); + btrfs_err(fs_info, "error relocating chunk %llu", +@@ -2113,7 +2118,8 @@ void btrfs_reclaim_block_groups(struct btrfs_fs_info *fs_info, unsigned int limi + spin_unlock(&fs_info->unused_bgs_lock); + ret = btrfs_reclaim_block_group(bg, &reclaimed); + +- if (ret && !READ_ONCE(space_info->periodic_reclaim)) ++ if ((btrfs_is_zoned(fs_info) && ret == -EAGAIN) || ++ (ret && !READ_ONCE(space_info->periodic_reclaim))) + btrfs_link_bg_list(bg, &retry_list); + btrfs_put_block_group(bg); + +@@ -2624,10 +2630,9 @@ static int fill_dummy_bgs(struct btrfs_fs_info *fs_info) + + /* Fill dummy cache as FULL */ + bg->length = 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); + /* +@@ -3916,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); + } + } +@@ -1130,20 +1130,15 @@ static_assert(PAGE_ALIGNED(CLUSTER_SIZE)); + * + * - Extent bits are locked + */ +-static int defrag_one_locked_target(struct btrfs_inode *inode, +- struct defrag_target_range *target, +- struct folio **folios, int nr_pages, +- struct extent_state **cached_state) ++static void defrag_one_locked_target(struct btrfs_inode *inode, ++ struct defrag_target_range *target, ++ struct folio **folios, int nr_pages, ++ struct extent_state **cached_state) + { + struct btrfs_fs_info *fs_info = inode->root->fs_info; +- struct extent_changeset *data_reserved = NULL; + const u64 start = target->start; + const u64 len = target->len; +- int ret = 0; + +- ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); +- if (ret < 0) +- return ret; + btrfs_clear_extent_bit(&inode->io_tree, start, start + len - 1, + EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | + EXTENT_DEFRAG, cached_state); +@@ -1164,10 +1159,6 @@ static int defrag_one_locked_target(struct btrfs_inode *inode, + continue; + btrfs_folio_clamp_set_dirty(fs_info, folio, start, len); + } +- btrfs_delalloc_release_extents(inode, len); +- extent_changeset_free(data_reserved); +- +- return ret; + } + + static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, +@@ -1178,11 +1169,13 @@ 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) - + (start >> PAGE_SHIFT) + 1; ++ struct extent_changeset *data_reserved = NULL; ++ u64 last_defrag_end = start; + int ret = 0; + + ASSERT(nr_pages <= CLUSTER_SIZE / PAGE_SIZE); +@@ -1192,6 +1185,20 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + if (!folios) + return -ENOMEM; + ++ /* ++ * Reserve delalloc space before locking the range and before locking ++ * and dirtying any folios - otherwise we could deadlock, for example ++ * after defrag of one range we dirty folios and keep them locked when ++ * we move to the next range, so reserving delalloc space right before ++ * each range could trigger flushing of delalloc and deadlock on the ++ * extent lock or trigger a transaction commit with flushoncommit, which ++ * can either deadlock on the lock of a folio made dirty in the previous ++ * range or the extent lock. ++ */ ++ ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); ++ if (ret < 0) ++ return ret; ++ + /* Prepare all pages */ + for (int i = 0; cur < start + len && i < nr_pages; i++) { + folios[i] = defrag_prepare_one_folio(inode, cur >> PAGE_SHIFT); +@@ -1225,15 +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) { +- ret = defrag_one_locked_target(inode, entry, folios, nr_pages, +- &cached_state); +- if (ret < 0) +- break; +- } +- + list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->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; + kfree(entry); + } + unlock_extent: +@@ -1245,7 +1249,12 @@ 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, ++ start + len - last_defrag_end, true); ++ extent_changeset_free(data_reserved); ++ + return ret; + } + +@@ -1310,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 +@@ -271,14 +271,15 @@ int btree_csum_one_bio(struct btrfs_bio *bbio) + return -EIO; + + /* +- * If an extent_buffer is marked as EXTENT_BUFFER_ZONED_ZEROOUT, don't +- * checksum it but zero-out its content. This is done to preserve +- * ordering of I/O without unnecessarily writing out data. ++ * An extent_buffer marked EXTENT_BUFFER_ZONED_ZEROOUT is written out as ++ * zeros to preserve ordering of I/O without persisting the now ++ * unnecessary block. The bio is fed from the shared zero page (see ++ * write_one_eb()), so there is nothing to checksum here. Crucially, the ++ * buffer's own content is left intact: it may still be referenced, e.g. ++ * btrfs_free_tree_block() reads its header to add a delayed reference. + */ +- if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) { +- memzero_extent_buffer(eb, 0, eb->len); ++ if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) + return 0; +- } + + if (WARN_ON_ONCE(found_start != eb->start)) + return -EIO; +@@ -590,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); + } + + /* +@@ -608,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; +@@ -666,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); +@@ -2052,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; + } +@@ -2062,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; + } + +@@ -2303,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; + } + +@@ -2395,8 +2395,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); + +@@ -2438,24 +2438,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; + } +@@ -2905,7 +2905,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; +@@ -2979,8 +2978,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; + } +@@ -3083,7 +3082,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; + } + } +@@ -3094,7 +3093,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; + } + } +@@ -3105,7 +3104,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; + } + /* +@@ -3139,7 +3139,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; + } + +@@ -3149,7 +3149,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; + } + } +@@ -3177,7 +3177,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; + } + } +@@ -3314,6 +3314,8 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info) + */ + rcu_read_lock(); + xa_for_each(&fs_info->buffer_tree, index, eb) { ++ unsigned int refs; ++ + /* Increase the ref so that the eb won't disappear. */ + if (!refcount_inc_not_zero(&eb->refs)) + continue; +@@ -3323,17 +3325,27 @@ static void invalidate_and_check_btree_folios(struct btrfs_fs_info *fs_info) + if (test_bit(EXTENT_BUFFER_READING, &eb->bflags)) + wait_on_bit_io(&eb->bflags, EXTENT_BUFFER_READING, + TASK_UNINTERRUPTIBLE); ++ /* ++ * We hold the spinlock to make sure above ++ * EXTENT_BUFFER_READING flag is cleared with the held ++ * ref dropped. ++ * Or we can hit a race window and lead to false alerts. ++ */ ++ spin_lock(&eb->refs_lock); ++ refs = refcount_read(&eb->refs); ++ spin_unlock(&eb->refs_lock); ++ + /* + * The refs threshold is 2, one held by us at the beginning + * of the loop, one for the ownership in the buffer tree. + */ +- if (unlikely(refcount_read(&eb->refs) > 2 || extent_buffer_under_io(eb))) { ++ if (unlikely(refs > 2 || extent_buffer_under_io(eb))) { + WARN_ON_ONCE(IS_ENABLED(CONFIG_BTRFS_DEBUG)); + btrfs_warn(fs_info, + "unable to release extent buffer %llu owner %llu gen %llu refs %u flags 0x%lx", + eb->start, btrfs_header_owner(eb), + btrfs_header_generation(eb), +- refcount_read(&eb->refs), eb->bflags); ++ refs, eb->bflags); + } + free_extent_buffer(eb); + rcu_read_lock(); +@@ -3355,7 +3367,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; +@@ -3464,7 +3475,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)); + +@@ -3483,7 +3493,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) +@@ -3549,7 +3558,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; + } + +@@ -3568,7 +3577,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; + } + +@@ -3598,7 +3607,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; + } + +@@ -3621,72 +3630,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; + } + +@@ -3742,7 +3752,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; + } +@@ -3763,7 +3773,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; + } +@@ -3879,8 +3889,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; + } +@@ -3898,8 +3908,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; + } +@@ -4381,6 +4391,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 +@@ -4511,7 +4536,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)); + } + } + +@@ -4550,6 +4575,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. +@@ -4998,6 +5030,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--; + +@@ -2379,14 +2397,17 @@ static struct extent_buffer *find_extent_buffer_nolock( + static void end_bbio_meta_write(struct btrfs_bio *bbio) + { + struct extent_buffer *eb = bbio->private; +- struct folio_iter fi; + + if (bbio->bio.bi_status != BLK_STS_OK) + set_btree_ioerr(eb); + +- bio_for_each_folio_all(fi, &bbio->bio) { +- btrfs_meta_folio_clear_writeback(fi.folio, eb); +- } ++ /* ++ * Clear writeback on the buffer's own folios. The bio may carry the ++ * shared zero page instead (EXTENT_BUFFER_ZONED_ZEROOUT), so iterate ++ * the extent buffer folios rather than the bio folios. ++ */ ++ for (int i = 0; i < num_extent_folios(eb); i++) ++ btrfs_meta_folio_clear_writeback(eb->folios[i], eb); + + buffer_tree_clear_mark(eb, PAGECACHE_TAG_WRITEBACK); + clear_and_wake_up_bit(EXTENT_BUFFER_WRITEBACK, &eb->bflags); +@@ -2427,7 +2448,8 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, + struct btrfs_fs_info *fs_info = eb->fs_info; + struct btrfs_bio *bbio; + +- prepare_eb_write(eb); ++ if (!test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) ++ prepare_eb_write(eb); + + bbio = btrfs_bio_alloc(INLINE_EXTENT_BUFFER_PAGES, + REQ_OP_WRITE | REQ_META | wbc_to_write_flags(wbc), +@@ -2447,8 +2469,21 @@ static noinline_for_stack void write_one_eb(struct extent_buffer *eb, + btrfs_meta_folio_set_writeback(folio, eb); + if (!folio_test_dirty(folio)) + wbc->nr_to_write -= folio_nr_pages(folio); +- bio_add_folio_nofail(&bbio->bio, folio, range_len, +- offset_in_folio(folio, range_start)); ++ if (test_bit(EXTENT_BUFFER_ZONED_ZEROOUT, &eb->bflags)) { ++ u32 off = 0; ++ ++ while (off < range_len) { ++ u32 add = min_t(u32, PAGE_SIZE, range_len - off); ++ ++ bio_add_folio_nofail(&bbio->bio, ++ page_folio(ZERO_PAGE(0)), ++ add, 0); ++ off += add; ++ } ++ } else { ++ bio_add_folio_nofail(&bbio->bio, folio, range_len, ++ offset_in_folio(folio, range_start)); ++ } + wbc_account_cgroup_owner(wbc, folio, range_len); + folio_unlock(folio); + } +@@ -2496,6 +2531,76 @@ void btrfs_btree_wait_writeback_range(struct btrfs_fs_info *fs_info, u64 start, + } + } + ++static int write_meta_extent_buffer(struct btrfs_eb_write_context *ctx, ++ struct writeback_control *wbc) ++{ ++ struct extent_buffer *eb = ctx->eb; ++ int ret; ++ ++ ret = btrfs_check_meta_write_pointer(eb->fs_info, ctx); ++ if (ret) ++ return ret; ++ ++ if (!lock_extent_buffer_for_io(eb, wbc)) ++ return 0; ++ ++ /* Implies write in zoned mode. */ ++ if (ctx->zoned_bg) { ++ /* Mark the last eb in the block group. */ ++ btrfs_schedule_zone_finish_bg(ctx->zoned_bg, eb); ++ ctx->zoned_bg->meta_write_pointer += eb->len; ++ } ++ write_one_eb(eb, wbc); ++ return 0; ++} ++ ++/* ++ * On a zoned filesystem, write out the currently dirty metadata extent buffers ++ * of @bg. Used to flush the active metadata/system block group before the ++ * ascending-address walk in btree_writepages(), so that walk can pivot the ++ * active block group away (finishing it) instead of aborting the commit; see ++ * the caller for details. ++ */ ++static void flush_active_meta_bg(struct address_space *mapping, ++ struct writeback_control *wbc, ++ struct btrfs_eb_write_context *ctx, ++ struct btrfs_block_group *bg) ++{ ++ struct btrfs_fs_info *fs_info = inode_to_fs_info(mapping->host); ++ unsigned long index = bg->start >> fs_info->nodesize_bits; ++ unsigned long end = (btrfs_block_group_end(bg) - 1) >> fs_info->nodesize_bits; ++ struct eb_batch batch; ++ unsigned int nr_ebs; ++ ++ ASSERT(btrfs_is_zoned(fs_info)); ++ lockdep_assert_held(&fs_info->zoned_meta_io_lock); ++ ++ eb_batch_init(&batch); ++ while (index <= end && ++ (nr_ebs = buffer_tree_get_ebs_tag(fs_info, &index, end, ++ PAGECACHE_TAG_DIRTY, &batch))) { ++ struct extent_buffer *eb; ++ ++ while ((eb = eb_batch_next(&batch)) != NULL) { ++ ctx->eb = eb; ++ ++ /* ++ * If the eb is behind the write pointer (-EBUSY, e.g. ++ * already being written by someone else) skip it and ++ * carry on. Only a hole at the write pointer (-EAGAIN) ++ * stops the flush. The main walk in btree_writepages() ++ * then deals with it. ++ */ ++ if (write_meta_extent_buffer(ctx, wbc) == -EAGAIN) { ++ eb_batch_release(&batch); ++ return; ++ } ++ } ++ eb_batch_release(&batch); ++ cond_resched(); ++ } ++} ++ + int btree_writepages(struct address_space *mapping, struct writeback_control *wbc) + { + struct btrfs_eb_write_context ctx = { .wbc = wbc }; +@@ -2531,6 +2636,22 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb + else + tag = PAGECACHE_TAG_DIRTY; + btrfs_zoned_meta_io_lock(fs_info); ++ ++ /* ++ * On a zoned filesystem, flush the currently active metadata/system ++ * block group(s) first, under this same lock, so the ascending-address ++ * walk below can pivot the active block group instead of aborting the ++ * transaction commit with -EAGAIN. ++ */ ++ if (btrfs_is_zoned(fs_info) && wbc->sync_mode == WB_SYNC_ALL && ++ !wbc->for_sync) { ++ if (fs_info->active_meta_bg) ++ flush_active_meta_bg(mapping, wbc, &ctx, ++ fs_info->active_meta_bg); ++ if (fs_info->active_system_bg) ++ flush_active_meta_bg(mapping, wbc, &ctx, ++ fs_info->active_system_bg); ++ } + retry: + if (wbc->sync_mode == WB_SYNC_ALL) + buffer_tree_tag_for_writeback(fs_info, index, end); +@@ -2541,28 +2662,13 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb + while ((eb = eb_batch_next(&batch)) != NULL) { + ctx.eb = eb; + +- ret = btrfs_check_meta_write_pointer(eb->fs_info, &ctx); +- if (ret) { +- if (ret == -EBUSY) +- ret = 0; +- +- if (ret) { +- done = true; +- break; +- } +- continue; ++ ret = write_meta_extent_buffer(&ctx, wbc); ++ if (ret == -EBUSY) { ++ ret = 0; ++ } else if (ret) { ++ done = true; ++ break; + } +- +- if (!lock_extent_buffer_for_io(eb, wbc)) +- continue; +- +- /* Implies write in zoned mode. */ +- if (ctx.zoned_bg) { +- /* Mark the last eb in the block group. */ +- btrfs_schedule_zone_finish_bg(ctx.zoned_bg, eb); +- ctx.zoned_bg->meta_write_pointer += eb->len; +- } +- write_one_eb(eb, wbc); + } + nr_to_write_done = (wbc->nr_to_write <= 0); + eb_batch_release(&batch); +@@ -2888,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); +@@ -3129,47 +3247,71 @@ static inline void btrfs_release_extent_buffer(struct extent_buffer *eb) + kmem_cache_free(extent_buffer_cache, eb); + } + ++/* ++ * Claim a slot to track an extent buffer in, evicting the coldest tracked buffer ++ * when the array is full. ++ * ++ * Slots fill in order until the array is full. After that a CLOCK (second ++ * chance) scan advances the hand, clearing one reference bit per step, until ++ * it lands on an unreferenced slot whose buffer is evicted. Clearing a bit per ++ * step bounds the scan to BTRFS_INHIBITED_EBS_SLOTS iterations. ++ */ ++static int btrfs_inhibit_claim_slot(struct btrfs_trans_handle *trans) ++{ ++ int slot; ++ ++ if (trans->nr_inhibited_ebs < BTRFS_INHIBITED_EBS_SLOTS) ++ return trans->nr_inhibited_ebs++; ++ ++ while (trans->inhibited_ebs_referenced & (1U << trans->inhibited_ebs_hand)) { ++ trans->inhibited_ebs_referenced &= ~(1U << trans->inhibited_ebs_hand); ++ trans->inhibited_ebs_hand = ++ (trans->inhibited_ebs_hand + 1) % BTRFS_INHIBITED_EBS_SLOTS; ++ } ++ slot = trans->inhibited_ebs_hand; ++ trans->inhibited_ebs_hand = (trans->inhibited_ebs_hand + 1) % BTRFS_INHIBITED_EBS_SLOTS; ++ ++ atomic_dec(&trans->inhibited_ebs[slot]->writeback_inhibitors); ++ free_extent_buffer(trans->inhibited_ebs[slot]); ++ ++ return slot; ++} ++ + /* + * Inhibit writeback on buffer during transaction. + * + * @trans: transaction handle that will own the inhibitor + * @eb: extent buffer to inhibit writeback on + * +- * Attempt to track this extent buffer in the transaction's inhibited set. If +- * memory allocation fails, the buffer is simply not tracked. It may be written +- * back and need re-COW, which is the original behavior. This is acceptable +- * since inhibiting writeback is an optimization. ++ * Attempt to track this extent buffer in the transaction's inhibited set. When ++ * the set is full the coldest tracked buffer is evicted instead. An untracked ++ * buffer may be written back and need re-COW, which is the original behavior. ++ * This is acceptable since inhibiting writeback is an optimization. + */ + void btrfs_inhibit_eb_writeback(struct btrfs_trans_handle *trans, struct extent_buffer *eb) + { +- unsigned long index = eb->start >> trans->fs_info->nodesize_bits; +- void *old; ++ int slot; + + lockdep_assert_held(&eb->lock); +- /* Check if already inhibited by this handle. */ +- old = xa_load(&trans->writeback_inhibited_ebs, index); +- if (old == eb) +- return; + +- /* Take reference for the xarray entry. */ ++ /* Already tracked: set its reference bit (second chance) and return. */ ++ for (int i = 0; i < trans->nr_inhibited_ebs; i++) { ++ if (trans->inhibited_ebs[i] == eb) { ++ trans->inhibited_ebs_referenced |= 1U << i; ++ return; ++ } ++ } ++ ++ slot = btrfs_inhibit_claim_slot(trans); ++ ++ /* ++ * Pin the eb while the array holds a raw pointer to it; the counter is ++ * what lock_extent_buffer_for_io() checks. ++ */ + refcount_inc(&eb->refs); +- +- old = xa_store(&trans->writeback_inhibited_ebs, index, eb, GFP_NOFS); +- if (xa_is_err(old)) { +- /* Allocation failed, just skip inhibiting this buffer. */ +- free_extent_buffer(eb); +- return; +- } +- +- /* Handle replacement of different eb at same index. */ +- if (old && old != eb) { +- struct extent_buffer *old_eb = old; +- +- atomic_dec(&old_eb->writeback_inhibitors); +- free_extent_buffer(old_eb); +- } +- + atomic_inc(&eb->writeback_inhibitors); ++ trans->inhibited_ebs[slot] = eb; ++ trans->inhibited_ebs_referenced |= 1U << slot; + } + + /* +@@ -3177,22 +3319,18 @@ void btrfs_inhibit_eb_writeback(struct btrfs_trans_handle *trans, struct extent_ + */ + void btrfs_uninhibit_all_eb_writeback(struct btrfs_trans_handle *trans) + { +- struct extent_buffer *eb; +- unsigned long index; +- +- xa_for_each(&trans->writeback_inhibited_ebs, index, eb) { +- atomic_dec(&eb->writeback_inhibitors); +- free_extent_buffer(eb); ++ for (int i = 0; i < trans->nr_inhibited_ebs; i++) { ++ atomic_dec(&trans->inhibited_ebs[i]->writeback_inhibitors); ++ free_extent_buffer(trans->inhibited_ebs[i]); + } +- xa_destroy(&trans->writeback_inhibited_ebs); ++ trans->nr_inhibited_ebs = 0; ++ trans->inhibited_ebs_referenced = 0; ++ 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; +@@ -3205,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; + } + +@@ -3502,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) + { + +@@ -3524,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)) +@@ -3532,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; +@@ -3563,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 +@@ -3579,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; +@@ -3609,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 +@@ -3622,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++; + + /* +@@ -3758,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 +@@ -3842,12 +4063,31 @@ static int release_extent_buffer(struct extent_buffer *eb) + return 0; + } + +-void free_extent_buffer(struct extent_buffer *eb) ++static void clear_extent_buffer_reading(struct extent_buffer *eb) ++{ ++ clear_and_wake_up_bit(EXTENT_BUFFER_READING, &eb->bflags); ++} ++ ++static void free_extent_buffer_clear_reading(struct extent_buffer *eb, ++ bool clear_reading) + { + int refs; ++ + if (!eb) + return; + ++ /* ++ * We want to clear EXTENT_BUFFER_READING flag and decrease refs ++ * in the same critical section. ++ * This will make sure invalidate_and_check_btree_folios() won't ++ * see an eb with EXTENT_BUFFER_READING cleared but refs not yet ++ * decreased. ++ */ ++ if (clear_reading) { ++ spin_lock(&eb->refs_lock); ++ clear_extent_buffer_reading(eb); ++ } ++ + refs = refcount_read(&eb->refs); + while (1) { + if (test_bit(EXTENT_BUFFER_UNMAPPED, &eb->bflags)) { +@@ -3858,11 +4098,16 @@ void free_extent_buffer(struct extent_buffer *eb) + } + + /* Optimization to avoid locking eb->refs_lock. */ +- if (atomic_try_cmpxchg(&eb->refs.refs, &refs, refs - 1)) ++ if (atomic_try_cmpxchg(&eb->refs.refs, &refs, refs - 1)) { ++ if (clear_reading) ++ spin_unlock(&eb->refs_lock); + return; ++ } + } + +- spin_lock(&eb->refs_lock); ++ if (!clear_reading) ++ spin_lock(&eb->refs_lock); ++ + if (refcount_read(&eb->refs) == 2 && + test_bit(EXTENT_BUFFER_STALE, &eb->bflags) && + !extent_buffer_under_io(eb) && +@@ -3876,6 +4121,11 @@ void free_extent_buffer(struct extent_buffer *eb) + release_extent_buffer(eb); + } + ++void free_extent_buffer(struct extent_buffer *eb) ++{ ++ return free_extent_buffer_clear_reading(eb, false); ++} ++ + void free_extent_buffer_stale(struct extent_buffer *eb) + { + if (!eb) +@@ -3890,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) + { +@@ -3914,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) +@@ -4001,11 +4293,6 @@ void set_extent_buffer_uptodate(struct extent_buffer *eb) + btrfs_meta_folio_set_uptodate(eb->folios[i], eb); + } + +-static void clear_extent_buffer_reading(struct extent_buffer *eb) +-{ +- clear_and_wake_up_bit(EXTENT_BUFFER_READING, &eb->bflags); +-} +- + static void end_bbio_meta_read(struct btrfs_bio *bbio) + { + struct extent_buffer *eb = bbio->private; +@@ -4029,8 +4316,7 @@ static void end_bbio_meta_read(struct btrfs_bio *bbio) + else + clear_extent_buffer_uptodate(eb); + +- clear_extent_buffer_reading(eb); +- free_extent_buffer(eb); ++ free_extent_buffer_clear_reading(eb, true); + + bio_put(&bbio->bio); + } +@@ -4789,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; + +@@ -4797,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; + +@@ -660,7 +660,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + range_end = round_up(start + len, sectorsize); + prev_extent_end = range_start; + +- btrfs_lock_extent(&inode->io_tree, range_start, range_end, &cached_state); ++ btrfs_lock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state); + + ret = fiemap_find_last_extent_offset(inode, path, &last_extent_end); + if (ret < 0) +@@ -840,7 +840,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + } + + out_unlock: +- btrfs_unlock_extent(&inode->io_tree, range_start, range_end, &cached_state); ++ btrfs_unlock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state); + + if (ret == BTRFS_FIEMAP_FLUSH_CACHE) { + btrfs_release_path(path); +diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c +--- a/fs/btrfs/file.c ++++ b/fs/btrfs/file.c +@@ -875,70 +875,64 @@ static noinline int prepare_one_folio(struct inode *inode, struct folio **folio_ + + /* + * Locks the extent and properly waits for data=ordered extents to finish +- * before allowing the folios to be modified if need. ++ * before allowing the folios to be modified. + * + * Return: +- * 1 - the extent is locked +- * 0 - the extent is not locked, and everything is OK ++ * 0 - the extent is locked + * -EAGAIN - need to prepare the folios again + */ + static noinline int +-lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct folio *folio, +- loff_t pos, size_t write_bytes, +- u64 *lockstart, u64 *lockend, bool nowait, +- struct extent_state **cached_state) ++lock_and_cleanup_extent(struct btrfs_inode *inode, struct folio *folio, ++ loff_t pos, size_t write_bytes, ++ u64 *lockstart, u64 *lockend, bool nowait, ++ struct extent_state **cached_state) + { + struct btrfs_fs_info *fs_info = inode->root->fs_info; ++ struct btrfs_ordered_extent *ordered; + u64 start_pos; + u64 last_pos; +- int ret = 0; + + start_pos = round_down(pos, fs_info->sectorsize); + last_pos = round_up(pos + write_bytes, fs_info->sectorsize) - 1; + +- if (start_pos < inode->vfs_inode.i_size) { +- struct btrfs_ordered_extent *ordered; +- +- if (nowait) { +- if (!btrfs_try_lock_extent(&inode->io_tree, start_pos, +- last_pos, cached_state)) { +- folio_unlock(folio); +- folio_put(folio); +- return -EAGAIN; +- } +- } else { +- btrfs_lock_extent(&inode->io_tree, start_pos, last_pos, +- cached_state); +- } +- +- ordered = btrfs_lookup_ordered_range(inode, start_pos, +- last_pos - start_pos + 1); +- if (ordered && +- ordered->file_offset + ordered->num_bytes > start_pos && +- ordered->file_offset <= last_pos) { +- btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos, +- cached_state); ++ if (nowait) { ++ if (!btrfs_try_lock_extent(&inode->io_tree, start_pos, ++ last_pos, cached_state)) { + folio_unlock(folio); + folio_put(folio); +- btrfs_start_ordered_extent(ordered); +- btrfs_put_ordered_extent(ordered); + return -EAGAIN; + } +- if (ordered) +- btrfs_put_ordered_extent(ordered); +- +- *lockstart = start_pos; +- *lockend = last_pos; +- ret = 1; ++ } else { ++ btrfs_lock_extent(&inode->io_tree, start_pos, last_pos, ++ cached_state); + } + ++ ordered = btrfs_lookup_ordered_range(inode, start_pos, ++ last_pos - start_pos + 1); ++ if (ordered && ++ ordered->file_offset + ordered->num_bytes > start_pos && ++ ordered->file_offset <= last_pos) { ++ btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos, ++ cached_state); ++ folio_unlock(folio); ++ folio_put(folio); ++ btrfs_start_ordered_extent(ordered); ++ btrfs_put_ordered_extent(ordered); ++ return -EAGAIN; ++ } ++ if (ordered) ++ btrfs_put_ordered_extent(ordered); ++ ++ *lockstart = start_pos; ++ *lockend = last_pos; ++ + /* + * We should be called after prepare_one_folio() which should have locked + * all pages in the range. + */ + WARN_ON(!folio_test_locked(folio)); + +- return ret; ++ return 0; + } + + /* +@@ -1195,7 +1189,6 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + const u64 reserved_start = round_down(start, fs_info->sectorsize); + u64 reserved_len; + struct folio *folio = NULL; +- int extents_locked; + u64 lockstart; + u64 lockend; + bool only_release_metadata = false; +@@ -1253,18 +1246,16 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + reserved_len = last_block - reserved_start; + } + +- extents_locked = lock_and_cleanup_extent_if_need(inode, folio, start, +- write_bytes, &lockstart, +- &lockend, nowait, +- &cached_state); +- if (extents_locked < 0) { +- if (!nowait && extents_locked == -EAGAIN) ++ ret = lock_and_cleanup_extent(inode, folio, start, write_bytes, ++ &lockstart, &lockend, nowait, &cached_state); ++ if (ret < 0) { ++ if (!nowait) + goto again; + + btrfs_delalloc_release_extents(inode, reserved_len); + release_space(inode, *data_reserved, reserved_start, reserved_len, + only_release_metadata); +- return extents_locked; ++ return ret; + } + + copied = copy_folio_from_iter_atomic(folio, offset_in_folio(folio, start), +@@ -1288,11 +1279,8 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + + /* No copied bytes, unlock, release reserved space and exit. */ + if (copied == 0) { +- if (extents_locked) +- btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, +- &cached_state); +- else +- btrfs_free_extent_state(cached_state); ++ btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, ++ &cached_state); + btrfs_delalloc_release_extents(inode, reserved_len); + release_space(inode, *data_reserved, reserved_start, reserved_len, + only_release_metadata); +@@ -1311,17 +1299,7 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter, + + ret = btrfs_dirty_folio(inode, folio, start, copied, &cached_state, + only_release_metadata); +- /* +- * If we have not locked the extent range, because the range's start +- * offset is >= i_size, we might still have a non-NULL cached extent +- * state, acquired while marking the extent range as delalloc through +- * btrfs_dirty_page(). Therefore free any possible cached extent state +- * to avoid a memory leak. +- */ +- if (extents_locked) +- btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state); +- else +- btrfs_free_extent_state(cached_state); ++ btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state); + + btrfs_delalloc_release_extents(inode, reserved_len); + if (ret) { +@@ -1595,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 +@@ -1679,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 +@@ -2683,8 +2657,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. +@@ -2888,7 +2862,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; + +@@ -2918,7 +2892,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); +@@ -2967,8 +2941,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; + } + +@@ -3050,7 +3051,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; +@@ -3907,7 +3908,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; + } + +@@ -4210,8 +4211,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)); + } + + /* +@@ -6825,8 +6826,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)); + } + + /* +@@ -10205,6 +10206,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); +@@ -10214,15 +10217,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 { +@@ -10300,8 +10312,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 +@@ -10551,6 +10565,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 && +@@ -10598,6 +10615,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)); + } + } + +@@ -4339,12 +4339,13 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode, + struct ulist_node *unode; + struct ulist_iterator uiter; + struct extent_changeset changeset; ++ const u32 sectorsize = root->fs_info->sectorsize; ++ const u64 aligned_start = round_down(start, sectorsize); ++ const u64 aligned_len = round_up(start + len, sectorsize) - aligned_start; + u64 freed = 0; + int ret; + + extent_changeset_init_bytes_only(&changeset); +- len = round_up(start + len, root->fs_info->sectorsize); +- start = round_down(start, root->fs_info->sectorsize); + + ULIST_ITER_INIT(&uiter); + while ((unode = ulist_next(&reserved->range_changed, &uiter))) { +@@ -4356,12 +4357,15 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode, + + extent_changeset_release(&changeset); + +- /* Only free range in range [start, start + len) */ +- if (range_start >= start + len || +- range_start + range_len <= start) ++ /* ++ * Only free the range within ++ * [aligned_start, aligned_start + aligned_len). ++ */ ++ if (range_start >= aligned_start + aligned_len || ++ range_start + range_len <= aligned_start) + continue; +- free_start = max(range_start, start); +- free_len = min(start + len, range_start + range_len) - ++ free_start = max(range_start, aligned_start); ++ free_len = min(aligned_start + aligned_len, range_start + range_len) - + free_start; + /* + * TODO: To also modify reserved->ranges_reserved to reflect +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; + } + +@@ -1537,6 +1538,33 @@ static void clear_reloc_root(struct btrfs_root *root) + clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state); + } + ++/* Drop the reloc trees of a relocation that is being deferred and retried. */ ++static void abort_reloc_roots(struct reloc_control *rc, struct list_head *list) ++{ ++ struct btrfs_fs_info *fs_info = rc->extent_root->fs_info; ++ struct btrfs_root *reloc_root, *tmp; ++ ++ list_for_each_entry_safe(reloc_root, tmp, list, root_list) { ++ struct btrfs_root *root; ++ ++ root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset, false); ++ if (!IS_ERR(root)) { ++ if (root->reloc_root == reloc_root) { ++ clear_reloc_root(root); ++ btrfs_put_root(reloc_root); ++ } ++ btrfs_put_root(root); ++ } ++ ++ btrfs_set_root_refs(&reloc_root->root_item, 0); ++ memset(&reloc_root->root_item.drop_progress, 0, sizeof(struct btrfs_disk_key)); ++ btrfs_set_root_drop_level(&reloc_root->root_item, 0); ++ ++ list_del_init(&reloc_root->root_list); ++ list_add_tail(&reloc_root->reloc_dirty_list, &rc->dirty_subvol_roots); ++ } ++} ++ + static int clean_dirty_subvols(struct reloc_control *rc) + { + struct btrfs_root *root; +@@ -1876,8 +1904,7 @@ int prepare_to_merge(struct reloc_control *rc, int err) + return err; + } + +-static noinline_for_stack +-void merge_reloc_roots(struct reloc_control *rc) ++static noinline_for_stack int merge_reloc_roots(struct reloc_control *rc) + { + struct btrfs_fs_info *fs_info = rc->extent_root->fs_info; + struct btrfs_root *root; +@@ -1975,7 +2002,15 @@ void merge_reloc_roots(struct reloc_control *rc) + goto again; + } + out: +- if (ret) { ++ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) { ++ abort_reloc_roots(rc, &reloc_roots); ++ ++ /* New reloc root may be added. */ ++ mutex_lock(&fs_info->reloc_mutex); ++ list_splice_init(&rc->reloc_roots, &reloc_roots); ++ mutex_unlock(&fs_info->reloc_mutex); ++ abort_reloc_roots(rc, &reloc_roots); ++ } else if (ret) { + btrfs_handle_fs_error(fs_info, ret, NULL); + free_reloc_roots(&reloc_roots); + +@@ -2001,6 +2036,7 @@ void merge_reloc_roots(struct reloc_control *rc) + * + * The remaining nodes will be cleaned up by put_reloc_control(). + */ ++ return ret; + } + + static void free_block_list(struct rb_root *blocks) +@@ -3730,7 +3766,9 @@ static noinline_for_stack int relocate_block_group(struct reloc_control *rc) + */ + err = prepare_to_merge(rc, err); + +- merge_reloc_roots(rc); ++ ret = merge_reloc_roots(rc); ++ if (ret && !err) ++ err = ret; + + rc->merge_reloc_tree = false; + unset_reloc_control(rc); +@@ -4114,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) +@@ -5720,7 +5758,9 @@ int btrfs_recover_relocation(struct btrfs_fs_info *fs_info) + goto out_drop_reloc_refs; + release_recovered_fs_roots(&recovered_roots, false); + +- merge_reloc_roots(rc); ++ ret = merge_reloc_roots(rc); ++ if (ret) ++ goto out_unset; + + unset_reloc_control(rc); + +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); + +@@ -1245,20 +1227,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); +@@ -1270,7 +1241,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) { +@@ -1349,7 +1320,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); +@@ -1844,7 +1815,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); + } + +@@ -1869,7 +1840,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; + +@@ -1882,7 +1852,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++) +@@ -2138,7 +2108,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); +@@ -3091,14 +3061,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 +@@ -698,8 +698,6 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, + goto alloc_fail; + } + +- xa_init(&h->writeback_inhibited_ebs); +- + /* + * If we are JOIN_NOLOCK we're already committing a transaction and + * waiting on this guy, so we don't need to do the sb_start_intwrite +@@ -1519,12 +1517,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), +@@ -1642,7 +1636,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; + } + +@@ -2588,7 +2582,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)); + mutex_unlock(&fs_info->tree_log_mutex); + goto scrub_continue; + } +@@ -2749,8 +2743,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/transaction.h b/fs/btrfs/transaction.h +--- a/fs/btrfs/transaction.h ++++ b/fs/btrfs/transaction.h +@@ -7,12 +7,12 @@ + #define BTRFS_TRANSACTION_H + + #include ++#include + #include + #include + #include + #include + #include +-#include + #include "btrfs_inode.h" + #include "delayed-ref.h" + +@@ -23,6 +23,7 @@ struct btrfs_fs_info; + struct btrfs_root_item; + struct btrfs_root; + struct btrfs_path; ++struct extent_buffer; + + /* + * Signal that a direct IO write is in progress, to avoid deadlock for sync +@@ -136,6 +137,18 @@ enum { + + #define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH) + ++/* ++ * Number of extent buffers a transaction handle tracks for writeback ++ * inhibition. The CLOCK reference bits pack into a u32 so this must not exceed ++ * 32, and keeping it a power of two lets the compiler reduce the CLOCK hand ++ * modulo to a mask. ++ */ ++#define BTRFS_INHIBITED_EBS_SLOTS 8 ++ ++static_assert(BTRFS_INHIBITED_EBS_SLOTS <= 32); ++static_assert(BTRFS_INHIBITED_EBS_SLOTS != 0 && ++ (BTRFS_INHIBITED_EBS_SLOTS & (BTRFS_INHIBITED_EBS_SLOTS - 1)) == 0); ++ + struct btrfs_trans_handle { + u64 transid; + u64 bytes_reserved; +@@ -163,8 +176,14 @@ struct btrfs_trans_handle { + struct btrfs_fs_info *fs_info; + struct list_head new_bgs; + struct btrfs_block_rsv delayed_rsv; +- /* Extent buffers with writeback inhibited by this handle. */ +- struct xarray writeback_inhibited_ebs; ++ ++ /* Extent buffers this handle has inhibited writeback on. */ ++ struct extent_buffer *inhibited_ebs[BTRFS_INHIBITED_EBS_SLOTS]; ++ /* CLOCK reference bit per slot. */ ++ u32 inhibited_ebs_referenced; ++ u32 nr_inhibited_ebs; ++ /* CLOCK hand. */ ++ u32 inhibited_ebs_hand; + }; + + /* +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 +@@ -638,7 +638,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; + } + +@@ -720,14 +720,18 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + goto out; + + folio_lock(folio); +- /* If it's not uptodate after we have the lock, we got a read error. */ +- if (!folio_test_uptodate(folio)) { ++ /* Folio was truncated from mapping. */ ++ if (!folio->mapping) { + folio_unlock(folio); + folio_put(folio); +- return ERR_PTR(-EIO); ++ goto again; + } +- folio_unlock(folio); +- goto out; ++ /* Another reader may have filled the folio while we waited. */ ++ if (folio_test_uptodate(folio)) { ++ folio_unlock(folio); ++ goto out; ++ } ++ goto read_folio; + } + + folio = filemap_alloc_folio(mapping_gfp_constraint(inode->i_mapping, ~__GFP_FS), +@@ -744,6 +748,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + return ERR_PTR(ret); + } + ++read_folio: + /* + * Merkle item keys are indexed from byte 0 in the merkle tree. + * They have the form: +@@ -753,6 +758,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, + ret = read_key_bytes(BTRFS_I(inode), BTRFS_VERITY_MERKLE_ITEM_KEY, off, + folio_address(folio), PAGE_SIZE, folio); + if (ret < 0) { ++ folio_unlock(folio); + folio_put(folio); + return ERR_PTR(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; + + /* +@@ -4588,7 +4553,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; +@@ -6049,6 +6014,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) +@@ -6067,11 +6045,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++) { +@@ -6250,7 +6227,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); +@@ -7632,9 +7609,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 +@@ -7645,6 +7620,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/0300-btrfs.patch.sig b/pkgbuilds/linux-omarchy/0300-btrfs.patch.sig new file mode 100644 index 0000000..60dbc9e Binary files /dev/null and b/pkgbuilds/linux-omarchy/0300-btrfs.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch b/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch new file mode 100644 index 0000000..c5a4318 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch @@ -0,0 +1,386 @@ +diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c +--- a/fs/btrfs/dev-replace.c ++++ b/fs/btrfs/dev-replace.c +@@ -626,7 +626,7 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, + + ret = mark_block_group_to_copy(fs_info, src_device); + if (ret) +- return ret; ++ goto leave; + + down_write(&dev_replace->rwsem); + dev_replace->replace_task = current; +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -3436,6 +3436,9 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent) + */ + btrfs_remove_ordered_extent(ordered_extent); + ++ /* Cleanup any remaining biocs attached to the OE. */ ++ btrfs_cleanup_ordered_bioc_list(ordered_extent); ++ + /* once for us */ + btrfs_put_ordered_extent(ordered_extent); + /* once for the tree */ +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/raid-stripe-tree.c b/fs/btrfs/raid-stripe-tree.c +--- a/fs/btrfs/raid-stripe-tree.c ++++ b/fs/btrfs/raid-stripe-tree.c +@@ -310,8 +310,10 @@ static int update_raid_extent_item(struct btrfs_trans_handle *trans, + + ret = btrfs_search_slot(trans, trans->fs_info->stripe_root, key, path, + 0, 1); +- if (ret) +- return (ret == 1 ? ret : -EINVAL); ++ if (ret > 0) ++ ret = -ENOENT; ++ if (ret < 0) ++ return ret; + + leaf = path->nodes[0]; + slot = path->slots[0]; +@@ -337,7 +339,6 @@ int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, + stripe_extent = kzalloc(item_size, GFP_NOFS); + if (unlikely(!stripe_extent)) { + btrfs_abort_transaction(trans, -ENOMEM); +- btrfs_end_transaction(trans); + return -ENOMEM; + } + +@@ -374,7 +375,7 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + struct btrfs_ordered_extent *ordered_extent) + { + struct btrfs_io_context *bioc; +- int ret; ++ int ret = 0; + + if (!btrfs_fs_incompat(trans->fs_info, RAID_STRIPE_TREE)) + return 0; +@@ -382,17 +383,23 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + list_for_each_entry(bioc, &ordered_extent->bioc_list, rst_ordered_entry) { + ret = btrfs_insert_one_raid_extent(trans, bioc); + if (ret) +- return ret; ++ break; + } + +- while (!list_empty(&ordered_extent->bioc_list)) { +- bioc = list_first_entry(&ordered_extent->bioc_list, ++ btrfs_cleanup_ordered_bioc_list(ordered_extent); ++ return ret; ++} ++ ++void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered) ++{ ++ while (!list_empty(&ordered->bioc_list)) { ++ struct btrfs_io_context *bioc; ++ ++ bioc = list_first_entry(&ordered->bioc_list, + typeof(*bioc), rst_ordered_entry); + list_del(&bioc->rst_ordered_entry); + btrfs_put_bioc(bioc); + } +- +- return 0; + } + + int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, +diff --git a/fs/btrfs/raid-stripe-tree.h b/fs/btrfs/raid-stripe-tree.h +--- a/fs/btrfs/raid-stripe-tree.h ++++ b/fs/btrfs/raid-stripe-tree.h +@@ -28,6 +28,7 @@ int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, + u32 stripe_index, struct btrfs_io_stripe *stripe); + int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, + struct btrfs_ordered_extent *ordered_extent); ++void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered); + + #ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS + int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, +diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c +--- a/fs/btrfs/scrub.c ++++ b/fs/btrfs/scrub.c +@@ -1023,6 +1023,10 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + + skip: + for_each_set_bit(sector_nr, &extent_bitmap, stripe->nr_sectors) { ++ const u64 sector_logical = stripe->logical + ++ ((u64)sector_nr << fs_info->sectorsize_bits); ++ const u64 sector_physical = physical + ++ ((u64)sector_nr << fs_info->sectorsize_bits); + bool repaired = false; + + if (scrub_bitmap_test_bit_is_metadata(stripe, sector_nr)) { +@@ -1051,12 +1055,12 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + if (dev) { + btrfs_err_rl(fs_info, + "scrub: fixed up error at logical %llu on dev %s physical %llu", +- stripe->logical, btrfs_dev_name(dev), +- physical); ++ sector_logical, btrfs_dev_name(dev), ++ sector_physical); + } else { + btrfs_err_rl(fs_info, + "scrub: fixed up error at logical %llu on mirror %u", +- stripe->logical, stripe->mirror_num); ++ sector_logical, stripe->mirror_num); + } + continue; + } +@@ -1065,30 +1069,30 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, + if (dev) { + btrfs_err_rl(fs_info, + "scrub: unable to fixup (regular) error at logical %llu on dev %s physical %llu", +- stripe->logical, btrfs_dev_name(dev), +- physical); ++ sector_logical, btrfs_dev_name(dev), ++ sector_physical); + } else { + btrfs_err_rl(fs_info, + "scrub: unable to fixup (regular) error at logical %llu on mirror %u", +- stripe->logical, stripe->mirror_num); ++ sector_logical, stripe->mirror_num); + } + + if (scrub_bitmap_test_bit_io_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("i/o error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_csum_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("checksum error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_meta_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("header error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + if (scrub_bitmap_test_bit_meta_gen_error(stripe, sector_nr)) + if (__ratelimit(&rs) && dev) + scrub_print_common_warning("generation error", dev, false, +- stripe->logical, physical); ++ sector_logical, sector_physical); + } + + /* Update the device stats. */ +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -2065,7 +2065,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen, + + ret = is_inode_existent(sctx, dir, dir_gen, NULL, &parent_root_dir_gen); + if (ret <= 0) +- return 0; ++ return ret; + + /* + * If we have a parent root we need to verify that the parent dir was +@@ -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/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c +--- a/fs/btrfs/tests/extent-io-tests.c ++++ b/fs/btrfs/tests/extent-io-tests.c +@@ -133,14 +133,14 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) + if (IS_ERR(root)) { + test_std_err(TEST_ALLOC_ROOT); + ret = PTR_ERR(root); +- goto out; ++ goto out_root_info; + } + + inode = btrfs_new_test_inode(); + if (!inode) { + test_std_err(TEST_ALLOC_INODE); + ret = -ENOMEM; +- goto out; ++ goto out_root_info; + } + tmp = &BTRFS_I(inode)->io_tree; + BTRFS_I(inode)->root = root; +@@ -333,6 +333,7 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) + process_page_range(inode, 0, total_dirty - 1, + PROCESS_UNLOCK | PROCESS_RELEASE); + iput(inode); ++out_root_info: + btrfs_free_dummy_root(root); + btrfs_free_dummy_fs_info(fs_info); + return ret; +diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c +--- a/fs/btrfs/transaction.c ++++ b/fs/btrfs/transaction.c +@@ -458,8 +458,19 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, + * through btrfs_record_root_in_trans without having to take the + * lock. smp_wmb() makes sure that all the writes above are + * done before we pop in the zero below ++ * ++ * If @force is true, it means the call is from ++ * qgroup_account_snapshot(), which only requires radix tree ++ * tracking. ++ * We should not force reloc root creation here, as the root ++ * may have already been modified, and in that case ++ * root->commit_root has already been dropped. ++ * ++ * Using that commit root will cause the reloc root to refer ++ * to a deleted extent, causing extent tree corruption. + */ +- ret = btrfs_init_reloc_root(trans, root); ++ if (!force) ++ ret = btrfs_init_reloc_root(trans, root); + smp_mb__before_atomic(); + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state); + } +@@ -2583,6 +2594,12 @@ 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: %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 ++ * for this failed transaction. See btrfs_sync_log(). ++ */ ++ btrfs_abort_transaction(trans, ret); + mutex_unlock(&fs_info->tree_log_mutex); + goto scrub_continue; + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -3035,7 +3035,11 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path + error_sysfs: + btrfs_sysfs_remove_device(device); + mutex_lock(&fs_info->fs_devices->device_list_mutex); ++ if (seeding_dev) ++ btrfs_assign_next_active_device(device, seed_devices->latest_dev); + mutex_lock(&fs_info->chunk_mutex); ++ if (!list_empty(&device->post_commit_list)) ++ list_del_init(&device->post_commit_list); + list_del_rcu(&device->dev_list); + list_del(&device->dev_alloc_list); + fs_info->fs_devices->num_devices--; +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2626,16 +2626,13 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ + down_read(&dev_replace->rwsem); + map = block_group->physical_map; + for (i = 0; i < map->num_stripes; i++) { +- + ret = call_zone_finish(block_group, &map->stripes[i]); +- if (ret) { +- up_read(&dev_replace->rwsem); +- return ret; +- } ++ if (ret) ++ break; + } + up_read(&dev_replace->rwsem); + +- if (!fully_written) ++ if (!ret && !fully_written) + btrfs_dec_block_group_ro(block_group); + + spin_lock(&fs_info->zone_active_bgs_lock); +@@ -2648,7 +2645,7 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ + + clear_and_wake_up_bit(BTRFS_FS_NEED_ZONE_FINISH, &fs_info->flags); + +- return 0; ++ return ret; + } + + int btrfs_zone_finish(struct btrfs_block_group *block_group) +@@ -2713,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; +@@ -2732,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/fs/btrfs/zstd.c b/fs/btrfs/zstd.c +--- a/fs/btrfs/zstd.c ++++ b/fs/btrfs/zstd.c +@@ -307,8 +307,17 @@ struct list_head *zstd_get_workspace(struct btrfs_fs_info *fs_info, int level) + DEFINE_WAIT(wait); + + prepare_to_wait(&zwsm->wait, &wait, TASK_UNINTERRUPTIBLE); +- schedule(); ++ /* ++ * Re-check after being queued: zstd_put_workspace() only wakes ++ * a queue that already has a sleeper, so a workspace returned ++ * since the failed allocation woke nobody. ++ */ ++ ws = zstd_find_workspace(fs_info, level); ++ if (!ws) ++ schedule(); + finish_wait(&zwsm->wait, &wait); ++ if (ws) ++ return ws; + + goto again; + } diff --git a/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch.sig b/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch.sig new file mode 100644 index 0000000..53ac4a1 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch b/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch new file mode 100644 index 0000000..d949f95 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0302-btrfs-zstd-decompress-direct-to-page.patch.sig b/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch.sig new file mode 100644 index 0000000..a75cf51 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch b/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch new file mode 100644 index 0000000..b34a909 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch @@ -0,0 +1,42 @@ +diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c +--- a/fs/fuse/dir.c ++++ b/fs/fuse/dir.c +@@ -2280,6 +2280,9 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + */ + if ((is_truncate || !is_wb) && + S_ISREG(inode->i_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); + +@@ -2908,6 +2912,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/0310-fuse-eof-zeroing.patch.sig b/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch.sig new file mode 100644 index 0000000..c65855e Binary files /dev/null and b/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0311-fuse-perf.patch b/pkgbuilds/linux-omarchy/0311-fuse-perf.patch new file mode 100644 index 0000000..5162077 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0311-fuse-perf.patch @@ -0,0 +1,104 @@ +diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c +--- a/fs/fuse/dev.c ++++ b/fs/fuse/dev.c +@@ -216,10 +216,13 @@ EXPORT_SYMBOL_GPL(fuse_req_hash); + /* + * A new request is available, wake fiq->waitq + */ +-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); + } +@@ -236,7 +239,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); +@@ -258,7 +261,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); +@@ -288,11 +291,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; +@@ -755,6 +760,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); +@@ -1809,7 +1819,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 +@@ -1417,6 +1417,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/0311-fuse-perf.patch.sig b/pkgbuilds/linux-omarchy/0311-fuse-perf.patch.sig new file mode 100644 index 0000000..ce13d31 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0311-fuse-perf.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch b/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch new file mode 100644 index 0000000..8aaf4c6 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0312-fuse-writethrough-uptodate.patch.sig b/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch.sig new file mode 100644 index 0000000..391ede6 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch b/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch new file mode 100644 index 0000000..77e750a --- /dev/null +++ b/pkgbuilds/linux-omarchy/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 +@@ -409,7 +409,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/0313-fuse-background-wakeup.patch.sig b/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch.sig new file mode 100644 index 0000000..e161277 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch b/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch new file mode 100644 index 0000000..9a8f97f --- /dev/null +++ b/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch @@ -0,0 +1,123 @@ +diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h +index 4590d6a3d821..dbdc2053332d 100644 +--- a/drivers/gpu/drm/drm_displayid_internal.h ++++ b/drivers/gpu/drm/drm_displayid_internal.h +@@ -67,6 +67,7 @@ struct drm_edid; + #define DATA_BLOCK_2_TILED_DISPLAY_TOPOLOGY 0x28 + #define DATA_BLOCK_2_CONTAINER_ID 0x29 + #define DATA_BLOCK_2_TYPE_10_FORMULA_TIMING 0x2a ++#define DATA_BLOCK_2_ADAPTIVE_SYNC 0x2b + #define DATA_BLOCK_2_VENDOR_SPECIFIC 0x7e + #define DATA_BLOCK_2_CTA_DISPLAY_ID 0x81 + +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +index df3c25bac761..354d4ff1aff7 100644 +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -6598,6 +6598,49 @@ static void drm_get_monitor_range(struct drm_connector *connector, + info->monitor_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/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig b/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig new file mode 100644 index 0000000..f46a1a4 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch b/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch new file mode 100644 index 0000000..278fc0c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch @@ -0,0 +1,601 @@ +diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c +--- a/drivers/gpu/drm/nouveau/nouveau_ttm.c ++++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c +@@ -188,6 +188,11 @@ nouveau_ttm_init_vram(struct nouveau_drm *drm) + + man->func = &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/0360-gpu-mem-cgroup.patch.sig b/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch.sig new file mode 100644 index 0000000..ddced6d Binary files /dev/null and b/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch b/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch new file mode 100644 index 0000000..32f3010 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig b/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig new file mode 100644 index 0000000..b591f47 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch b/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch new file mode 100644 index 0000000..28a6330 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch @@ -0,0 +1,196 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +index c21e0c0ef0b1..e62788328bfd 100644 +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -1780,6 +1780,7 @@ struct intel_psr { + u32 dc3co_exitline; + u32 dc3co_exit_delay; + struct delayed_work dc3co_work; ++ struct delayed_work panel_replay_reenable_work; + u8 entry_setup_frames; + + u8 io_wake_lines; +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +index beaa1d62613d..644ab9b1bee9 100644 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2475,6 +2475,7 @@ void intel_psr_disable(struct intel_dp *intel_dp, + mutex_unlock(&intel_dp->psr.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); + } + + /** +@@ -2506,6 +2507,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); + } + + /** +@@ -3539,6 +3541,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_alpm_cursor_lag_workaround_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_ALPM_CURSOR_LAG); ++} ++ + static void intel_psr_work(struct work_struct *work) + { + struct intel_dp *intel_dp = +@@ -3557,6 +3570,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 workaround is armed. */ ++ if (panel_replay_alpm_cursor_lag_workaround_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. +@@ -3566,6 +3584,10 @@ static void intel_psr_work(struct work_struct *work) + if (!__psr_wait_for_idle_locked(intel_dp)) + goto unlock; + ++ if (panel_replay_alpm_cursor_lag_workaround_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 +@@ -3579,6 +3601,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_alpm_cursor_lag_workaround_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); +@@ -3650,8 +3699,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_alpm_cursor_lag_workaround_enabled(intel_dp)) ++ cancel_delayed_work(&intel_dp->psr.panel_replay_reenable_work); + _psr_invalidate_handle(intel_dp); ++ } + + mutex_unlock(&intel_dp->psr.lock); + } +@@ -3767,6 +3819,15 @@ void intel_psr_flush(struct intel_display *display, + if (intel_dp->psr.pause_counter) + goto unlock; + ++ if (pipe_frontbuffer_bits && ++ panel_replay_alpm_cursor_lag_workaround_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)) { +@@ -3830,6 +3891,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 +index 33245f44c0d5..dc6576016400 100644 +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -86,12 +86,13 @@ static void quirk_edp_limit_rate_hbr2(struct intel_display *display) + drm_info(display->drm, "Applying eDP Limit rate to HBR2 quirk\n"); + } + +-static void quirk_disable_edp_panel_replay(struct intel_dp *intel_dp) ++static void quirk_panel_replay_alpm_cursor_lag(struct intel_dp *intel_dp) + { + struct intel_display *display = to_intel_display(intel_dp); + +- intel_set_dpcd_quirk(intel_dp, QUIRK_DISABLE_EDP_PANEL_REPLAY); +- drm_info(display->drm, "Applying disable Panel Replay quirk\n"); ++ intel_set_dpcd_quirk(intel_dp, QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG); ++ drm_info(display->drm, ++ "Applying Panel Replay ALPM cursor lag workaround\n"); + } + + static void quirk_disable_psr2(struct intel_display *display) +@@ -276,7 +277,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_alpm_cursor_lag, + }, + /* Dell XPS 16 DA16260 */ + { +@@ -284,7 +285,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_alpm_cursor_lag, + }, + }; + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h b/drivers/gpu/drm/i915/display/intel_quirks.h +index 970a4fe52faf..50ef333b87a9 100644 +--- 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_ALPM_CURSOR_LAG, + }; + + void intel_init_quirks(struct intel_display *display); diff --git a/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch.sig b/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch.sig new file mode 100644 index 0000000..4c3c1a0 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch b/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch new file mode 100644 index 0000000..3e88cea --- /dev/null +++ b/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch @@ -0,0 +1,20 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +index beaa1d626..5fdf4594c 100644 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -714,8 +714,14 @@ static void _psr_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *co + * To support PSR version 02h and PSR version 03h without + * Y-coordinate requirement panels we would need to enable + * GTC first. ++ * ++ * Early Transport (version 04h) implies Y-coordinate ++ * support. Accept it without explicit Y-coordinate ++ * requirement bit. + */ +- connector->dp.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/0402-psr2-early-transport-panels.patch.sig b/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch.sig new file mode 100644 index 0000000..1b2c039 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch b/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch new file mode 100644 index 0000000..c1f3ad5 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0411-drm-xe-display-no-stolen-framebuffers.patch.sig b/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch.sig new file mode 100644 index 0000000..5bc668b Binary files /dev/null and b/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0420-safe-window.patch b/pkgbuilds/linux-omarchy/0420-safe-window.patch new file mode 100644 index 0000000..21017c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0420-safe-window.patch @@ -0,0 +1,23 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c +--- a/drivers/gpu/drm/i915/display/intel_display.c ++++ b/drivers/gpu/drm/i915/display/intel_display.c +@@ -2452,6 +2452,19 @@ static int intel_crtc_set_context_latency(struct intel_crtc_state *crtc_state) + set_context_latency = max(set_context_latency, + intel_psr_min_set_context_latency(crtc_state)); + ++ /* ++ * From PTL onwards, the set context latency can be in the vactive ++ * region, letting the safe window start some lines before the vblank ++ * start. With modes that have a smaller vblank region, the computed ++ * guardband is clamped to the vblank length, making the undelayed and ++ * delayed vblank coincide. If the SCL is also 0, the 'safe window' ++ * becomes effectively 0, and the DSB configured to wait for it gets ++ * stalled, since the hardware never signals the safe window. Keep the ++ * set context latency at a minimum of 1 to avoid this. ++ */ ++ if (DISPLAY_VER(display) >= 30) ++ set_context_latency = max(1, set_context_latency); ++ + return set_context_latency; + } + diff --git a/pkgbuilds/linux-omarchy/0420-safe-window.patch.sig b/pkgbuilds/linux-omarchy/0420-safe-window.patch.sig new file mode 100644 index 0000000..4564638 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0420-safe-window.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0430-fbc.patch b/pkgbuilds/linux-omarchy/0430-fbc.patch new file mode 100644 index 0000000..4548866 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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 +@@ -1937,10 +1937,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); +@@ -1948,10 +1948,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/0430-fbc.patch.sig b/pkgbuilds/linux-omarchy/0430-fbc.patch.sig new file mode 100644 index 0000000..f0db1f3 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0430-fbc.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch b/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch new file mode 100644 index 0000000..a13b165 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0440-xe3-peak-bandwidth.patch.sig b/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch.sig new file mode 100644 index 0000000..711523e Binary files /dev/null and b/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch b/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch new file mode 100644 index 0000000..ebe9a36 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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" + +@@ -10068,6 +10069,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, +@@ -10079,6 +10083,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, +@@ -10645,9 +10679,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; ++ } + } + } + +@@ -12049,6 +12086,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( +@@ -14011,6 +14056,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 */ +@@ -14036,7 +14090,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; +@@ -14052,6 +14108,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) +@@ -14093,6 +14202,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/0450-amd-hdmi-vrr-allm.patch.sig b/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch.sig new file mode 100644 index 0000000..ee35537 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch b/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch new file mode 100644 index 0000000..f61c400 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch @@ -0,0 +1,15 @@ +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 +@@ -10069,7 +10069,10 @@ static void update_freesync_state_on_stream( + &vrr_infopacket, + pack_sdp_v1_3); + +- if (new_stream->sink->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/0451-amd-vtem-tmds-links.patch.sig b/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch.sig new file mode 100644 index 0000000..7077a0e Binary files /dev/null and b/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch b/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch new file mode 100644 index 0000000..e392ccc --- /dev/null +++ b/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +index 1aed121f4ddb..081de37c2083 100644 +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -216,8 +216,9 @@ int amdgpu_smu_pptable_id = -1; + * DISABLE_FRACTIONAL_PWM (bit 2) disabled by default + * PSR (bit 3) disabled by default + * EDP NO POWER SEQUENCING (bit 4) disabled by default ++ * FRL (bit 10) enabled by default + */ +-uint amdgpu_dc_feature_mask = 2; ++uint amdgpu_dc_feature_mask = DC_MULTI_MON_PP_MCLK_SWITCH_MASK | DC_FRL_MASK; + uint amdgpu_dc_debug_mask; + uint amdgpu_dc_visual_confirm; + int amdgpu_async_gfx_ring = 1; +diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h +index 3fd38323a88b..bdd80c5e378e 100644 +--- a/drivers/gpu/drm/amd/include/amd_shared.h ++++ b/drivers/gpu/drm/amd/include/amd_shared.h +@@ -287,7 +287,7 @@ enum DC_FEATURE_MASK { + */ + DC_REPLAY_MASK = (1 << 9), + /** +- * @DC_FRL_MASK: (0x400) disabled by default ++ * @DC_FRL_MASK: (0x400) enabled by default + */ + DC_FRL_MASK = (1 << 10), + }; diff --git a/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch.sig b/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch.sig new file mode 100644 index 0000000..c507d82 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch b/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch new file mode 100644 index 0000000..1ba6ec7 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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 +@@ -7096,6 +7096,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, +@@ -7616,6 +7621,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; +@@ -7707,6 +7713,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/0460-vesa-displayid-dsc-bpp.patch.sig b/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch.sig new file mode 100644 index 0000000..f76e652 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch b/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch new file mode 100644 index 0000000..e27b493 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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 +@@ -7622,6 +7622,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; +@@ -7688,6 +7689,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 +@@ -7713,16 +7730,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/0461-vesa-dsc-passthru-mode-match-fix.patch.sig b/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch.sig new file mode 100644 index 0000000..d303e52 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch b/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch new file mode 100644 index 0000000..942772c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch @@ -0,0 +1,26 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +@@ -1426,7 +1426,7 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) + struct amdgpu_usermode_queue *queue; + const struct amdgpu_userq_funcs *userq_funcs; + unsigned long queue_id; +- int r = 0; ++ int ret = 0, r; + + xa_for_each(&adev->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/0472-amdgpu-userq-post-reset-error.patch.sig b/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch.sig new file mode 100644 index 0000000..5de4d85 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch b/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch new file mode 100644 index 0000000..08d39ec --- /dev/null +++ b/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c +--- a/drivers/gpu/drm/i915/display/intel_cdclk.c ++++ b/drivers/gpu/drm/i915/display/intel_cdclk.c +@@ -2366,8 +2366,10 @@ static void bxt_sanitize_cdclk(struct intel_display *display) + * dividers both syncing to an active pipe, or asynchronously + * (PIPE_NONE). + */ +- cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); +- cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ if (DISPLAY_VER(display) < 30) { ++ cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); ++ } + + if (cdctl != expected) { + if (DISPLAY_VER(display) < 20) { diff --git a/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch.sig b/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch.sig new file mode 100644 index 0000000..4e8c059 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch b/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch new file mode 100644 index 0000000..590d8a0 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch @@ -0,0 +1,18 @@ +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 +@@ -4160,8 +4160,12 @@ static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) + else if (!IS_ERR_OR_NULL(panel_backlight_quirk) && + panel_backlight_quirk->force_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/0474-amd-display-oled-vesa-backlight.patch.sig b/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch.sig new file mode 100644 index 0000000..14aa526 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch b/pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch new file mode 100644 index 0000000..a58a962 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig b/pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig new file mode 100644 index 0000000..3ddfad0 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0510-sound-updates.patch b/pkgbuilds/linux-omarchy/0510-sound-updates.patch new file mode 100644 index 0000000..29d72a3 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0510-sound-updates.patch @@ -0,0 +1,50784 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -1752,6 +1752,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/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/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/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/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/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/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/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_fdl.h b/include/sound/sdca_fdl.h +--- a/include/sound/sdca_fdl.h ++++ b/include/sound/sdca_fdl.h +@@ -83,6 +83,10 @@ static inline int sdca_fdl_alloc_state(struct sdca_interrupt *interrupt) + return 0; + } + ++static inline void sdca_fdl_free_state(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_fdl_process(struct sdca_interrupt *interrupt) + { + return 0; +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/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/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/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 +@@ -44,6 +44,18 @@ + + struct asoc_sdw_codec_info; + ++struct asoc_sdw_mc_private { ++ struct snd_soc_card card; ++ struct snd_soc_jack sdw_headset; ++ struct device *headset_codec_dev; /* only one headset per card */ ++ struct device *amp_dev1, *amp_dev2; ++ bool append_dai_type; ++ bool ignore_internal_dmic; ++ void *private; ++ unsigned long mc_quirk; ++ int codec_info_list_count; ++}; ++ + struct asoc_sdw_dai_info { + const bool direction[2]; /* playback & capture support */ + const char *codec_name; +@@ -88,25 +100,13 @@ struct asoc_sdw_codec_info { + + int (*codec_card_late_probe)(struct snd_soc_card *card); + +- int (*count_sidecar)(struct snd_soc_card *card, ++ int (*count_sidecar)(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs); + int (*add_sidecar)(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, + struct snd_soc_codec_conf **codec_conf); + }; + +-struct asoc_sdw_mc_private { +- struct snd_soc_card card; +- struct snd_soc_jack sdw_headset; +- struct device *headset_codec_dev; /* only one headset per card */ +- struct device *amp_dev1, *amp_dev2; +- bool append_dai_type; +- bool ignore_internal_dmic; +- void *private; +- unsigned long mc_quirk; +- int codec_info_list_count; +-}; +- + struct asoc_sdw_endpoint { + struct list_head list; + +@@ -182,7 +182,8 @@ struct asoc_sdw_dailink *asoc_sdw_find_dailink(struct asoc_sdw_dailink *dailinks + const struct snd_soc_acpi_endpoint *new); + int asoc_sdw_get_dai_type(u32 type); + +-int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, ++int asoc_sdw_parse_sdw_endpoints(struct device *dev, ++ struct asoc_sdw_mc_private *ctx, + struct snd_soc_aux_dev *soc_aux, + struct asoc_sdw_dailink *soc_dais, + struct asoc_sdw_endpoint *soc_ends, +@@ -235,7 +236,7 @@ int asoc_sdw_es9356_amp_init(struct snd_soc_card *card, + int asoc_sdw_es9356_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); + + /* CS AMP support */ +-int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card, ++int asoc_sdw_bridge_cs35l56_count_sidecar(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs); + int asoc_sdw_bridge_cs35l56_add_sidecar(struct snd_soc_card *card, + struct snd_soc_dai_link **dai_links, +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/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/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/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 + }; + + /** +@@ -584,12 +583,17 @@ EXPORT_SYMBOL_GPL(snd_card_disconnect_sync); + + static int snd_card_do_free(struct snd_card *card) + { ++ bool managed = card->managed; ++ + card->releasing = true; + #if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + 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 +@@ -601,7 +605,7 @@ static int snd_card_do_free(struct snd_card *card) + } + if (card->release_completion) + complete(card->release_completion); +- if (!card->managed) ++ if (!managed) + kfree(card); + return 0; + } +@@ -722,7 +726,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; + +@@ -1031,21 +1035,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) +@@ -4241,29 +4245,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. +@@ -528,7 +540,7 @@ static int bounce_error_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop) + { +- struct snd_seq_event bounce_ev; ++ struct snd_seq_event bounce_ev, quoted; + int result; + + if (client == NULL || +@@ -548,15 +560,19 @@ static int bounce_error_event(struct snd_seq_client *client, + * For user clients, send SNDRV_SEQ_EVENT_BOUNCE with the + * original event embedded as variable-length data. This + * avoids exposing data.quote.event (a kernel pointer) to +- * userspace. The variable-length path in snd_seq_event_dup() +- * copies the event data from data.ext.ptr into chained cells, +- * and snd_seq_expand_var_event() copies only the data content +- * -- never the pointer -- to userspace. ++ * userspace. Sanitise the embedded copy too - a queued ++ * variable-length event carries the address of its own ++ * extension cell, and the payload goes out verbatim. + */ ++ quoted = *event; ++ if (snd_seq_ev_is_variable("ed)) { ++ quoted.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; ++ quoted.data.ext.ptr = NULL; ++ } + bounce_ev.type = SNDRV_SEQ_EVENT_BOUNCE; + bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + bounce_ev.data.ext.len = sizeof(struct snd_seq_event); +- bounce_ev.data.ext.ptr = (char *)event; ++ bounce_ev.data.ext.ptr = (char *)"ed; + } else { + /* + * For kernel clients, quote the event pointer directly. +@@ -720,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; +@@ -744,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); +@@ -1941,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) = +@@ -1968,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; +@@ -2532,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", +@@ -2666,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_midi.c b/sound/core/seq/seq_midi.c +--- a/sound/core/seq/seq_midi.c ++++ b/sound/core/seq/seq_midi.c +@@ -42,9 +42,11 @@ struct seq_midisynth { + struct snd_rawmidi *rmidi; + int device; + int subdevice; ++ struct snd_rawmidi_substream __rcu *input_substream; ++ snd_use_lock_t input_use_lock; /* in-flight event_input users */ + struct snd_rawmidi_file input_rfile; +- spinlock_t output_lock; /* protects output_rfile publication */ + snd_use_lock_t output_use_lock; /* in-flight event_input users */ ++ struct snd_rawmidi_substream __rcu *output_substream; + struct snd_rawmidi_file output_rfile; + int seq_client; + int seq_port; +@@ -76,6 +78,14 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) + msynth = runtime->private_data; + if (msynth == NULL) + return; ++ ++ scoped_guard(rcu) { ++ if (rcu_dereference(msynth->input_substream) != substream) ++ return; ++ ++ snd_use_lock_use(&msynth->input_use_lock); ++ } ++ + memset(&ev, 0, sizeof(ev)); + while (runtime->avail > 0) { + res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); +@@ -95,6 +105,8 @@ static void snd_midi_input_event(struct snd_rawmidi_substream *substream) + memset(&ev, 0, sizeof(ev)); + } + } ++ ++ snd_use_lock_free(&msynth->input_use_lock); + } + + static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count) +@@ -134,8 +146,8 @@ static int event_process_midi(struct snd_seq_event *ev, int direct, + if (snd_BUG_ON(!msynth)) + return -EINVAL; + +- scoped_guard(spinlock_irqsave, &msynth->output_lock) { +- substream = msynth->output_rfile.output; ++ scoped_guard(rcu) { ++ substream = rcu_dereference(msynth->output_substream); + if (!substream) + return -ENODEV; + snd_use_lock_use(&msynth->output_use_lock); +@@ -177,7 +189,7 @@ static int snd_seq_midisynth_new(struct seq_midisynth *msynth, + msynth->card = card; + msynth->device = device; + msynth->subdevice = subdevice; +- spin_lock_init(&msynth->output_lock); ++ snd_use_lock_init(&msynth->input_use_lock); + snd_use_lock_init(&msynth->output_use_lock); + return 0; + } +@@ -188,28 +200,31 @@ static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_runtime *runtime; ++ struct snd_rawmidi_file rfile = {}; + struct snd_rawmidi_params params; + + /* open midi port */ + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, + SNDRV_RAWMIDI_LFLG_INPUT, +- &msynth->input_rfile); ++ &rfile); + if (err < 0) { + pr_debug("ALSA: seq_midi: midi input open failed!!!\n"); + return err; + } +- runtime = msynth->input_rfile.input->runtime; ++ runtime = rfile.input->runtime; + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = input_buffer_size; +- err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms); ++ err = snd_rawmidi_input_params(rfile.input, ¶ms); + if (err < 0) { +- snd_rawmidi_kernel_release(&msynth->input_rfile); ++ snd_rawmidi_kernel_release(&rfile); + return err; + } + snd_midi_event_reset_encode(msynth->parser); + runtime->event = snd_midi_input_event; + runtime->private_data = msynth; ++ msynth->input_rfile = rfile; ++ rcu_assign_pointer(msynth->input_substream, rfile.input); + snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); + return 0; + } +@@ -219,10 +234,19 @@ static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscri + { + int err; + struct seq_midisynth *msynth = private_data; ++ struct snd_rawmidi_file rfile; + +- if (snd_BUG_ON(!msynth->input_rfile.input)) ++ rcu_assign_pointer(msynth->input_substream, NULL); ++ synchronize_rcu(); ++ snd_use_lock_sync(&msynth->input_use_lock); ++ ++ rfile = msynth->input_rfile; ++ msynth->input_rfile = (struct snd_rawmidi_file){}; ++ ++ if (snd_BUG_ON(!rfile.input)) + return -EINVAL; +- err = snd_rawmidi_kernel_release(&msynth->input_rfile); ++ ++ err = snd_rawmidi_kernel_release(&rfile); + return err; + } + +@@ -252,8 +276,8 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info + return err; + } + snd_midi_event_reset_decode(msynth->parser); +- scoped_guard(spinlock_irqsave, &msynth->output_lock) +- msynth->output_rfile = rfile; ++ msynth->output_rfile = rfile; ++ rcu_assign_pointer(msynth->output_substream, rfile.output); + return 0; + } + +@@ -261,17 +285,16 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info + static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info) + { + struct seq_midisynth *msynth = private_data; +- struct snd_rawmidi_file rfile = {}; ++ struct snd_rawmidi_file rfile; + +- scoped_guard(spinlock_irqsave, &msynth->output_lock) { +- rfile = msynth->output_rfile; +- msynth->output_rfile = (struct snd_rawmidi_file){}; +- } ++ rcu_assign_pointer(msynth->output_substream, NULL); ++ synchronize_rcu(); ++ snd_use_lock_sync(&msynth->output_use_lock); ++ rfile = msynth->output_rfile; ++ msynth->output_rfile = (struct snd_rawmidi_file){}; + + if (snd_BUG_ON(!rfile.output)) + return -EINVAL; +- +- snd_use_lock_sync(&msynth->output_use_lock); + snd_rawmidi_drain_output(rfile.output); + return snd_rawmidi_kernel_release(&rfile); + } +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), +@@ -3524,6 +3579,16 @@ static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, + } + } + ++static void alc_fixup_yoga_pro7_audio(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* Reuse the DAC routing selected for ThinkPad X1 Gen7 */ ++ alc285_fixup_thinkpad_x1_gen7(codec, fix, action); ++ ++ /* Limit the internal mic boost to 0 or 1 to avoid noise */ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++} ++ + static void alc295_fixup_dell_inspiron_top_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3929,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, +@@ -3997,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, +@@ -4066,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, +@@ -4091,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, +@@ -4108,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, +@@ -4139,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, +@@ -4183,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, +@@ -4197,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, +@@ -4206,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, +@@ -4226,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; +@@ -4263,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[]) { +@@ -4703,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, +@@ -5078,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, +@@ -5143,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 + }, +@@ -5662,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, +@@ -6209,11 +6294,20 @@ static const struct hda_fixup alc269_fixups[] = { + }, + [ALC287_FIXUP_LENOVO_YOGA_PRO7] = { + .type = HDA_FIXUP_FUNC, +- /* Reuse the DAC routing selected for ThinkPad X1 Gen7 */ +- .v.func = alc285_fixup_thinkpad_x1_gen7, ++ .v.func = alc_fixup_yoga_pro7_audio, + .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, +@@ -6653,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, +@@ -6729,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[]) { +@@ -6749,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, +@@ -6776,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, +@@ -6898,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[] = { +@@ -6951,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), +@@ -6961,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), +@@ -7145,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), +@@ -7259,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), +@@ -7350,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), +@@ -7502,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), +@@ -7509,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), +@@ -7516,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), +@@ -7574,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), +@@ -7585,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), +@@ -7614,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), +@@ -7625,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), +@@ -7837,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), +@@ -7853,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), +@@ -7928,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), +@@ -7963,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), +@@ -7980,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 + */ +@@ -7990,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), +@@ -7998,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), +@@ -8018,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), +@@ -8032,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), +@@ -8069,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), +@@ -8086,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), +@@ -8159,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), + {} + }; + +@@ -8298,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 \ +@@ -8903,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; +@@ -173,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; + } + +@@ -722,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; + } + +@@ -752,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. +@@ -785,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 +@@ -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; + } + +@@ -400,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); + } + } +@@ -848,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; +@@ -865,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; + } +@@ -883,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; + } +@@ -918,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/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; +@@ -2967,8 +2973,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) +@@ -3370,7 +3379,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/common/proc.c b/sound/hda/common/proc.c +--- a/sound/hda/common/proc.c ++++ b/sound/hda/common/proc.c +@@ -624,7 +624,7 @@ static void print_conn_list(struct snd_info_buffer *buffer, + /* Get Cache connections info */ + cache_len = snd_hda_get_conn_list(codec, nid, &list); + if (cache_len >= 0 && (cache_len != conn_len || +- memcmp(list, conn, conn_len) != 0)) { ++ memcmp(list, conn, conn_len * sizeof(*conn)) != 0)) { + snd_iprintf(buffer, " In-driver Connection: %d\n", cache_len); + if (cache_len > 0) { + snd_iprintf(buffer, " "); +diff --git a/sound/hda/controllers/Kconfig b/sound/hda/controllers/Kconfig +--- a/sound/hda/controllers/Kconfig ++++ b/sound/hda/controllers/Kconfig +@@ -33,6 +33,7 @@ config SND_HDA_TEGRA + config SND_HDA_CIX_IPBLOQ + tristate "CIX IPBLOQ HD Audio" + depends on ARCH_CIX || COMPILE_TEST ++ depends on ARCH_DMA_ADDR_T_64BIT + select SND_HDA + select SND_HDA_ALIGNED_MMIO + help +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 +@@ -663,6 +663,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x1af4, "QEMU" }, + { 0x1fa8, "Senarytech" }, + { 0x434d, "C-Media" }, ++ { 0x4c54, "Lisuan" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +@@ -765,7 +766,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/hpi6000.c b/sound/pci/asihpi/hpi6000.c +--- a/sound/pci/asihpi/hpi6000.c ++++ b/sound/pci/asihpi/hpi6000.c +@@ -537,6 +537,11 @@ static short create_adapter_obj(struct hpi_adapter_obj *pao, + hr1.size = sizeof(hr1); + + error = hpi6000_message_response_sequence(pao, 0, &hm, &hr0); ++ if (error) { ++ HPI_DEBUG_LOG(ERROR, "message transport error %d\n", ++ error); ++ return error; ++ } + if (hr0.error) { + HPI_DEBUG_LOG(DEBUG, "message error %d\n", hr0.error); + return hr0.error; +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/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c +--- a/sound/pci/via82xx_modem.c ++++ b/sound/pci/via82xx_modem.c +@@ -573,24 +573,18 @@ static inline unsigned int calc_linear_pos(struct via82xx_modem *chip, + viadev->bufsize2, viadev->idx_table[idx].offset, + viadev->idx_table[idx].size, count); + #endif +- if (count && size < count) { ++ if (! count) ++ /* bogus count 0 on the DMA boundary? */ ++ res = viadev->idx_table[idx].offset; ++ else ++ /* count register returns full size ++ * when end of buffer is reached ++ */ ++ res = viadev->idx_table[idx].offset + size; ++ if (check_invalid_pos(viadev, res)) { + dev_dbg(chip->card->dev, +- "invalid via82xx_cur_ptr, using last valid pointer\n"); ++ "invalid via82xx_cur_ptr (2), using last valid pointer\n"); + res = viadev->lastpos; +- } else { +- if (! count) +- /* bogus count 0 on the DMA boundary? */ +- res = viadev->idx_table[idx].offset; +- else +- /* count register returns full size +- * when end of buffer is reached +- */ +- res = viadev->idx_table[idx].offset + size; +- if (check_invalid_pos(viadev, res)) { +- dev_dbg(chip->card->dev, +- "invalid via82xx_cur_ptr (2), using last valid pointer\n"); +- res = viadev->lastpos; +- } + } + } + viadev->lastpos = res; /* remember the last position */ +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/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 +@@ -432,7 +432,7 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) + if (!soc_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(card, soc_aux, soc_dais, soc_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, soc_aux, soc_dais, soc_ends, &num_confs); + if (ret < 0) + return ret; + +@@ -521,15 +521,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; +@@ -546,12 +546,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); +@@ -560,9 +560,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; + } +@@ -580,8 +580,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 +@@ -303,7 +303,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_devs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + if (ret < 0) + return ret; + +@@ -388,12 +388,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 */ +@@ -412,15 +412,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; + } +@@ -438,8 +438,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; +@@ -210,16 +215,22 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, + SERDES_STATUS_EN | SERDES_STATUS_RST, + SERDES_STATUS_RST); + /* +- * Experiments suggest that it takes at most ~1 us +- * for the bit to clear, so wait 2 us for good measure. ++ * The SERDES cluster needs a bit of time to reset itself ++ * and settle before we start poking it. This is... slow... + */ +- udelay(2); ++ udelay(25); + WARN_ON(readl_relaxed(cl->base + serdes_unit + REG_SERDES_STATUS) & + SERDES_STATUS_RST); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + 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" +@@ -726,6 +728,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 +739,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 +@@ -889,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 +@@ -1201,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 +@@ -1888,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 +@@ -1978,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 +@@ -2297,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 +@@ -2771,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 +@@ -2894,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 +@@ -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 +@@ -284,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 +@@ -426,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 +@@ -496,6 +499,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 +@@ -724,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 +@@ -876,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,6 +14,7 @@ + * for ST-Ericsson. + */ + ++#include + #include + #include + #include +@@ -989,9 +990,8 @@ static int sid_status_control_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + +- mutex_lock(&drvdata->ctrl_lock); ++ guard(mutex)(&drvdata->ctrl_lock); + ucontrol->value.enumerated.item[0] = drvdata->sid_status; +- mutex_unlock(&drvdata->ctrl_lock); + + return 0; + } +@@ -1014,7 +1014,7 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + return -EIO; + } + +- mutex_lock(&drvdata->ctrl_lock); ++ guard(mutex)(&drvdata->ctrl_lock); + + sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); + if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { +@@ -1025,7 +1025,8 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + } else { + status = -EBUSY; + } +- goto out; ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ return status; + } + + snd_soc_component_write(component, AB8500_SIDFIRADR, 0); +@@ -1043,9 +1044,6 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, + + drvdata->sid_status = SID_FIR_CONFIGURED; + +-out: +- mutex_unlock(&drvdata->ctrl_lock); +- + dev_dbg(component->dev, "%s: Exit\n", __func__); + + return status; +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 +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -0,0 +1,1409 @@ ++// 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 __free(firmware) = 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) ++ return -ENOMEM; ++ ++ aw88399->aw_cfg->len = (int)cont->size; ++ memcpy(aw88399->aw_cfg->data, cont->data, cont->size); ++ ++ 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 +@@ -7,1220 +7,16 @@ + // Author: Weidong Wang + // + ++#include + #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", +@@ -1702,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; + } + +@@ -1715,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + aw88399_start(aw88399, AW88399_SYNC_START); + } + +- mutex_unlock(&aw88399->lock); +- + return 1; + } + +@@ -1869,86 +662,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, +@@ -1975,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); +@@ -1986,7 +699,6 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88399->lock); + + return 0; + } +@@ -2040,70 +752,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/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-i2c.c b/sound/soc/codecs/cs35l56-i2c.c +--- a/sound/soc/codecs/cs35l56-i2c.c ++++ b/sound/soc/codecs/cs35l56-i2c.c +@@ -51,15 +51,7 @@ static int cs35l56_i2c_probe(struct i2c_client *client) + return dev_err_probe(cs35l56->base.dev, ret, "Failed to allocate register map\n"); + } + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- ret = cs35l56_irq_request(&cs35l56->base, client->irq); +- if (ret < 0) +- cs35l56_remove(cs35l56); +- +- return ret; ++ return cs35l56_common_probe(cs35l56, client->irq); + } + + static void cs35l56_i2c_remove(struct i2c_client *client) +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,25 +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); + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- return 0; ++ 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 */ + }; +@@ -509,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-spi.c b/sound/soc/codecs/cs35l56-spi.c +--- a/sound/soc/codecs/cs35l56-spi.c ++++ b/sound/soc/codecs/cs35l56-spi.c +@@ -40,15 +40,7 @@ static int cs35l56_spi_probe(struct spi_device *spi) + if (ret) + return ret; + +- ret = cs35l56_common_probe(cs35l56); +- if (ret != 0) +- return ret; +- +- ret = cs35l56_irq_request(&cs35l56->base, spi->irq); +- if (ret < 0) +- cs35l56_remove(cs35l56); +- +- return ret; ++ return cs35l56_common_probe(cs35l56, spi->irq); + } + + static void cs35l56_spi_remove(struct spi_device *spi) +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 +@@ -37,48 +37,49 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + +-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); +@@ -828,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); +@@ -1402,6 +1399,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; +@@ -1941,7 +1939,7 @@ static int cs35l56_try_get_broken_sdca_spkid_gpio(struct cs35l56_private *cs35l5 + return ret; + } + +-int cs35l56_common_probe(struct cs35l56_private *cs35l56) ++int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) + { + int ret; + +@@ -2018,16 +2016,24 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56) + goto err_remove_wm_adsp; + } + ++ ret = cs35l56_irq_request(&cs35l56->base, irq); ++ if (ret) ++ goto err_remove_wm_adsp; ++ + ret = snd_soc_register_component(cs35l56->base.dev, + &soc_component_dev_cs35l56, + cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); + if (ret < 0) { + dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n"); +- goto err_remove_wm_adsp; ++ goto err_free_irq; + } + + return 0; + ++err_free_irq: ++ if (cs35l56->base.irq) ++ devm_free_irq(cs35l56->base.dev, cs35l56->base.irq, &cs35l56->base); ++ + err_remove_wm_adsp: + wm_adsp2_remove(&cs35l56->dsp); + +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 +@@ -39,8 +39,6 @@ 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; + struct completion init_completion; +@@ -65,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); +@@ -76,9 +72,8 @@ 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 cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); + int cs35l56_init(struct cs35l56_private *cs35l56); + void cs35l56_remove(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 +@@ -452,31 +453,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); +@@ -484,8 +484,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + + hcp->busy = true; + +-err: +- mutex_unlock(&hcp->lock); + return ret; + } + +@@ -503,9 +501,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; + } +@@ -458,10 +458,8 @@ static void rt700_sdw_remove(struct sdw_slave *slave) + { + struct rt700_priv *rt700 = dev_get_drvdata(&slave->dev); + +- if (rt700->hw_init) { +- cancel_delayed_work_sync(&rt700->jack_detect_work); +- cancel_delayed_work_sync(&rt700->jack_btn_check_work); +- } ++ cancel_delayed_work_sync(&rt700->jack_detect_work); ++ cancel_delayed_work_sync(&rt700->jack_btn_check_work); + + pm_runtime_disable(&slave->dev); + } +@@ -501,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 +@@ -916,10 +916,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 +@@ -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; + } +@@ -2243,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; +@@ -2253,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"); + +@@ -2399,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, +@@ -2407,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"); + +@@ -2797,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 +@@ -2818,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 = { +@@ -1098,7 +1147,13 @@ static s32 tas2783_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(tas_dev->regmap, false); +- regcache_sync(tas_dev->regmap); ++ ret = regcache_sync(tas_dev->regmap); ++ if (ret) { ++ regcache_cache_only(tas_dev->regmap, true); ++ regcache_mark_dirty(tas_dev->regmap); ++ return ret; ++ } ++ + return 0; + } + +@@ -1224,9 +1279,23 @@ static s32 tas_update_status(struct sdw_slave *slave, + if (tas_dev->hw_init || tas_dev->status != SDW_SLAVE_ATTACHED) + return 0; + +- /* updated the cache data to device */ + regcache_cache_only(tas_dev->regmap, false); +- regcache_sync(tas_dev->regmap); ++ ++ /* ++ * The device is attaching uninitialized: either this is the first ++ * attach, or it lost power (and with it all register and DSP state) ++ * while the controller was power-gated during system suspend. The ++ * cache still holds the pre-suspend values, and tas_io_init() below ++ * resets the device via TAS2783_SW_RESET anyway, so syncing it back ++ * is both useless and harmful: later read-modify-write updates would ++ * compare against stale data and skip the hardware write. ++ * ++ * Drop the cache instead, so that subsequent accesses see the real ++ * hardware state. Syncing after the reset is not an option either: ++ * the cache accepts registers for which tas2783_sdca_mbq_size() ++ * returns 0, and writing those back fails with -EINVAL. ++ */ ++ regcache_drop_region(tas_dev->regmap, 0, UINT_MAX); + + /* perform I/O transfers required for Slave initialization */ + return tas_io_init(&slave->dev, slave); +@@ -1329,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/tas675x.c b/sound/soc/codecs/tas675x.c +--- a/sound/soc/codecs/tas675x.c ++++ b/sound/soc/codecs/tas675x.c +@@ -1924,15 +1924,6 @@ static const struct reg_default tas675x_reg_defaults[] = { + { TAS675X_PWM_PHASE_M_CTRL_CH2_REG, 0x00 }, + { TAS675X_PWM_PHASE_M_CTRL_CH3_REG, 0x00 }, + { TAS675X_PWM_PHASE_M_CTRL_CH4_REG, 0x00 }, +- { TAS675X_DC_LDG_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_LO_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 }, +- { TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 }, +- { TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 }, +- { TAS675X_AC_LDG_CTRL_REG, 0x10 }, +- { TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 }, +- { TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 }, +- { TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 }, + { TAS675X_REPORT_ROUTING_1_REG, 0x00 }, + { TAS675X_OTSD_RECOVERY_EN_REG, 0x00 }, + { TAS675X_REPORT_ROUTING_2_REG, 0xA2 }, +@@ -1943,6 +1934,15 @@ static const struct reg_default tas675x_reg_defaults[] = { + { TAS675X_GPIO1_OUTPUT_SEL_REG, 0x00 }, + { TAS675X_GPIO2_OUTPUT_SEL_REG, 0x00 }, + { TAS675X_GPIO_CTRL_REG, TAS675X_GPIO_CTRL_RSTVAL }, ++ { TAS675X_DC_LDG_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_LO_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_TIME_CTRL_REG, 0x00 }, ++ { TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 }, ++ { TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 }, ++ { TAS675X_AC_LDG_CTRL_REG, 0x10 }, ++ { TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 }, ++ { TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 }, ++ { TAS675X_AC_LDG_FREQ_CTRL_REG, 0xC8 }, + { TAS675X_OTW_CTRL_CH1_CH2_REG, 0x11 }, + { TAS675X_OTW_CTRL_CH3_CH4_REG, 0x11 }, + }; +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) { +- dev_err(&pdev->dev, "failed to find CPU DAI device\n"); +- ret = -EINVAL; +- 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); + +@@ -1118,7 +1349,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", +@@ -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); + } + + /** +@@ -1358,7 +1352,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.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_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_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; +@@ -457,6 +454,9 @@ static const struct of_device_id fsl_audmix_ids[] = { + }; + MODULE_DEVICE_TABLE(of, fsl_audmix_ids); + ++static int fsl_audmix_runtime_resume(struct device *dev); ++static int fsl_audmix_runtime_suspend(struct device *dev); ++ + static int fsl_audmix_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +@@ -488,13 +488,25 @@ static int fsl_audmix_probe(struct platform_device *pdev) + spin_lock_init(&priv->lock); + platform_set_drvdata(pdev, priv); + pm_runtime_enable(dev); ++ if (!pm_runtime_enabled(dev)) { ++ ret = fsl_audmix_runtime_resume(dev); ++ if (ret) ++ goto err_disable_pm; ++ } ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err_pm_get_sync; ++ ++ /* To enable regmap cache only when runtime PM enabled */ ++ pm_runtime_put(dev); + + ret = devm_snd_soc_register_component(dev, &fsl_audmix_component, + fsl_audmix_dai, + ARRAY_SIZE(fsl_audmix_dai)); + if (ret) { + dev_err(dev, "failed to register ASoC DAI\n"); +- goto err_disable_pm; ++ goto err_pm_get_sync; + } + + /* +@@ -506,12 +518,15 @@ static int fsl_audmix_probe(struct platform_device *pdev) + if (IS_ERR(priv->pdev)) { + ret = PTR_ERR(priv->pdev); + dev_err(dev, "failed to register platform: %d\n", ret); +- goto err_disable_pm; ++ goto err_pm_get_sync; + } + } + + return 0; + ++err_pm_get_sync: ++ if (!pm_runtime_status_suspended(dev)) ++ fsl_audmix_runtime_suspend(dev); + err_disable_pm: + pm_runtime_disable(dev); + return ret; +@@ -522,6 +537,8 @@ static void fsl_audmix_remove(struct platform_device *pdev) + struct fsl_audmix *priv = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); ++ if (!pm_runtime_status_suspended(&pdev->dev)) ++ fsl_audmix_runtime_suspend(&pdev->dev); + + if (priv->pdev) + platform_device_unregister(priv->pdev); +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); + } + + /* +@@ -2227,7 +2219,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; +@@ -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_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/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-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/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,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -251,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; + } + +@@ -1305,7 +1303,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) +@@ -1314,8 +1312,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, +@@ -1334,13 +1330,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) { +@@ -1348,10 +1344,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; + } + +@@ -1496,15 +1488,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) +@@ -1560,7 +1550,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) +@@ -1572,8 +1562,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 +@@ -958,7 +958,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) +@@ -1387,25 +1387,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 +@@ -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); +@@ -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 +@@ -2222,7 +2222,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); +@@ -2235,7 +2235,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 +@@ -1288,7 +1288,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + goto err_dai; + } + +- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + if (ret < 0) + goto err_end; + +@@ -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/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-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/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); + +@@ -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/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 +@@ -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), +@@ -180,7 +178,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/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 +@@ -50,25 +50,20 @@ int meson_card_reallocate_links(struct snd_soc_card *card, + num_links * sizeof(*priv->card.dai_link), + GFP_KERNEL | __GFP_ZERO); + if (!links) +- goto err_links; ++ return -ENOMEM; ++ ++ priv->card.dai_link = links; ++ priv->card.num_links = num_links; + + ldata = krealloc(priv->link_data, + num_links * sizeof(*priv->link_data), + GFP_KERNEL | __GFP_ZERO); ++ /* meson_card_clean_references() will free the links on this error path */ + if (!ldata) +- goto err_ldata; ++ return -ENOMEM; + +- priv->card.dai_link = links; + priv->link_data = ldata; +- priv->card.num_links = num_links; + return 0; +- +-err_ldata: +- kfree(links); +-err_links: +- dev_err(priv->card.dev, "failed to allocate links\n"); +- return -ENOMEM; +- + } + EXPORT_SYMBOL_GPL(meson_card_reallocate_links); + +@@ -76,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) +@@ -83,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; + } +@@ -94,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; +@@ -137,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; + +@@ -163,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; + } +@@ -194,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; + } + +@@ -224,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/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/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/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c +--- a/sound/soc/pxa/pxa-ssp.c ++++ b/sound/soc/pxa/pxa-ssp.c +@@ -769,13 +769,10 @@ static int pxa_ssp_probe(struct snd_soc_dai *dai) + goto err_priv; + } + +- priv->extclk = devm_clk_get(dev, "extclk"); ++ priv->extclk = devm_clk_get_optional(dev, "extclk"); + if (IS_ERR(priv->extclk)) { + ret = PTR_ERR(priv->extclk); +- if (ret == -EPROBE_DEFER) +- goto err_priv; +- +- priv->extclk = NULL; ++ goto err_priv; + } + } else { + priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio"); +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/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c +--- a/sound/soc/qcom/qdsp6/q6apm.c ++++ b/sound/soc/qcom/qdsp6/q6apm.c +@@ -802,14 +802,17 @@ EXPORT_SYMBOL_GPL(q6apm_graph_prepare); + int q6apm_graph_start(struct q6apm_graph *graph) + { + struct audioreach_graph *ar_graph = graph->ar_graph; +- int ret = 0; ++ int ret; + +- if (ar_graph->start_count == 0) ++ if (ar_graph->start_count == 0) { + ret = audioreach_graph_mgmt_cmd(ar_graph, APM_CMD_GRAPH_START); ++ if (ret) ++ return ret; ++ } + + ar_graph->start_count++; + +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(q6apm_graph_start); + +@@ -817,6 +820,9 @@ int q6apm_graph_stop(struct q6apm_graph *graph) + { + struct audioreach_graph *ar_graph = graph->ar_graph; + ++ if (ar_graph->start_count == 0) ++ return 0; ++ + if (--ar_graph->start_count > 0) + return 0; + +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 +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -512,7 +513,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + u32 mod, mask, val = 0; + int ret = 0; + +- pm_runtime_get_sync(dai->dev); ++ guard(pm_runtime_active)(dai->dev); + + scoped_guard(spinlock_irqsave, &priv->lock) + mod = readl(priv->addr + I2SMOD); +@@ -537,8 +538,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + && (mod & cdcon_mask))))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); +- ret = -EAGAIN; +- goto err; ++ return -EAGAIN; + } + + if (dir == SND_SOC_CLOCK_IN) +@@ -566,7 +566,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + } else { + priv->rclk_srcrate = + clk_get_rate(priv->op_clk); +- goto done; ++ return 0; + } + } + +@@ -580,14 +580,14 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + if (WARN_ON(IS_ERR(priv->op_clk))) { + ret = PTR_ERR(priv->op_clk); + priv->op_clk = NULL; +- goto err; ++ return ret; + } + + ret = clk_prepare_enable(priv->op_clk); + if (ret) { + clk_put(priv->op_clk); + priv->op_clk = NULL; +- goto err; ++ return ret; + } + priv->rclk_srcrate = clk_get_rate(priv->op_clk); + +@@ -595,11 +595,10 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + || (clk_id && !(mod & rsrc_mask))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); +- ret = -EAGAIN; +- goto err; ++ return -EAGAIN; + } else { + /* Call can't be on the active DAI */ +- goto done; ++ return 0; + } + + if (clk_id == 1) +@@ -607,8 +606,7 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + break; + default: + dev_err(&i2s->pdev->dev, "We don't serve that!\n"); +- ret = -EINVAL; +- goto err; ++ return -EINVAL; + } + + scoped_guard(spinlock_irqsave, &priv->lock) { +@@ -616,13 +614,8 @@ static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs, + mod = (mod & ~mask) | val; + writel(mod, priv->addr + I2SMOD); + } +-done: +- pm_runtime_put(dai->dev); + + return 0; +-err: +- pm_runtime_put(dai->dev); +- return ret; + } + + static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +@@ -1123,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), + +@@ -1448,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) { +@@ -1657,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, + }; +@@ -1682,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_bridge_cs35l56.c b/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c +--- a/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c ++++ b/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c +@@ -99,11 +99,9 @@ static const struct snd_soc_dai_link bridge_dai_template = { + SND_SOC_DAILINK_REG(asoc_sdw_bridge_dai), + }; + +-int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card, ++int asoc_sdw_bridge_cs35l56_count_sidecar(struct asoc_sdw_mc_private *ctx, + int *num_dais, int *num_devs) + { +- struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); +- + if (ctx->mc_quirk & SOC_SDW_SIDECAR_AMPS) { + (*num_dais)++; + (*num_devs) += ARRAY_SIZE(bridge_cs35l56_name_prefixes); +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,21 +1990,16 @@ 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 snd_soc_card *card, ++int asoc_sdw_parse_sdw_endpoints(struct device *dev, ++ struct asoc_sdw_mc_private *ctx, + struct snd_soc_aux_dev *soc_aux, + struct asoc_sdw_dailink *soc_dais, + struct asoc_sdw_endpoint *soc_ends, + int *num_devs) + { +- struct device *dev = card->dev; +- struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); + struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; + const struct snd_soc_acpi_link_adr *adr_link; +@@ -2045,7 +2061,7 @@ int asoc_sdw_parse_sdw_endpoints(struct snd_soc_card *card, + ctx->ignore_internal_dmic |= codec_info->ignore_internal_dmic; + + if (codec_info->count_sidecar && codec_info->add_sidecar) { +- ret = codec_info->count_sidecar(card, &num_dais, num_devs); ++ ret = codec_info->count_sidecar(ctx, &num_dais, num_devs); + if (ret) + return ret; + +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); +@@ -1127,6 +1168,7 @@ static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget) + "Invalid number of formats: input: %d, output: %d\n", + src->available_fmt.num_input_formats, + src->available_fmt.num_output_formats); ++ ret = -EINVAL; + goto err; + } + +@@ -1179,6 +1221,7 @@ static int sof_ipc4_widget_setup_comp_asrc(struct snd_sof_widget *swidget) + "Invalid number of formats: input: %d, output: %d\n", + asrc->available_fmt.num_input_formats, + asrc->available_fmt.num_output_formats); ++ ret = -EINVAL; + goto err; + } + +@@ -1369,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); + +@@ -1686,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) { +@@ -2063,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; +@@ -2538,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, +@@ -2551,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, +@@ -2596,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, +@@ -2642,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, +@@ -2808,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, +@@ -2820,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; + +@@ -2866,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; + } +@@ -3083,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) +@@ -3111,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; +@@ -3154,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; +@@ -3340,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; + +@@ -3353,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; +@@ -3410,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) +@@ -846,6 +846,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + struct snd_soc_tplg_mixer_control *mc = + container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); + int tlv[SOF_TLV_ITEMS]; ++ u32 min, max; + unsigned int mask; + int ret; + +@@ -853,6 +854,11 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; + ++ min = le32_to_cpu(mc->min); ++ max = le32_to_cpu(mc->max); ++ if (min > max || max >= INT_MAX) ++ return -EINVAL; ++ + /* + * If control has more than 2 channels we need to override the info. This is because even if + * ASoC layer has defined topology's max channel count to SND_SOC_TPLG_MAX_CHAN = 8, the +@@ -863,12 +869,12 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + kc->info = snd_sof_volume_info; + + scontrol->comp_id = sdev->next_comp_id; +- scontrol->min_volume_step = le32_to_cpu(mc->min); +- scontrol->max_volume_step = le32_to_cpu(mc->max); ++ scontrol->min_volume_step = min; ++ scontrol->max_volume_step = max; + scontrol->num_channels = le32_to_cpu(mc->num_channels); + +- scontrol->max = le32_to_cpu(mc->max); +- if (le32_to_cpu(mc->max) == 1) ++ scontrol->max = max; ++ if (max == 1) + goto skip; + + /* extract tlv data */ +@@ -878,7 +884,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + } + + /* set up volume table */ +- ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); ++ ret = set_up_volume_table(scontrol, tlv, max + 1); + if (ret < 0) { + dev_err(scomp->dev, "error: setting up volume table\n"); + return ret; +@@ -911,7 +917,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, + return 0; + + err: +- if (le32_to_cpu(mc->max) > 1) ++ if (max > 1) + kfree(scontrol->volume_table); + + return ret; +@@ -1102,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)) { +@@ -1167,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)) +@@ -1943,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; +@@ -1995,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; +@@ -2013,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) { +@@ -2070,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; +@@ -2505,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; + +@@ -2537,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; +- } + } + + /* +@@ -2551,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; +@@ -2567,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) { + /* +@@ -2585,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); +@@ -2605,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) { + /* +@@ -2629,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); +@@ -2649,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 +@@ -52,7 +52,7 @@ struct spacemit_i2s_dev { + + struct clk *sysclk; + struct clk *bclk; +- struct clk *sspa_clk; ++ struct clk *func_clk; + struct clk *sysclk_div; + struct clk *c_sysclk; + struct clk *c_bclk; +@@ -221,7 +221,7 @@ static int spacemit_i2s_hw_params(struct snd_pcm_substream *substream, + if (ret) + return ret; + +- return clk_set_rate(i2s->sspa_clk, bclk_rate); ++ return clk_set_rate(i2s->func_clk, bclk_rate); + } + + static int spacemit_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, +@@ -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,26 +348,12 @@ 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 = { + .ops = &spacemit_i2s_dai_ops, +- .playback = { +- .channels_min = 1, +- .channels_max = 2, +- .rates = SPACEMIT_PCM_RATES, +- .rate_min = SNDRV_PCM_RATE_8000, +- .rate_max = SNDRV_PCM_RATE_48000, +- .formats = SPACEMIT_PCM_FORMATS, +- }, +- .capture = { +- .channels_min = 1, +- .channels_max = 2, +- .rates = SPACEMIT_PCM_RATES, +- .rate_min = SNDRV_PCM_RATE_8000, +- .rate_max = SNDRV_PCM_RATE_48000, +- .formats = SPACEMIT_PCM_FORMATS, +- }, + .symmetric_rate = 1, + }; + +@@ -392,6 +383,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, + dai->playback.channels_min = 1; + dai->playback.channels_max = 2; + dai->playback.rates = SPACEMIT_PCM_RATES; ++ dai->playback.rate_min = SNDRV_PCM_RATE_8000; ++ dai->playback.rate_max = SNDRV_PCM_RATE_48000; + dai->playback.formats = SPACEMIT_PCM_FORMATS; + + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +@@ -404,6 +397,8 @@ static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, + dai->capture.channels_min = 1; + dai->capture.channels_max = 2; + dai->capture.rates = SPACEMIT_PCM_RATES; ++ dai->capture.rate_min = SNDRV_PCM_RATE_8000; ++ dai->capture.rate_max = SNDRV_PCM_RATE_48000; + dai->capture.formats = SPACEMIT_PCM_FORMATS; + + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +@@ -445,14 +440,14 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + if (IS_ERR(i2s->bclk)) + return dev_err_probe(i2s->dev, PTR_ERR(i2s->bclk), "failed to enable bit clock\n"); + +- clk = devm_clk_get_enabled(i2s->dev, "sspa_bus"); ++ clk = devm_clk_get_enabled(i2s->dev, "bus"); + if (IS_ERR(clk)) +- return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable sspa_bus clock\n"); ++ return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable bus clock\n"); + +- i2s->sspa_clk = devm_clk_get_enabled(i2s->dev, "sspa"); +- if (IS_ERR(i2s->sspa_clk)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->sspa_clk), +- "failed to enable sspa clock\n"); ++ i2s->func_clk = devm_clk_get_enabled(i2s->dev, "func"); ++ if (IS_ERR(i2s->func_clk)) ++ return dev_err_probe(i2s->dev, PTR_ERR(i2s->func_clk), ++ "failed to enable func clock\n"); + + i2s->sysclk_div = devm_clk_get_optional_enabled(i2s->dev, "sysclk_div"); + if (IS_ERR(i2s->sysclk_div)) +@@ -471,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)) +@@ -488,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 +@@ -4,6 +4,7 @@ + * Author: Peter Ujfalusi + */ + ++#include + #include + #include + #include +@@ -263,7 +264,7 @@ static int j721e_audio_startup(struct snd_pcm_substream *substream) + int ret = 0; + int i; + +- guard(mutex)(&priv->mutex); ++ mutex_lock(&priv->mutex); + + domain->active++; + +@@ -303,6 +304,7 @@ static int j721e_audio_startup(struct snd_pcm_substream *substream) + out: + if (ret) + domain->active--; ++ mutex_unlock(&priv->mutex); + + return ret; + } +@@ -868,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 +@@ -384,7 +382,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err) { + dev_err(component->dev, + "Unable to set constraint on period bytes\n"); +- return err; ++ goto error; + } + + /* Resize the buffer bytes as divisible by 64 */ +@@ -394,7 +392,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err) { + dev_err(component->dev, + "Unable to set constraint on buffer bytes\n"); +- return err; ++ goto error; + } + + /* Set periods as integer multiple */ +@@ -403,7 +401,7 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + if (err < 0) { + dev_err(component->dev, + "Unable to set constraint on periods to be integer\n"); +- return err; ++ goto error; + } + + /* enable DMA IOC irq */ +@@ -412,6 +410,14 @@ static int xlnx_formatter_pcm_open(struct snd_soc_component *component, + writel(val, stream_data->mmio + XLNX_AUD_CTRL); + + return 0; ++ ++error: ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ++ adata->play_stream = NULL; ++ else ++ adata->capture_stream = NULL; ++ kfree(stream_data); ++ return err; + } + + static int xlnx_formatter_pcm_close(struct snd_soc_component *component, +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/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 +@@ -1236,7 +1236,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); + } + + /* allocate and initialize data urbs */ +@@ -1260,6 +1263,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/0510-sound-updates.patch.sig b/pkgbuilds/linux-omarchy/0510-sound-updates.patch.sig new file mode 100644 index 0000000..cfb31fc Binary files /dev/null and b/pkgbuilds/linux-omarchy/0510-sound-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch b/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch new file mode 100644 index 0000000..c32b5c3 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch @@ -0,0 +1,1655 @@ +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/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/control_led.c b/sound/core/control_led.c +--- a/sound/core/control_led.c ++++ b/sound/core/control_led.c +@@ -255,6 +255,8 @@ static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, + kctl = snd_ctl_find_id(card, id); + if (!kctl) + return -ENOENT; ++ if (!kctl->info || !kctl->get) ++ return -EINVAL; + ioff = snd_ctl_get_ioff(kctl, id); + vd = &kctl->vd[ioff]; + access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; +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/drivers/mtpav.c b/sound/drivers/mtpav.c +--- a/sound/drivers/mtpav.c ++++ b/sound/drivers/mtpav.c +@@ -642,9 +642,7 @@ static void snd_mtpav_free(struct snd_card *card) + { + struct mtpav *crd = card->private_data; + +- guard(spinlock_irqsave)(&crd->spinlock); +- if (crd->istimer > 0) +- snd_mtpav_remove_output_timer(crd); ++ timer_shutdown_sync(&crd->timer); + } + + /* +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/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; ++ struct snd_card *card __free(snd_card_unref) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; +@@ -2535,8 +2535,8 @@ static int snd_ice1712_probe(struct pci_dev *pci, + return -ENOENT; + } + +- err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, +- sizeof(*ice), &card); ++ err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, ++ sizeof(*ice), &card); + if (err < 0) + return err; + ice = card->private_data; +@@ -2640,6 +2640,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + if (err < 0) + return err; + pci_set_drvdata(pci, card); ++ card = NULL; /* probe succeeded, don't release as error */ + dev++; + 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 +@@ -54,7 +54,7 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), +- DMI_MATCH(DMI_PRODUCT_NAME, "TUF Gaming A14 FA401EA"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "FA401EA"), + }, + }, + { +@@ -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/fsl/Kconfig b/sound/soc/fsl/Kconfig +--- a/sound/soc/fsl/Kconfig ++++ b/sound/soc/fsl/Kconfig +@@ -244,7 +244,7 @@ config SND_SOC_MPC5200_AC97 + config SND_MPC52xx_SOC_PCM030 + tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" + depends on PPC_MPC5200_SIMPLE +- select SND_SOC_MPC5200_AC97 ++ depends on SND_SOC_MPC5200_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for sound on the Phytec pcm030 +@@ -253,7 +253,7 @@ config SND_MPC52xx_SOC_PCM030 + config SND_MPC52xx_SOC_EFIKA + tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" + depends on PPC_EFIKA +- select SND_SOC_MPC5200_AC97 ++ depends on SND_SOC_MPC5200_AC97 + select SND_SOC_STAC9766 + help + Say Y if you want to add support for sound on the Efika. +diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c +--- a/sound/soc/fsl/efika-audio-fabric.c ++++ b/sound/soc/fsl/efika-audio-fabric.c +@@ -22,8 +22,6 @@ + #include + #include + +-#include "mpc5200_dma.h" +- + #define DRV_NAME "efika-audio-fabric" + + SND_SOC_DAILINK_DEFS(analog, +diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c +--- a/sound/soc/fsl/pcm030-audio-fabric.c ++++ b/sound/soc/fsl/pcm030-audio-fabric.c +@@ -13,8 +13,6 @@ + + #include + +-#include "mpc5200_dma.h" +- + #define DRV_NAME "pcm030-audio-fabric" + + struct pcm030_audio_data { +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-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -3614,7 +3614,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + mask = e->mask << e->shift_l; + if (e->shift_l != e->shift_r) { +- if (item[1] > e->items) ++ if (item[1] >= e->items) + return -EINVAL; + val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; + mask |= e->mask << e->shift_r; +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/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/0511-sound-updates-fixes.patch.sig b/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch.sig new file mode 100644 index 0000000..d84e156 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0512-sound-fixes.patch b/pkgbuilds/linux-omarchy/0512-sound-fixes.patch new file mode 100644 index 0000000..9566376 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0512-sound-fixes.patch @@ -0,0 +1,507 @@ +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 +@@ -4023,20 +4023,33 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + return -EINVAL; + } + runtime = substream->runtime; +- if (runtime->state == SNDRV_PCM_STATE_OPEN) +- return -EBADFD; +- if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) +- return -ENXIO; ++ /* don't race with buffer reallocation in hw_params/hw_free */ ++ if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) ++ return -EBUSY; ++ if (runtime->state == SNDRV_PCM_STATE_OPEN) { ++ err = -EBADFD; ++ goto out; ++ } ++ if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) { ++ err = -ENXIO; ++ goto out; ++ } + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || +- runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) +- return -EINVAL; ++ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { ++ err = -EINVAL; ++ goto out; ++ } + size = area->vm_end - area->vm_start; + offset = area->vm_pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); +- if ((size_t)size > dma_bytes) +- return -EINVAL; +- if (offset > dma_bytes - size) +- return -EINVAL; ++ if ((size_t)size > dma_bytes) { ++ err = -EINVAL; ++ goto out; ++ } ++ if (offset > dma_bytes - size) { ++ err = -EINVAL; ++ goto out; ++ } + + area->vm_ops = &snd_pcm_vm_ops_data; + area->vm_private_data = substream; +@@ -4046,6 +4059,8 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + err = snd_pcm_lib_default_mmap(substream, area); + if (!err) + atomic_inc(&substream->mmap_count); ++out: ++ atomic_dec(&runtime->buffer_accessing); + return err; + } + EXPORT_SYMBOL(snd_pcm_mmap_data); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1335,6 +1335,8 @@ static void update_legacy_names(struct snd_ump_endpoint *ump) + { + struct snd_rawmidi *rmidi = ump->legacy_rmidi; + ++ if (!rmidi) ++ return; + update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT); + update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT); + } +@@ -1343,6 +1345,8 @@ static void ump_legacy_set_rawmidi_name(struct snd_ump_endpoint *ump) + { + struct snd_rawmidi *rmidi = ump->legacy_rmidi; + ++ if (!rmidi) ++ return; + snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)", + ump->core.name); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -807,7 +807,7 @@ static int snd_dummy_capsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_el + left = ucontrol->value.integer.value[0] & 1; + right = ucontrol->value.integer.value[1] & 1; + guard(spinlock_irq)(&dummy->mixer_lock); +- change = dummy->capture_source[addr][0] != left && ++ change = dummy->capture_source[addr][0] != left || + dummy->capture_source[addr][1] != right; + dummy->capture_source[addr][0] = left; + dummy->capture_source[addr][1] = right; +diff --git a/sound/hda/codecs/cirrus/cs420x.c b/sound/hda/codecs/cirrus/cs420x.c +--- a/sound/hda/codecs/cirrus/cs420x.c ++++ b/sound/hda/codecs/cirrus/cs420x.c +@@ -571,6 +571,7 @@ static const struct hda_model_fixup cs4208_models[] = { + + static const struct hda_quirk cs4208_fixup_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS4208_MAC_AUTO), ++ SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MAC_AUTO), + {} /* terminator */ + }; + +@@ -583,6 +584,7 @@ static const struct hda_quirk cs4208_mac_fixup_tbl[] = { + SND_PCI_QUIRK(0x106b, 0x7800, "MacPro 6,1", CS4208_MACMINI), + SND_PCI_QUIRK(0x106b, 0x7b00, "MacBookPro 12,1", CS4208_MBP11), + SND_PCI_QUIRK(0x106b, 0x7f00, "iMac 16,1", CS4208_MBP11), ++ SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MBA6), + {} /* terminator */ + }; + +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 +@@ -249,6 +249,25 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + } + } + ++#define SN6140_S3_AFG_D0_DELAY_MS 1000 ++ ++static void cx_set_power_state(struct hda_codec *codec, hda_nid_t fg, ++ unsigned int power_state) ++{ ++ snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); ++ ++ /* ++ * SN6140 may not respond to AFG D0 immediately after S3. ++ * Wait before the D0 verb so the power-state command itself succeeds. ++ */ ++ if (codec->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/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -404,6 +404,7 @@ static void setup_fg_nodes(struct hdac_device *codec) + */ + int snd_hdac_refresh_widgets(struct hdac_device *codec) + { ++ hda_nid_t fg = codec->afg ? codec->afg : codec->mfg; + hda_nid_t start_nid; + int nums, err = 0; + +@@ -412,10 +413,10 @@ int snd_hdac_refresh_widgets(struct hdac_device *codec) + * widgets array. + */ + guard(mutex)(&codec->widget_lock); +- nums = snd_hdac_get_sub_nodes(codec, codec->afg, &start_nid); ++ nums = snd_hdac_get_sub_nodes(codec, fg, &start_nid); + if (!start_nid || nums <= 0 || nums >= 0xff) { + dev_err(&codec->dev, "cannot read sub nodes for FG 0x%02x\n", +- codec->afg); ++ fg); + return -EINVAL; + } + +diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c +--- a/sound/usb/caiaq/audio.c ++++ b/sound/usb/caiaq/audio.c +@@ -828,16 +828,13 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) + + cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret); + if (ret < 0) { +- kfree(cdev->data_cb_info); +- free_urbs(cdev->data_urbs_in); ++ snd_usb_caiaq_audio_free(cdev); + return ret; + } + + cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret); + if (ret < 0) { +- kfree(cdev->data_cb_info); +- free_urbs(cdev->data_urbs_in); +- free_urbs(cdev->data_urbs_out); ++ snd_usb_caiaq_audio_free(cdev); + return ret; + } + +@@ -858,6 +855,9 @@ void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + free_urbs(cdev->data_urbs_in); ++ cdev->data_urbs_in = NULL; + free_urbs(cdev->data_urbs_out); ++ cdev->data_urbs_out = NULL; + kfree(cdev->data_cb_info); ++ cdev->data_cb_info = NULL; + } +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/0512-sound-fixes.patch.sig b/pkgbuilds/linux-omarchy/0512-sound-fixes.patch.sig new file mode 100644 index 0000000..c02c1df Binary files /dev/null and b/pkgbuilds/linux-omarchy/0512-sound-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch b/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch new file mode 100644 index 0000000..6a5a07c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0513-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/0513-xps13-sof-quirk.patch.sig b/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch.sig new file mode 100644 index 0000000..1ccaf57 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch b/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch new file mode 100644 index 0000000..25fee81 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch @@ -0,0 +1,30 @@ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +--- a/sound/soc/codecs/rt766-sdca.c ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -936,9 +936,8 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + 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_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/0514-rt766-stream-config-type.patch.sig b/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch.sig new file mode 100644 index 0000000..2b79ff3 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch b/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch new file mode 100644 index 0000000..47dae22 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0516-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/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig b/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig new file mode 100644 index 0000000..0cfe941 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch b/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch new file mode 100644 index 0000000..0d8e619 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0517-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/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig b/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig new file mode 100644 index 0000000..62063af Binary files /dev/null and b/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch b/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch new file mode 100644 index 0000000..3762b2e --- /dev/null +++ b/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch @@ -0,0 +1,41 @@ +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 +@@ -173,6 +173,19 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC10E1" }, /* PTL */ + }; + ++/* ++ * The subset of ivsc_acpi_ids[] which are IVSC, rather than CVS, devices. The ++ * CVS IDs are deliberately not listed here: new ones keep being added, whereas ++ * this list is complete. ++ */ ++static const struct acpi_device_id ivsc_only_acpi_ids[] = { ++ { "INTC1059" }, ++ { "INTC1095" }, ++ { "INTC100A" }, ++ { "INTC10CF" }, ++ { } ++}; ++ + static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) + { + unsigned int i; +@@ -224,6 +237,17 @@ static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev) + return csi_dev; + } + ++ /* ++ * The lookups below match on the ACPI companion alone. That is fine for ++ * CVS, which binds a driver to that very device, but not for IVSC: there ++ * the ACPI device also has a driverless platform device, which would be ++ * returned instead of the mei-csi client. Return NULL for IVSC so that ++ * the caller fails and the probe is retried once the IVSC device shows ++ * up. ++ */ ++ if (!acpi_match_device_ids(adev, ivsc_only_acpi_ids)) ++ return NULL; ++ + /* Try to locate CVS device on the I2C bus */ + csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev); + if (csi_dev) diff --git a/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch.sig b/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch.sig new file mode 100644 index 0000000..5f2c204 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch b/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch new file mode 100644 index 0000000..39ccf4d --- /dev/null +++ b/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch @@ -0,0 +1,33 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -862,6 +862,7 @@ static const char * const acpi_honor_dep_ids[] = { + "INTC10DE", /* CVS (LNL) driver must be loaded to allow camera streaming */ + "INTC10E0", /* CVS (ARL) driver must be loaded to allow camera streaming */ + "INTC10E1", /* CVS (PTL) driver must be loaded to allow camera streaming */ ++ "INTC10FA", /* CVS (NVL) driver must be loaded to allow camera streaming */ + "RSCV0001", /* RISC-V PLIC */ + "RSCV0002", /* RISC-V APLIC */ + "RSCV0005", /* RISC-V SBI MPXY MBOX */ +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 +@@ -962,6 +962,7 @@ static const struct acpi_device_id intel_cvs_acpi_match[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ ++ { "INTC10FA" }, /* NVL */ + { } + }; + MODULE_DEVICE_TABLE(acpi, intel_cvs_acpi_match); +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 +@@ -171,6 +171,7 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { + { "INTC10DE" }, /* LNL */ + { "INTC10E0" }, /* ARL */ + { "INTC10E1" }, /* PTL */ ++ { "INTC10FA" }, /* NVL */ + }; + + /* diff --git a/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch.sig b/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch.sig new file mode 100644 index 0000000..6ea6cfa Binary files /dev/null and b/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch b/pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch new file mode 100644 index 0000000..6a78d2e --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig b/pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig new file mode 100644 index 0000000..69eeeea Binary files /dev/null and b/pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0560-input.patch b/pkgbuilds/linux-omarchy/0560-input.patch new file mode 100644 index 0000000..0d58569 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0560-input.patch @@ -0,0 +1,76 @@ +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 +@@ -267,7 +267,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: +@@ -1466,6 +1474,9 @@ static void psmouse_disconnect(struct serio *serio) + psmouse_deactivate(parent); + } + ++ scoped_guard(serio_pause_rx, serio) ++ psmouse->protocol_handler = NULL; ++ + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + diff --git a/pkgbuilds/linux-omarchy/0560-input.patch.sig b/pkgbuilds/linux-omarchy/0560-input.patch.sig new file mode 100644 index 0000000..643da0a Binary files /dev/null and b/pkgbuilds/linux-omarchy/0560-input.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch b/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch new file mode 100644 index 0000000..01748b9 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0565-i2c-asue140d-touchpad-100khz.patch.sig b/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch.sig new file mode 100644 index 0000000..d5acafd Binary files /dev/null and b/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch b/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch new file mode 100644 index 0000000..e569f4c --- /dev/null +++ b/pkgbuilds/linux-omarchy/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 +@@ -1294,12 +1294,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/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig b/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig new file mode 100644 index 0000000..dbe7b90 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch b/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch new file mode 100644 index 0000000..5fe836e --- /dev/null +++ b/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch @@ -0,0 +1,47 @@ +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +index 4cc86d8d6491..43d29d69dcdb 100644 +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -512,8 +512,10 @@ tbstream_dev_alloc_tx(struct tbstream_dev *sdev, enum tbstream_frame_pdf pdf, + dma_sync_single_for_cpu(dma_dev, sf->frame.buffer_phy, size, + DMA_TO_DEVICE); + if (pdf == TBSTREAM_DATA) { +- if (copy_page_from_iter(sf->page, 0, size, from) != size) ++ if (copy_page_from_iter(sf->page, 0, size, from) != size) { ++ sdev->tx_ring.cons--; + return ERR_PTR(-EFAULT); ++ } + } else { + memset(page_address(sf->page), 0, size); + } +@@ -671,7 +673,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + } + + nbytes = 0; +- while (nbytes < iov_iter_count(to)) { ++ while (iov_iter_count(to)) { + struct tbstream_frame *sf; + size_t size, sf_size; + +@@ -693,7 +695,7 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + } + + sf_size = tb_ring_frame_size(&sf->frame); +- size = min(iov_iter_count(to) - nbytes, sf_size); ++ size = min(iov_iter_count(to), sf_size); + + if (copy_page_to_iter(sf->page, sf->offset, size, to) != size) { + ret = -EFAULT; +@@ -763,10 +765,10 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + } + + nbytes = 0; +- while (nbytes < iov_iter_count(from)) { ++ while (iov_iter_count(from)) { + size_t size; + +- size = min(iov_iter_count(from) - nbytes, TB_MAX_FRAME_SIZE); ++ size = min(iov_iter_count(from), TB_MAX_FRAME_SIZE); + ret = tbstream_dev_send_data(sdev, from, size); + if (ret) { + /* diff --git a/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch.sig b/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch.sig new file mode 100644 index 0000000..753b735 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch b/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch new file mode 100644 index 0000000..0cb7d2a --- /dev/null +++ b/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch @@ -0,0 +1,477 @@ +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +index 35e3c119d5ee..a4816db5cacd 100644 +--- 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); +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +index 43d29d69dcdb..a7d9fda66875 100644 +--- 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, +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +index 557288c0274b..8367d106d81b 100644 +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -592,6 +592,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/0601-usb4stream-busy-poll.patch.sig b/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch.sig new file mode 100644 index 0000000..45711f6 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch b/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch new file mode 100644 index 0000000..3db7549 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch @@ -0,0 +1,132 @@ +diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c +index 263a89c5f324..5ee33a69b9cf 100644 +--- a/drivers/usb/typec/altmodes/displayport.c ++++ b/drivers/usb/typec/altmodes/displayport.c +@@ -790,7 +790,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 +810,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 +index 32250b94262a..2eccdddf1b1f 100644 +--- 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 +index 0595e8cb83aa..54c7810b563b 100644 +--- 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/include/linux/usb/typec.h b/include/linux/usb/typec.h +index d61ec38216fa..10a783b738ef 100644 +--- 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/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch.sig b/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch.sig new file mode 100644 index 0000000..939524b Binary files /dev/null and b/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch b/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch new file mode 100644 index 0000000..d2ac981 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0620-usb-string-sanitize.patch.sig b/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch.sig new file mode 100644 index 0000000..030b9fa Binary files /dev/null and b/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch b/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch new file mode 100644 index 0000000..87743c9 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h +--- a/drivers/net/wireguard/queueing.h ++++ b/drivers/net/wireguard/queueing.h +@@ -78,12 +78,14 @@ static inline void wg_reset_packet(struct sk_buff *skb, bool encapsulating) + u8 l4_hash = skb->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/0650-wireguard-tstamp-type.patch.sig b/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch.sig new file mode 100644 index 0000000..4dfd43e Binary files /dev/null and b/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch b/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch new file mode 100644 index 0000000..8789452 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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 +@@ -801,6 +801,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/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig b/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig new file mode 100644 index 0000000..ade8cc2 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch b/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch new file mode 100644 index 0000000..539fe9a --- /dev/null +++ b/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch @@ -0,0 +1,32 @@ +diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c +--- a/drivers/net/wireless/realtek/rtw89/fw.c ++++ b/drivers/net/wireless/realtek/rtw89/fw.c +@@ -11780,7 +11780,7 @@ static void rtw89_fw_cmd_ofld_write_rf(struct rtw89_dev *rtwdev, + static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) + { + struct rtw89_fw_cmd_ofld_arg cmd = { +- .src = RTW89_FW_CMD_OFLD_SRC_OTHER, ++ .src = RTW89_FW_CMD_OFLD_SRC_BB, + .type = RTW89_FW_CMD_OFLD_DELAY, + .value = us, + }; +@@ -11794,7 +11794,7 @@ static void rtw89_fw_cmd_ofld_udelay(struct rtw89_dev *rtwdev, u32 us) + static void rtw89_fw_cmd_ofld_mdelay(struct rtw89_dev *rtwdev, u32 ms) + { + struct rtw89_fw_cmd_ofld_arg cmd = { +- .src = RTW89_FW_CMD_OFLD_SRC_OTHER, ++ .src = RTW89_FW_CMD_OFLD_SRC_BB, + .type = RTW89_FW_CMD_OFLD_DELAY, + .value = ms * 1000, + }; +diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h +--- a/drivers/net/wireless/realtek/rtw89/fw.h ++++ b/drivers/net/wireless/realtek/rtw89/fw.h +@@ -3142,7 +3142,6 @@ enum rtw89_fw_cmd_ofld_arg_src { + RTW89_FW_CMD_OFLD_SRC_RF, + RTW89_FW_CMD_OFLD_SRC_MAC, + RTW89_FW_CMD_OFLD_SRC_RF_DDIE, +- RTW89_FW_CMD_OFLD_SRC_OTHER, + }; + + enum rtw89_fw_cmd_ofld_arg_type { diff --git a/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch.sig b/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch.sig new file mode 100644 index 0000000..0459681 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch b/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch new file mode 100644 index 0000000..d3b514c --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig b/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig new file mode 100644 index 0000000..f2bc1c6 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch b/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch new file mode 100644 index 0000000..1102b3f --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0700-pci-target-speed-quirk.patch.sig b/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch.sig new file mode 100644 index 0000000..2b1e9cc Binary files /dev/null and b/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch b/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch new file mode 100644 index 0000000..bd8dd7c --- /dev/null +++ b/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch @@ -0,0 +1,31 @@ +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 +@@ -372,7 +373,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 +393,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); diff --git a/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch.sig b/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch.sig new file mode 100644 index 0000000..d3799b1 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch b/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch new file mode 100644 index 0000000..6eda26b --- /dev/null +++ b/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -1252,12 +1252,17 @@ static void applesmc_release_light_sensor(void) + + static int applesmc_create_key_backlight(void) + { ++ int ret; ++ + if (!smcreg.has_key_backlight) + return 0; + applesmc_led_wq = create_singlethread_workqueue("applesmc-led"); + if (!applesmc_led_wq) + return -ENOMEM; +- return led_classdev_register(&pdev->dev, &applesmc_backlight); ++ ret = led_classdev_register(&pdev->dev, &applesmc_backlight); ++ if (ret) ++ destroy_workqueue(applesmc_led_wq); ++ return ret; + } + + static void applesmc_release_key_backlight(void) diff --git a/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch.sig b/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch.sig new file mode 100644 index 0000000..f0da512 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch b/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch new file mode 100644 index 0000000..da85b37 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig b/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig new file mode 100644 index 0000000..8744164 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0800-platform-updates.patch b/pkgbuilds/linux-omarchy/0800-platform-updates.patch new file mode 100644 index 0000000..bb6a9de --- /dev/null +++ b/pkgbuilds/linux-omarchy/0800-platform-updates.patch @@ -0,0 +1,8751 @@ +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/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c +--- a/drivers/platform/arm64/qcom-hamoa-ec.c ++++ b/drivers/platform/arm64/qcom-hamoa-ec.c +@@ -92,8 +92,10 @@ static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp) + return ret; + else if (ret == 0 || ret == 0xff) + return -EOPNOTSUPP; ++ else if (ret != resp_len) ++ return -EIO; + +- if (resp[0] >= resp_len) ++ if (resp[0] != resp_len - 1) + return -EINVAL; + + return 0; +diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c +--- a/drivers/platform/mellanox/mlxbf-bootctl.c ++++ b/drivers/platform/mellanox/mlxbf-bootctl.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c +--- a/drivers/platform/mellanox/mlxbf-pmc.c ++++ b/drivers/platform/mellanox/mlxbf-pmc.c +@@ -2262,13 +2262,19 @@ static int mlxbf_pmc_map_counters(struct device *dev) + + static int mlxbf_pmc_probe(struct platform_device *pdev) + { +- struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); +- const char *hid = acpi_device_hid(acpi_dev); + struct device *dev = &pdev->dev; ++ struct acpi_device *acpi_dev; + struct arm_smccc_res res; ++ const char *hid; + guid_t guid; + int ret; + ++ acpi_dev = ACPI_COMPANION(&pdev->dev); ++ if (!acpi_dev) ++ return -ENODEV; ++ ++ hid = acpi_device_hid(acpi_dev); ++ + /* Ensure we have the UUID we expect for this service. */ + arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); + guid_parse(mlxbf_pmc_svc_uuid_str, &guid); +diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c +--- a/drivers/platform/surface/surface_acpi_notify.c ++++ b/drivers/platform/surface/surface_acpi_notify.c +@@ -777,12 +777,16 @@ static int san_consumer_links_setup(struct platform_device *pdev) + + static int san_probe(struct platform_device *pdev) + { +- struct acpi_device *san = ACPI_COMPANION(&pdev->dev); + struct ssam_controller *ctrl; ++ struct acpi_device *san; + struct san_data *data; + acpi_status astatus; + int status; + ++ san = ACPI_COMPANION(&pdev->dev); ++ if (!san) ++ return -ENODEV; ++ + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); +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/acer-wmi.c b/drivers/platform/x86/acer-wmi.c +--- a/drivers/platform/x86/acer-wmi.c ++++ b/drivers/platform/x86/acer-wmi.c +@@ -1581,7 +1581,9 @@ static int WMI_gaming_execute_u32_u64(u32 method_id, u32 in, u64 *out) + return -EIO; + + obj = result.pointer; +- if (obj && out) { ++ if (!obj && out) { ++ ret = -ENOMSG; ++ } else if (obj && out) { + switch (obj->type) { + case ACPI_TYPE_INTEGER: + *out = obj->integer.value; +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/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c +--- a/drivers/platform/x86/amd/hsmp/hwmon.c ++++ b/drivers/platform/x86/amd/hsmp/hwmon.c +@@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + if (attr != hwmon_power_cap) + return -EOPNOTSUPP; + ++ if (val < 0) ++ return -EINVAL; ++ + msg.num_args = 1; + msg.args[0] = val / MICROWATT_PER_MILLIWATT; + msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT; +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-wireless.c b/drivers/platform/x86/asus-wireless.c +--- a/drivers/platform/x86/asus-wireless.c ++++ b/drivers/platform/x86/asus-wireless.c +@@ -132,6 +132,10 @@ static int asus_wireless_probe(struct platform_device *pdev) + const struct acpi_device_id *id; + int err; + ++ id = acpi_match_acpi_device(device_ids, adev); ++ if (!id) ++ return -ENODEV; ++ + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; +@@ -139,6 +143,7 @@ static int asus_wireless_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, data); + + data->adev = adev; ++ data->hswc_params = (const struct hswc_params *)id->driver_data; + + data->idev = devm_input_allocate_device(&pdev->dev); + if (!data->idev) +@@ -153,12 +158,6 @@ static int asus_wireless_probe(struct platform_device *pdev) + if (err) + return err; + +- id = acpi_match_acpi_device(device_ids, adev); +- if (!id) +- return 0; +- +- data->hswc_params = (const struct hswc_params *)id->driver_data; +- + data->wq = create_singlethread_workqueue("asus_wireless_workqueue"); + if (!data->wq) + return -ENOMEM; +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) + { +@@ -5244,20 +5268,20 @@ static int asus_wmi_add(struct platform_device *pdev) + return 0; + + fail_wmi_handler: ++ asus_screenpad_exit(asus); ++fail_screenpad: + asus_wmi_backlight_exit(asus); + fail_backlight: + asus_wmi_rfkill_exit(asus); +-fail_screenpad: +- asus_screenpad_exit(asus); + fail_rfkill: + asus_wmi_led_exit(asus); + fail_leds: ++fail_custom_fan_curve: + fail_hwmon: + asus_wmi_input_exit(asus); + fail_input: + asus_wmi_sysfs_exit(asus->platform_device); + fail_sysfs: +-fail_custom_fan_curve: + fail_platform_profile_setup: + fail_fan_boost_mode: + fail_platform: +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-base.c b/drivers/platform/x86/dell/dell-wmi-base.c +--- a/drivers/platform/x86/dell/dell-wmi-base.c ++++ b/drivers/platform/x86/dell/dell-wmi-base.c +@@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l + key++; + used = 1; + } else if (type == 0x0012 && code == 0x000d && remaining > 0) { +- value = (le16_to_cpu(buffer[2]) == 2); ++ value = (le16_to_cpu(buffer[0]) == 2); + used = 1; + } + +@@ -843,9 +843,22 @@ static int __init dell_wmi_init(void) + + err = dell_privacy_register_driver(); + if (err) +- return err; ++ goto out_smbios; + +- return wmi_driver_register(&dell_wmi_driver); ++ err = wmi_driver_register(&dell_wmi_driver); ++ if (err) ++ goto out_privacy; ++ ++ return 0; ++ ++out_privacy: ++ dell_privacy_unregister_driver(); ++ ++out_smbios: ++ if (wmi_requires_smbios_request) ++ dell_wmi_events_set_enabled(false); ++ ++ return err; + } + late_initcall(dell_wmi_init); + +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/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c +--- a/drivers/platform/x86/dell/dell-wmi-privacy.c ++++ b/drivers/platform/x86/dell/dell-wmi-privacy.c +@@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void) + { + struct privacy_wmi_data *priv; + +- mutex_lock(&list_mutex); ++ guard(mutex)(&list_mutex); ++ + priv = list_first_entry_or_null(&wmi_list, + struct privacy_wmi_data, + list); +- mutex_unlock(&list_mutex); + + return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); + } +diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +--- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h ++++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +@@ -107,7 +107,7 @@ enum { + static int get_##type##_instance_id(struct kobject *kobj) \ + { \ + int i; \ +- for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ ++ for (i = 0; i < wmi_priv.type##_instances_count; i++) { \ + if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ + return i; \ + } \ +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-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +--- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c ++++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +@@ -353,6 +353,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor + case PSWD_ENCODINGS: + size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE); + for (pos_values = 0; pos_values < size; pos_values++) { ++ if (elem + pos_values >= password_obj_count) { ++ pr_err("Error elem-objects package is too small\n"); ++ return -EINVAL; ++ } ++ + ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer, + password_obj[elem + pos_values].string.length, + &str_value, &value_len); +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,11 +142,52 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { + .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, + }; + +-/* +- * A generic pointer for the currently-active board's thermal profile +- * parameters. +- */ +-static struct thermal_profile_params *active_thermal_profile_params; ++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; ++ ++static const struct thermal_profile_params *hp_wmi_thermal_profile(void) ++{ ++ if (!active_board_params) ++ return NULL; ++ ++ return active_board_params->thermal_profile; ++} + + /* DMI board names of devices that should use the omen specific path for + * thermal profiles. +@@ -157,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", +@@ -187,83 +237,103 @@ static const char * const victus_thermal_profile_boards[] = { + "8A25", + }; + +-/* DMI Board names of Victus 16-r and Victus 16-s laptops */ +-static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { ++/* DMI board-specific feature data for Omen and Victus laptops. */ ++static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .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_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .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_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .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_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") }, +- .driver_data = (void *)&omen_v1_thermal_params, ++ .driver_data = (void *)&omen_v1_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") }, +- .driver_data = (void *)&victus_s_thermal_params, ++ .driver_data = (void *)&victus_s_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") }, +- .driver_data = (void *)&omen_v1_no_ec_thermal_params, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, +- .driver_data = (void *)&omen_v1_no_ec_thermal_params, ++ .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") }, +@@ -271,7 +341,7 @@ static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, +- .driver_data = (void *)&omen_v1_legacy_thermal_params, ++ .driver_data = (void *)&omen_v1_legacy_board_params, + }, + {}, + }; +@@ -336,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 { +@@ -504,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; + }; + +@@ -833,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) +@@ -867,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) +@@ -1128,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, +@@ -1142,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); +@@ -1793,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, +@@ -1878,7 +2102,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + u8 current_dstate, current_gpu_slowdown_temp, tp; + const struct thermal_profile_params *params; + +- params = active_thermal_profile_params; ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return -ENODEV; ++ + if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || + params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + *profile = active_platform_profile; +@@ -1890,10 +2117,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + return ret; + + /* +- * We cannot use active_thermal_profile_params here, because boards +- * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically +- * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen +- * Gaming Hub on windows. Hence accept both of these values. ++ * Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, ++ * but logically it should have tp == 0x30 || tp == 0x31, as ++ * corrected by the Omen Gaming Hub on windows. Hence accept both ++ * of these values. + */ + if (tp == victus_s_thermal_params.performance || + tp == omen_v1_thermal_params.performance) { +@@ -1928,12 +2155,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil + + static int platform_profile_victus_s_set_ec(enum platform_profile_option profile) + { +- struct thermal_profile_params *params; ++ const struct thermal_profile_params *params; + bool gpu_ctgp_enable, gpu_ppab_enable; + u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */ + int err, tp; + +- params = active_thermal_profile_params; ++ params = hp_wmi_thermal_profile(); + if (!params) + return -ENODEV; + +@@ -2199,6 +2426,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = { + static int thermal_profile_setup(struct platform_device *device) + { + const struct platform_profile_ops *ops; ++ const struct thermal_profile_params *params; + int err, tp; + + if (is_omen_thermal_profile()) { +@@ -2230,13 +2458,17 @@ static int thermal_profile_setup(struct platform_device *device) + + ops = &platform_profile_victus_ops; + } else if (is_victus_s_thermal_profile()) { ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return -ENODEV; ++ + /* + * For an unknown EC layout board, platform_profile_victus_s_get_ec(), + * behaves like a wrapper around active_platform_profile, to avoid using + * uninitialized data, we default to PLATFORM_PROFILE_BALANCED. + */ +- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || +- active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { ++ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN || ++ params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) { + active_platform_profile = PLATFORM_PROFILE_BALANCED; + } else { + err = platform_profile_victus_s_get_ec(&active_platform_profile); +@@ -2404,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; +@@ -2416,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; +@@ -2449,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) +@@ -2478,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) +@@ -2488,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); +@@ -2518,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) +@@ -2534,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) { +@@ -2542,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: +@@ -2567,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 + }; + +@@ -2608,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, +@@ -2654,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; + } +@@ -2697,24 +2935,25 @@ static int hp_wmi_hwmon_init(void) + return 0; + } + +-static void __init setup_active_thermal_profile_params(void) ++static void __init setup_active_board_params(void) + { + const struct dmi_system_id *id; ++ const struct thermal_profile_params *params; + +- /* +- * Currently only victus_s devices use the +- * active_thermal_profile_params +- */ +- id = dmi_first_match(victus_s_thermal_profile_boards); ++ id = dmi_first_match(hp_wmi_feature_boards); + if (id) { ++ active_board_params = id->driver_data; ++ params = hp_wmi_thermal_profile(); ++ if (!params) ++ return; ++ + /* + * Marking this boolean is required to ensure that + * is_victus_s_thermal_profile() behaves like a valid + * wrapper. + */ + is_victus_s_board = true; +- active_thermal_profile_params = id->driver_data; +- if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { ++ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) { + pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n", + dmi_get_system_info(DMI_BOARD_NAME)); + } +@@ -2749,10 +2988,10 @@ static int __init hp_wmi_init(void) + } + + /* +- * Setup active board's thermal profile parameters before +- * starting platform driver probe. ++ * Setup active board feature data before starting platform ++ * driver probe. + */ +- setup_active_thermal_profile_params(); ++ setup_active_board_params(); + err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup); + if (err) + goto err_unregister_device; +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,42 +88,95 @@ 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; + 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]; +@@ -154,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) +@@ -211,72 +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; +- +- 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) { +- led_classdev_notify_brightness_hw_changed( +- &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent)); +- } 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); +@@ -285,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; + } +@@ -296,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, +@@ -361,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, +@@ -403,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, +@@ -453,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; + } + +@@ -471,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); +@@ -485,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; + +@@ -563,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) +@@ -579,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) +@@ -629,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) + { +@@ -860,15 +1036,23 @@ static int acpi_probe(struct platform_device *pdev) + if (year >= 2019) + battery_limit_use_wmbb = 1; + ++ /* LEDs are optional */ ++ ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); ++ if (ret < 0) ++ kbd_backlight_available = false; ++ else ++ kbd_backlight_available = true; ++ ++ devm_led_classdev_register(&pdev->dev, &tpad_led); ++ ++ 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; + +- /* LEDs are optional */ +- led_classdev_register(&pf_device->dev, &kbd_backlight); +- led_classdev_register(&pf_device->dev, &tpad_led); +- +- wmi_input_setup(); + battery_hook_register(&battery_hook); + + return 0; +@@ -884,11 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) + { + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + +- led_classdev_unregister(&tpad_led); +- led_classdev_unregister(&kbd_backlight); +- + 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/0800-platform-updates.patch.sig b/pkgbuilds/linux-omarchy/0800-platform-updates.patch.sig new file mode 100644 index 0000000..1afc8bb Binary files /dev/null and b/pkgbuilds/linux-omarchy/0800-platform-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch b/pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch new file mode 100644 index 0000000..029ebd2 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0801-amd-pmf-util-unbind-use-after-free.patch.sig b/pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch.sig new file mode 100644 index 0000000..6bc45ff Binary files /dev/null and b/pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch b/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch new file mode 100644 index 0000000..d4ab07f --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0850-futex-wait-multiple.patch.sig b/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch.sig new file mode 100644 index 0000000..c9e1f4e Binary files /dev/null and b/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch b/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch new file mode 100644 index 0000000..671934a --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0851-futex-wait-multiple-fixes.patch.sig b/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch.sig new file mode 100644 index 0000000..e3fb191 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch b/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch new file mode 100644 index 0000000..b0d6ba3 --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/0852-futex-wait-multiple-abi-fixes.patch.sig b/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch.sig new file mode 100644 index 0000000..d3db2d8 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch b/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch new file mode 100644 index 0000000..f340536 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch @@ -0,0 +1,132 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -54,13 +54,14 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea + write_unlock(&shrinker->lock); + } + +-static s64 __xe_shrinker_walk(struct xe_device *xe, ++static int __xe_shrinker_walk(struct xe_device *xe, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +- unsigned long to_scan, unsigned long *scanned) ++ unsigned long to_scan, unsigned long *scanned, ++ unsigned long *freed) + { + unsigned int mem_type; +- s64 freed = 0, lret; ++ s64 lret; + + for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { + struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type); +@@ -82,7 +83,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + if (lret < 0) + return lret; + +- freed += lret; ++ *freed += lret; + if (*scanned >= to_scan) + break; + } +@@ -90,7 +91,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + xe_assert(xe, !IS_ERR(ttm_bo)); + } + +- return freed; ++ return 0; + } + + /* +@@ -99,40 +100,35 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, + * add writeback. This avoids stalls and explicit writebacks with light or + * moderate memory pressure. + */ +-static s64 xe_shrinker_walk(struct xe_device *xe, ++static int xe_shrinker_walk(struct xe_device *xe, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +- unsigned long to_scan, unsigned long *scanned) ++ unsigned long to_scan, unsigned long *scanned, ++ unsigned long *freed) + { + bool no_wait_gpu = true; + struct xe_bo_shrink_flags save_flags = flags; +- s64 lret, freed; ++ int ret; + + swap(no_wait_gpu, ctx->no_wait_gpu); + save_flags.writeback = false; +- lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); ++ ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); + swap(no_wait_gpu, ctx->no_wait_gpu); +- if (lret < 0 || *scanned >= to_scan) +- return lret; ++ if (ret || *scanned >= to_scan) ++ return ret; + +- freed = lret; + if (!ctx->no_wait_gpu) { +- lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); +- if (lret < 0) +- return lret; +- freed += lret; +- if (*scanned >= to_scan) +- return freed; ++ ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, ++ freed); ++ if (ret || *scanned >= to_scan) ++ return ret; + } + +- if (flags.writeback) { +- lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned); +- if (lret < 0) +- return lret; +- freed += lret; +- } ++ if (flags.writeback) ++ ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, ++ freed); + +- return freed; ++ return ret; + } + + static unsigned long +@@ -214,7 +210,6 @@ 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); +- s64 lret; + + nr_to_scan = sc->nr_to_scan; + +@@ -225,12 +220,9 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + /* Might need runtime PM. Try to wake early if it looks like it. */ + runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); + +- if (purgeable && nr_scanned < nr_to_scan) { +- lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, +- nr_to_scan, &nr_scanned); +- if (lret >= 0) +- freed += lret; +- } ++ if (purgeable && nr_scanned < nr_to_scan) ++ xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; + if (nr_scanned >= nr_to_scan || !can_backup) +@@ -242,10 +234,8 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + shrink_flags.purge = false; + +- lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, +- nr_to_scan, &nr_scanned); +- if (lret >= 0) +- freed += lret; ++ xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; + out: diff --git a/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch.sig b/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch.sig new file mode 100644 index 0000000..bbe1ce3 Binary files /dev/null and b/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch b/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch new file mode 100644 index 0000000..29ccecf --- /dev/null +++ b/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch @@ -0,0 +1,149 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -54,13 +54,33 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea + write_unlock(&shrinker->lock); + } + +-static int __xe_shrinker_walk(struct xe_device *xe, ++static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) ++{ ++ struct xe_device *xe = shrinker->xe; ++ ++ if (xe_pm_runtime_get_if_active(xe)) ++ return true; ++ ++ if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { ++ xe_pm_runtime_get(xe); ++ return true; ++ } ++ ++ queue_work(xe->unordered_wq, &shrinker->pm_worker); ++ ++ return false; ++} ++ ++static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) + { ++ struct xe_device *xe = shrinker->xe; + unsigned int mem_type; ++ bool rpm = false; ++ int ret = 0; + s64 lret; + + for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { +@@ -75,23 +95,36 @@ static int __xe_shrinker_walk(struct xe_device *xe, + if (!man || !man->use_tt) + continue; + ++ if (mem_type != XE_PL_SYSTEM && !rpm && ++ xe_device_is_l2_flush_optimized(xe)) { ++ if (!__xe_shrinker_runtime_pm_get(shrinker)) ++ break; ++ rpm = true; ++ } ++ + ttm_bo_lru_for_each_reserved_guarded(&curs, man, &arg, ttm_bo) { + if (!ttm_bo_shrink_suitable(ttm_bo, ctx)) + continue; + + lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned); +- if (lret < 0) +- return lret; ++ if (lret < 0) { ++ ret = lret; ++ goto out; ++ } + + *freed += lret; + if (*scanned >= to_scan) +- break; ++ goto out; + } + /* Trylocks should never error, just fail. */ + xe_assert(xe, !IS_ERR(ttm_bo)); + } + +- return 0; ++out: ++ if (rpm) ++ xe_pm_runtime_put(xe); ++ ++ return ret; + } + + /* +@@ -100,7 +133,7 @@ static int __xe_shrinker_walk(struct xe_device *xe, + * add writeback. This avoids stalls and explicit writebacks with light or + * moderate memory pressure. + */ +-static int xe_shrinker_walk(struct xe_device *xe, ++static int xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, + unsigned long to_scan, unsigned long *scanned, +@@ -112,20 +145,21 @@ static int xe_shrinker_walk(struct xe_device *xe, + + swap(no_wait_gpu, ctx->no_wait_gpu); + save_flags.writeback = false; +- ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); ++ ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, ++ freed); + swap(no_wait_gpu, ctx->no_wait_gpu); + if (ret || *scanned >= to_scan) + return ret; + + if (!ctx->no_wait_gpu) { +- ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, ++ ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, + freed); + if (ret || *scanned >= to_scan) + return ret; + } + + if (flags.writeback) +- ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, ++ ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned, + freed); + + return ret; +@@ -176,16 +210,7 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, + return false; + } + +- if (!xe_pm_runtime_get_if_active(xe)) { +- if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { +- xe_pm_runtime_get(xe); +- return true; +- } +- queue_work(xe->unordered_wq, &shrinker->pm_worker); +- return false; +- } +- +- return true; ++ return __xe_shrinker_runtime_pm_get(shrinker); + } + + static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +@@ -221,7 +246,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); + + if (purgeable && nr_scanned < nr_to_scan) +- xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ xe_shrinker_walk(shrinker, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; +@@ -234,7 +259,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + + shrink_flags.purge = false; + +- xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, ++ xe_shrinker_walk(shrinker, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); + + sc->nr_scanned = nr_scanned; diff --git a/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch.sig b/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch.sig new file mode 100644 index 0000000..0707c41 Binary files /dev/null and b/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch b/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch new file mode 100644 index 0000000..6af456f --- /dev/null +++ b/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch @@ -0,0 +1,39 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -71,6 +71,12 @@ static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) + return false; + } + ++static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) ++{ ++ if (runtime_pm) ++ xe_pm_runtime_put(shrinker->xe); ++} ++ + static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + struct ttm_operation_ctx *ctx, + const struct xe_bo_shrink_flags flags, +@@ -121,8 +127,7 @@ static int __xe_shrinker_walk(struct xe_shrinker *shrinker, + } + + out: +- if (rpm) +- xe_pm_runtime_put(xe); ++ xe_shrinker_runtime_pm_put(shrinker, rpm); + + return ret; + } +@@ -213,12 +218,6 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, + return __xe_shrinker_runtime_pm_get(shrinker); + } + +-static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +-{ +- if (runtime_pm) +- xe_pm_runtime_put(shrinker->xe); +-} +- + static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc) + { + struct xe_shrinker *shrinker = to_xe_shrinker(shrink); diff --git a/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch.sig b/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch.sig new file mode 100644 index 0000000..ddd9329 Binary files /dev/null and b/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch b/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch new file mode 100644 index 0000000..0a31aa0 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch @@ -0,0 +1,381 @@ +diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h +--- c/include/linux/mmzone.h ++++ i/include/linux/mmzone.h +@@ -1465,6 +1465,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 +@@ -1529,6 +1562,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 c/include/linux/shrinker.h i/include/linux/shrinker.h +--- c/include/linux/shrinker.h ++++ i/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 c/mm/internal.h i/mm/internal.h +--- c/mm/internal.h ++++ i/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 c/mm/shrinker.c i/mm/shrinker.c +--- c/mm/shrinker.c ++++ i/mm/shrinker.c +@@ -474,7 +474,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; +@@ -536,6 +536,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); +@@ -595,7 +596,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; + } +@@ -607,6 +609,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. + * +@@ -622,7 +625,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; +@@ -635,7 +638,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. +@@ -664,6 +668,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 c/mm/vmscan.c i/mm/vmscan.c +--- c/mm/vmscan.c ++++ i/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/8204-mm-opportunistic-compaction.patch.sig b/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch.sig new file mode 100644 index 0000000..1b113b3 Binary files /dev/null and b/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch b/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch new file mode 100644 index 0000000..f579b27 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch @@ -0,0 +1,236 @@ +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1680,7 +1680,7 @@ enum kswapd_clear_hopeless_reason { + }; + + void wakeup_kswapd(struct zone *zone, gfp_t gfp_mask, int order, +- enum zone_type highest_zoneidx); ++ bool opportunistic, enum zone_type highest_zoneidx); + void kswapd_try_clear_hopeless(struct pglist_data *pgdat, + unsigned int order, int highest_zoneidx); + void kswapd_clear_hopeless(pg_data_t *pgdat, enum kswapd_clear_hopeless_reason reason); +diff --git a/include/linux/swap.h b/include/linux/swap.h +--- a/include/linux/swap.h ++++ b/include/linux/swap.h +@@ -348,7 +348,8 @@ extern void swap_setup(void); + /* linux/mm/vmscan.c */ + extern unsigned long zone_reclaimable_pages(struct zone *zone); + extern unsigned long try_to_free_pages(struct zonelist *zonelist, int order, +- gfp_t gfp_mask, nodemask_t *mask); ++ int alloc_order, gfp_t gfp_mask, ++ nodemask_t *mask); + unsigned long lruvec_lru_size(struct lruvec *lruvec, enum lru_list lru, int zone_idx); + + #define MEMCG_RECLAIM_MAY_SWAP (1 << 1) +diff --git a/mm/internal.h b/mm/internal.h +--- a/mm/internal.h ++++ b/mm/internal.h +@@ -1765,6 +1765,28 @@ void __meminit __init_single_page(struct page *page, unsigned long pfn, + unsigned long zone, int nid); + void __meminit __init_page_from_nid(unsigned long pfn, int nid); + ++/* ++ * Is this allocation eligible for the "opportunistic compaction" treatment ++ * in kswapd and the 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. ++ * ++ * @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. ++ */ ++static inline bool gfp_opportunistic_compaction(gfp_t gfp_flags, int order) ++{ ++ if (!order || (gfp_flags & __GFP_NOFAIL)) ++ return false; ++ ++ return !!(gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)); ++} ++ + /* shrinker related functions */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + int priority, bool opportunistic_compaction); +diff --git a/mm/migrate.c b/mm/migrate.c +--- a/mm/migrate.c ++++ b/mm/migrate.c +@@ -2727,7 +2727,7 @@ int migrate_misplaced_folio_prepare(struct folio *folio, + return -EAGAIN; + + wakeup_kswapd(pgdat->node_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 +@@ -3422,7 +3422,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); +@@ -4441,6 +4441,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; +@@ -4453,7 +4454,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); +@@ -4480,7 +4481,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; + +@@ -4512,19 +4513,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/8205-mm-hint-uses-allocation-order.patch.sig b/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch.sig new file mode 100644 index 0000000..7c3ef42 Binary files /dev/null and b/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch b/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch new file mode 100644 index 0000000..92b39ef --- /dev/null +++ b/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch @@ -0,0 +1,232 @@ +diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h +--- c/include/linux/mmzone.h ++++ i/include/linux/mmzone.h +@@ -1498,6 +1498,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 +@@ -1561,14 +1587,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 c/mm/mm_init.c i/mm/mm_init.c +--- c/mm/mm_init.c ++++ i/mm/mm_init.c +@@ -1553,7 +1553,8 @@ void __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 c/mm/vmscan.c i/mm/vmscan.c +--- c/mm/vmscan.c ++++ i/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/8206-mm-carry-order-and-hint-in-one-word.patch.sig b/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch.sig new file mode 100644 index 0000000..de65251 Binary files /dev/null and b/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch b/pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch new file mode 100644 index 0000000..bfa7d1e --- /dev/null +++ b/pkgbuilds/linux-omarchy/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/8207-mm-classify-huge-page-allocations-as-failable.patch.sig b/pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch.sig new file mode 100644 index 0000000..e60df1b Binary files /dev/null and b/pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch b/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch new file mode 100644 index 0000000..8de9983 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch @@ -0,0 +1,21 @@ +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -4476,6 +4476,17 @@ static unsigned long deferred_split_count(struct shrinker *shrink, + { + unsigned long count; + ++ /* ++ * Splitting a partially mapped folio during an opportunistic ++ * high-order pass frees its unmapped tail pages but shatters the ++ * folio, so the pass works against the contiguous block it is ++ * reclaiming for. Report nothing rather than SHRINK_EMPTY: for a ++ * memcg aware shrinker SHRINK_EMPTY clears the memcg shrinker bit, ++ * which would also skip the next normal pass. ++ */ ++ if (sc->opportunistic_compaction) ++ return 0; ++ + count = list_lru_shrink_count(&deferred_split_lru, sc); + return count ?: SHRINK_EMPTY; + } diff --git a/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch.sig b/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch.sig new file mode 100644 index 0000000..88c17d4 Binary files /dev/null and b/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch b/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch new file mode 100644 index 0000000..0552be0 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch @@ -0,0 +1,39 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -174,10 +174,17 @@ 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; ++ unsigned long num_pages = 0; + bool can_backup = !!(sc->gfp_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/8209-xe-shrinker-use-opportunistic-hint.patch.sig b/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch.sig new file mode 100644 index 0000000..7928b4e Binary files /dev/null and b/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch b/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch new file mode 100644 index 0000000..9daf161 --- /dev/null +++ b/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch @@ -0,0 +1,75 @@ +diff --git c/drivers/gpu/drm/xe/xe_shrinker.c i/drivers/gpu/drm/xe/xe_shrinker.c +--- c/drivers/gpu/drm/xe/xe_shrinker.c ++++ i/drivers/gpu/drm/xe/xe_shrinker.c +@@ -170,27 +170,28 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + ++static inline bool xe_shrinker_can_backup(const struct shrink_control *sc) ++{ ++ return (sc->gfp_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/8210-xe-shrinker-single-backup-decision.patch.sig b/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch.sig new file mode 100644 index 0000000..a823434 Binary files /dev/null and b/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch b/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch new file mode 100644 index 0000000..ff35b72 --- /dev/null +++ b/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch @@ -0,0 +1,12 @@ +diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile +--- a/tools/bpf/bpftool/Makefile ++++ b/tools/bpf/bpftool/Makefile +@@ -99,7 +99,7 @@ endif + HOST_LDFLAGS := $(LDFLAGS) + + # Remove warnings for libbpf bootstrap build +-LIBBPF_BOOTSTRAP_CFLAGS := $(filter-out -W -Wall -Wextra -Wformat -Wformat-signedness,$(HOST_CFLAGS)) ++LIBBPF_BOOTSTRAP_CFLAGS := $(filter-out -W -Wall -Wextra -Wformat%,$(HOST_CFLAGS)) + + INSTALL ?= install + RM ?= rm -f diff --git a/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch.sig b/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch.sig new file mode 100644 index 0000000..303bf7f Binary files /dev/null and b/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/PKGBUILD b/pkgbuilds/linux-omarchy/PKGBUILD new file mode 100644 index 0000000..660c9e2 --- /dev/null +++ b/pkgbuilds/linux-omarchy/PKGBUILD @@ -0,0 +1,726 @@ +# Maintainer: The Omarchy Authors +# Adopted from the Arch Linux linux package. + +pkgbase=linux-omarchy +pkgver=7.2.5 +pkgrel=4 +pkgdesc='Omarchy Linux' +url='https://omarchy.org' +arch=( + x86_64 +) +license=(GPL-2.0-only) +makedepends=( + bc + binutils + cpio + gettext + glibc + libelf + libgcc + openssl + pahole + perl + python + rust + rust-bindgen + rust-src + tar + xxhash + xz + zlib + zstd +) +options=( + !debug + !strip +) +_major=${pkgver%%.*} +if [[ $pkgver == *rc* ]]; then + _base=${pkgver%rc*} + _prev=${_base%.*}.$(( ${_base##*.} - 1 )) + _srcname=linux-${_prev%.0} + _rcpatch=patch-${pkgver//rc/-rc} +else + _srcname=linux-$pkgver +fi +source=( + https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/${_srcname}.tar.{xz,sign} + ${_rcpatch:+https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/stable-review/${_rcpatch}.xz} + ${_rcpatch:+https://cdn.kernel.org/pub/linux/kernel/v${_major}.x/stable-review/${_rcpatch}.sign} + 0010-archlinux-base.patch{,.sig} + 0120-tlbpull.patch{,.sig} + 0121-smp-preempt.patch{,.sig} + 0130-sched-detach-tasks.patch{,.sig} + 0131-sched-avg-idle.patch{,.sig} + 0140-sched-always-inline.patch{,.sig} + 0141-sched-urgent-fixes.patch{,.sig} + 0142-sched-itmt-no-debugfs-dependency.patch{,.sig} + 0143-sched-hybrid-cluster-balancing.patch{,.sig} + 0144-sched-nohz-idle-core.patch{,.sig} + 0145-sched-eevdf-tunables.patch{,.sig} + 0200-idle.patch{,.sig} + 0210-pstate.patch{,.sig} + 0211-amd-pstate-fixes.patch{,.sig} + 0212-amd-pstate-epp-cache.patch{,.sig} + 0250-zsmalloc.patch{,.sig} + 0260-mglru-exec-protect.patch{,.sig} + 0270-ksm-rmap-walk.patch{,.sig} + 0280-mm-updates.patch{,.sig} + 0290-zstd-bmi2-fallback-aliases.patch{,.sig} + 0291-zstd-bmi2-cpu-feature-dispatch.patch{,.sig} + 0292-crypto-zstd-defer-cstream-init.patch{,.sig} + 0293-crypto-zstd-defer-dstream-init.patch{,.sig} + 0295-af-alg-restrict.patch{,.sig} + 0296-x86-mm-pmd-modify-keep-dirty-bit.patch{,.sig} + 0300-btrfs.patch{,.sig} + 0301-btrfs-fixes.patch{,.sig} + 0302-btrfs-zstd-decompress-direct-to-page.patch{,.sig} + 0310-fuse-eof-zeroing.patch{,.sig} + 0311-fuse-perf.patch{,.sig} + 0312-fuse-writethrough-uptodate.patch{,.sig} + 0313-fuse-background-wakeup.patch{,.sig} + 0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch{,.sig} + 0360-gpu-mem-cgroup.patch{,.sig} + 0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch{,.sig} + 0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch{,.sig} + 0402-psr2-early-transport-panels.patch{,.sig} + 0411-drm-xe-display-no-stolen-framebuffers.patch{,.sig} + 0420-safe-window.patch{,.sig} + 0430-fbc.patch{,.sig} + 0440-xe3-peak-bandwidth.patch{,.sig} + 0450-amd-hdmi-vrr-allm.patch{,.sig} + 0451-amd-vtem-tmds-links.patch{,.sig} + 0452-amd-hdmi-frl-default.patch{,.sig} + 0460-vesa-displayid-dsc-bpp.patch{,.sig} + 0461-vesa-dsc-passthru-mode-match-fix.patch{,.sig} + 0472-amdgpu-userq-post-reset-error.patch{,.sig} + 0473-i915-ptl-cdclk-sanitize.patch{,.sig} + 0474-amd-display-oled-vesa-backlight.patch{,.sig} + 0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch{,.sig} + 0510-sound-updates.patch{,.sig} + 0511-sound-updates-fixes.patch{,.sig} + 0512-sound-fixes.patch{,.sig} + 0513-xps13-sof-quirk.patch{,.sig} + 0514-rt766-stream-config-type.patch{,.sig} + 0516-hda-realtek-rog-strix-g733zw-speakers.patch{,.sig} + 0517-asoc-amd-yc-acer-aspire-a314-23p.patch{,.sig} + 0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch{,.sig} + 0541-cvs-nova-lake-acpi-id.patch{,.sig} + 0542-media-cvs-wake-irq-without-claiming-gpio.patch{,.sig} + 0560-input.patch{,.sig} + 0565-i2c-asue140d-touchpad-100khz.patch{,.sig} + 0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch{,.sig} + 0600-usb4stream-fixes.patch{,.sig} + 0601-usb4stream-busy-poll.patch{,.sig} + 0610-typec-cable-altmode-check.patch{,.sig} + 0620-usb-string-sanitize.patch{,.sig} + 0650-wireguard-tstamp-type.patch{,.sig} + 0660-btusb-mediatek-mt7922-13d3-3625.patch{,.sig} + 0661-rtw89-command-offload-source.patch{,.sig} + 0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch{,.sig} + 0700-pci-target-speed-quirk.patch{,.sig} + 0750-applesmc-cache-race.patch{,.sig} + 0751-applesmc-key-backlight-workqueue-leak.patch{,.sig} + 0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch{,.sig} + 0800-platform-updates.patch{,.sig} + 0801-amd-pmf-util-unbind-use-after-free.patch{,.sig} + 0850-futex-wait-multiple.patch{,.sig} + 0851-futex-wait-multiple-fixes.patch{,.sig} + 0852-futex-wait-multiple-abi-fixes.patch{,.sig} + 8201-xe-shrinker-return-freed-page-count.patch{,.sig} + 8202-xe-shrinker-runtime-pm-for-non-system-memory.patch{,.sig} + 8203-xe-shrinker-release-through-the-put-helper.patch{,.sig} + 8204-mm-opportunistic-compaction.patch{,.sig} + 8205-mm-hint-uses-allocation-order.patch{,.sig} + 8206-mm-carry-order-and-hint-in-one-word.patch{,.sig} + 8207-mm-classify-huge-page-allocations-as-failable.patch{,.sig} + 8208-mm-thp-deferred-split-uses-hint.patch{,.sig} + 8209-xe-shrinker-use-opportunistic-hint.patch{,.sig} + 8210-xe-shrinker-single-backup-decision.patch{,.sig} + 9999-bpftool-strip-wformat-bootstrap.patch{,.sig} +) +source_x86_64=(config.x86_64) +validpgpkeys=( + ABAF11C65A2970B130ABE3C479BE3E4300411886 # Linus Torvalds + 647F28654894E3BD457199BE38DBBDC86092693E # Greg Kroah-Hartman + 12D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7 # Krzysztof Wilczynski +) +b2sums=('48551bee71cd02815136fb8abe7da4464c2e17c89ef35cb0c0530c8b969fe12127ca97ab6c656ea8e5b29f2c4a8fe4cc143a626f4cca6b972a8105991e4c6905' + 'SKIP' + '6d92fb81077232b8cd1ae500b3aabc71434792e750ea6126dc095e50d4278d3e42b3cfdc3c8bc693ea14b109f6adfa3a1ed095187fc5501962fbe8f049f867bc' + 'SKIP' + 'f6c00ac2400580dffe3605d2693809396e18185f0c59b21b86f1f22a1f8d6529c51e8cc2474bc40f74a96631f415da51521a8b35d5b096e9b9d164c2755fb091' + 'SKIP' + '6aa6ea3690f3e4e145818650537cd110d33720d662af0b3660d95e15a3f91e48c993e31117c0a2fde852f55a77a83e3a50a1a95c0df78a33ae75d43d46a9fe15' + 'SKIP' + 'f664077b274be12da33f0145ebea8fafda3f3e3bf96f449c31b9a56768d39b763c246394ee4ed144f7f2d1916c32085fa59dc18736bb4a28c94568e3e1919d56' + 'SKIP' + '7fb673924d268c0e3c416c7aa6200e96d62d156eb397832de12668b8a90d6b9c7311f1958e0f2a6d93de8e82fa3166082106c2bcbaaf42bb11b13bb83d6f732d' + 'SKIP' + '42597d4f8377d6f4c58c26c415b0621db16a37da89bc1eb079750eef1c3f7bf04c9a2ac6f334d6a3c4c439827df4aa1b92d53c15f6e3857f79d8be7eb79ea7bd' + 'SKIP' + '35075dae2576cb2b8f94df048bbd4fc6b667fc176310306f8939cf9263a50a9a558102af99c90746fa7b17d98c6a321c0cc36b2bdffab5fa714e3e1256be889b' + 'SKIP' + '3434a5bbcd2bd8b4e2013400bf4d771ad5c8295ec74f67ff7acaf917954a01d25931679a71ddb8995fd7703b0b9904db4e6fe5f384d3705cf4d7b80dbf5dba55' + 'SKIP' + 'bcf83ace482aa07e64ef6cdc1d6ae9b8f52ef79c8d15421e2d8525b1fcd3bd555be49fee48a87c2daa103a5f6a564046568722f03bf768f73dbd280acf59376d' + 'SKIP' + '38d2d63ca4eee7ce83d22e60ebeb9d7f41a019cf910a8a7d151e4a1eccb14478a0054e72ae6494c89e2d66effead44e59534899fd861fe05a68460ae2d2cd32a' + 'SKIP' + '64a2e489285c40df691f822dbca67e2b2f21446f300465f01bb589380173f6da182eda71d09a840e7ea3e14acae940d3cde66fc3b476ef6417c6e8479841cc56' + 'SKIP' + 'd1007ee452cdf92967ec7055f1f39a3007a69842040e12f5063a1d2f24fd6d1eeaff2357abf96d0e330541a4781f93fecbcb116a4c73371913582eb72b8f9466' + 'SKIP' + '0d3309140e496ad552d1a15d4fbf04b5ad03877bc73ae689f57554e91b7458578f2c88b6bbd37dd9fdf822a8d48c2cd6f961ec735ae5887f96fa51d908d203ee' + 'SKIP' + '13beae6f8d23a1f075410f50c09bc94f24f505571af1feb3cee58f732c0d649a9ce5939bc11642c4f3c8dfbd967939b2a0a205220abf06f10d559c4c43a946a3' + 'SKIP' + '2f24f88d8f960148046c36222fdaaadfed2ca88c60240fa1d111430898192ca45aeaa93a3afa24ad02b11c4dc8c3904a92f17c0284396f2f43352ad934a35056' + 'SKIP' + '9809e258eb684b80d508fa2f2fa270683b62bd1412ed05a970114aaa7d84c37c0beaea51f9ff8c6ee1bc7318c4aee3725e9a1131fefb51ba700801de6ac414c5' + 'SKIP' + 'f115e8c80d723f7c24e6b1638b801cd664bef20acb1d2f6e6f5612744b916278693011782228cd3f4d0518654b8d53309bc5b06bbe8ed722fab9995f369bf548' + 'SKIP' + '31abdd15ae3f9709d6912c90319a0b62d8380e1ae1169575c0e8e419f8ce264a7cf7c72558a85719cd5a1cae7341007adb3e3936268999ebd5215c2ed288f22c' + 'SKIP' + '4efb9ab5258aee01b07bc322b160d2f6e3571ff82c4f3e317cab152a919c67522629d56fb79d517da034373cd6924bc3a20c98073197c93590feb1a34e902358' + 'SKIP' + '55951c59286df43f069302b0b1036424812f80b45c5ea13219fce1bfc3e831731432c216288d722e44493c736cbf89a3e44801b5a4f1f242c17b5a89b236a5ef' + 'SKIP' + '737db39c2f7f46550de4d6d80659bd6c1fc2bf1ca42bd7bd6fc345fc90af995acd056051e12175ac6c957333629a017051d76d5359c871158c93f0a294fb8de5' + 'SKIP' + '4d0646c8ad139add6abb62d75308d2d7b5b07afbc74a0f0297ffc2148f0db38abaea16b048704187da49fe3ddee8370b10a7a41d6e62b665650cd79e8c392b4e' + 'SKIP' + 'bd7bce1bf0ee24ce201fe0e5f0b8a6c829a84d224800547cf97a893326381e2e6eaed9c70529a6864b0c338276b3a307b14e26fba221e62263b90c02eb5e5f34' + 'SKIP' + 'ad2cfdc3d8de2193110325395e11015577f26ef1081307bb236ed73fbd109822dd44ea026a57c2e17233e1fb7982e78aa986b71c043f42887e6c5d7f51b84ea9' + 'SKIP' + '19c3dd32bbe3ed7caec86e90a6af39b07eb99d5a36515cf061bb6979e5a403ca98a0e00e584a9f746330d663e5fc0868ba7fc98ab2df264bc863bdf22ef3dd5e' + 'SKIP' + 'ed2ee4849b715d80ba103fe090669062fc08c6864c64e8f8291f7f21092f0184ca5528bdd3a9318f077ef00cbe8cf28bacd1e7a9cb9f9ae1047becd930a0c42e' + 'SKIP' + 'f09e80ed25fdda9b2c79bd5ea3701985596d478b0b4e6948a1ea8fb789ef944e71640ae200442afcc23a9f0e07b34d83365e223b620baf776bbd864977e633a0' + 'SKIP' + 'a51e5f2a5e37c350fb4d715ece644c565a508970c3aeb1d28cb6cd03a0c7316a1935e028b64f20a03818926c611b50ae0ffd77d04c676d604d303beed0f96e1f' + 'SKIP' + '25dcd2c4a4287f26c01419b7d6430629d2827b6efff473361c68d6ec44e94e6d2bb8ab8215dbae5da25781a668f571cfc28b60c5693eb4ed629bf80fe2f68ccf' + 'SKIP' + '8d6eeb2330a3ca7c522cef77bf7336fac9444a0884d5d101c933abd3a31a3711529a31f9a181d4795148ecbcc24425750587314ce28e44c9ab2fd2de8365e853' + 'SKIP' + 'fd487cb02212ffdae5d8beb7d895819377686085b95658dcf597a534b017adcb65dfdf06e0276e7322a46dc28118803b05c246c22e7d3c361f1778ed9e12a3c6' + 'SKIP' + 'e18e5086c531a37837f6e7df72ce10a38289b02de4cea376772d45cccb5aabc628fe4f3f9607f646903a7cf59b02692703e6b26800604cf958136987df0b4d5d' + 'SKIP' + '1b340a502dcf5a1680caf5320b3aa72aa4317e4cf1d877c1f6425e281a5668d5f092417e1f282371c0a211344f5c187fb3bdaf3f97c7e122951edf7e4c08ca58' + 'SKIP' + '1124c5c3fd5104c886c841ead1ae9afb42683388b640b1a86523dad9919c6bd59a1f78b07ee5a213e0dca77c636a29676af0592d4ed1735ea35f282f6f23e46f' + 'SKIP' + '505bf01a9d6b0926b557b673bb686d1a4ff73ee70539991e52272fbf58aad3ea25cf5ffc5c2d094a2d5e78b18a1a134f2ab530f595073a2d3eba9ff2c1418e1e' + 'SKIP' + 'b33b46c37f12f7650a67c3731b87ff84add1ac8623cc96b52223a1ef9946bcaa990ed7c7036674e228335f57ec9ed421ea4d6c1dd72d19b516215d9ac2c7c3ee' + 'SKIP' + 'b4bf4c4c0672bad91d7f8d309e741104918dd8ee8688a00d600379eba5828d1b8157953377c73d0b58050b4ff888c43013ede4e9c959be8d9c028a6143bf58ed' + 'SKIP' + 'e403bdc300284c4f0f4302e501b8ccfe5e9af04e73a6070eb6615322f5f40ed6edad53af2ed278741f61a4e3811b92faf1af02f113dbcc2a19bde335a55930d7' + 'SKIP' + '5de29d0eb5264e24ab9228bea2ea4b549b3fe37ea67c045df9f83064f11c2f557b9641e772d28db59f8d258418c77435b66ff471c3695043f0f8275d5e7b1685' + 'SKIP' + '3c2cd960103ad97ce35679f906b3c9be79a9744959c5dfd0ea338d8075b698790934694b2f71fde026b164c5246cba71ffe3cd5dfc1464da338136fbbaea2f1c' + 'SKIP' + '2b33b11e48d8dd8d6743f26170fb99eae38541e3665e2448d91cedf729cca7c0278bfba1078ecbe88201eac4407ec5bd0292a97dbab9cfc67e42646712befc1f' + 'SKIP' + '71934436987ecf6914e5c559445c2b7ee92a31c467552342094c93a59d32563aa9930e710556cc1e271eeb352705a9df9b3702482f87d045d132233cee4ab48b' + 'SKIP' + '9fb1318a3a181644c67b2adcac0cb04d1b88dd0e632751967239d4e8d4698f193795d00194b0f68505964c84572b67168dcbed02ad5bb7abd41e406a62111489' + 'SKIP' + '353b7e89a7044b7426db730d5b900eb347907aa0c20e307d2317925d6021a969a343649ede7a32e698dad60dd4472eb197c9578fb0e36fc354a60266fd680cd7' + 'SKIP' + '803223224f22aca25dfe34d73e2bd49438f0cfa83b71baa545fa6fe93ee1a631b6b62c7324dc1c0d2d2ded94c36185a83af39b4f4a3362f554a1943ec2e2ec3e' + 'SKIP' + 'b0a7ab192066c472f3ebc39bc9afc9fef0528ff7c98bf67c5b9cb679cc6d93eab3d25e91d8d74c9c52f72803949a3ade7940e677b9a3114605b966df94da93bb' + 'SKIP' + '824089f9f45e2ff9e788d874bb9294b9208b4486fd22c56cb59d4d88cb996ff5a81ac22f8ff136ddffa1e40330f02e53ab759ffea7e4535c11d4f8195069966b' + 'SKIP' + '696309582c56fad530a0bf2be12bb744dedac11ed69642b14eade0a26baf1ce52b36074b89b963b40c9c6fbc4857cdc71b2a6c6d64afcbb2df716bbb57579986' + 'SKIP' + '14212fca051d3e9a85f75616ff7f95bbd119ef8e5400dafd4447f70db2c4e9c03d971ec4fdfb4db2319d2613062d74f54dfd5d0a100ab0fa27ac6c05e16bd063' + 'SKIP' + '7228e249d25a99f5106040b8ddb5705979b3a5af21ca60d0d43a9ecaf6eaccef6bde8ea69c896cffbf2b5963ec4f68987e69b184a8885c7f4c98d2b05fd59fbb' + 'SKIP' + 'a54ac1c6d43fe88dee08884ed2e5bad0a0b6d11f34a46dc9801e675860afb7641b57384248a144a621e66a73541d73da11f71671116f1cc7f21534526411439a' + 'SKIP' + '2e486cabad0d45aeb760d031c7946e9887c710b29dbe9c16e5f4bae6a230031dba4d817f1ee25a0bbca849d1c71b2f84052ffb73c4a6c4350a6ea5ee83c5a40e' + 'SKIP' + '23e2a5def526fa886d1f1b05607bfe6b1ecbb2d6009d023145adff9eadc3135f9078faca6638966b586e81e4ab9d48d23b5758504d5a043d5f46e7a701d185fa' + 'SKIP' + '5d82f32f7c04b084530722b9848254aac289c2a754a10e16821083a871ec14065dd9ee83e72b8e9e8ba2915f3fbaf17b0874df724002eb08a5f4f84d1b6aecd8' + 'SKIP' + '4928cc008dc91fddb89b154b89b1ce2bbff14d8186c0ec1f81971fb9d3b622475e93e42015916699c3442e2268680779f545d183112b57dedae96fc48a75844c' + 'SKIP' + 'f93494f5f3196cc35d017ff9719d146bf3e5e7bfe32a7f5e75d96468ce30ef367428ba83b1c99b665064d3edaec84cf3bf18211d8f7b4f118b3bdee495eb8ef9' + 'SKIP' + 'eb79554ef0d014b94bc72d4d559c2cc5419df96486acb3b6a09d5e8e9dc48db626ce0f90338c67fe873b2cc3c45c3f308c8080532b60f74a07c9e39e01d34197' + 'SKIP' + 'f657143cdd9c937d78315f4617392e60f7c46f5cec882e060f740f77a27dd4c045878cb66e568a6d9ae74e9c706ffb3052817db6f327c87b85ff2f46eea678e2' + 'SKIP' + '0acbd87a09a62af860d14f342d61c82c29ec1711038d4abf4ae473df187fbda6aa49386d88928c5dc4c3379e097478b1d40d9ebe9360e64d02e59161164b2a3f' + 'SKIP' + 'bb06066e90132f0172333d46be54ca816eb7d3230a193a6cae765d8edc50df4a6ba9fb7c0647cd6befc8c3bbbe1d96c285ee202812477d7295c1130ea8cdd6f4' + 'SKIP' + 'f1a94986b4b3f310fecf981e3619f9787a0c8cb0b4fb3f39e5fe85ee3400e7e50278d41bf5468f65e5bd890358d2aabe28d3e11ee838ccbda2ac7af16be568d9' + 'SKIP' + '44dcaad2ab326ebee62d3e63207a161dcb1b63bdb21412b5624ca7875bb6e99997528683a633d7dd526dc5ae9408e73bd066e2a1e5b34ba5c829162df2265346' + 'SKIP' + '162a2c2104d5f036657ace6eef2ac2d533c0934376fab2521ff1d3fccd704e3d08fa4f6162e9100a844db2aeb57fa3ce214b8058bd227f38b058ab7606ee7816' + 'SKIP' + 'd50d5a69087a5ba6c416a6391232764c8281a8094645175fdb2cfc1bb011e33335ae4e96d79364681bea61edcb58ba9acb491e08c301d0667481759d0ee1e183' + 'SKIP' + '539b1d0e764f0ace3e572ac49f20a790183155ddc0bcd3a8d040f44c9891dd36b1a0accb8c0288267afbd33e6ceadf6fc3a5e3db40491c695e0c1d3dd64af40c' + 'SKIP' + 'f5cae6eabf2066d5deef737a15967d5d11340de34b181f3b3d841023507fc58af2b68926eb04bb342e1adb857165400f862f2b35ffec4204a107b3b22a1571e7' + 'SKIP' + 'ab28a1f4f1756b8451840566d8100cb3d0b498b13d006593c72829b9783e94900bae78ab47c576d156e1d08c56602274b3f7d5bd885de17336623b23c3e4f255' + 'SKIP' + '8481f23c33a84d8efb798f4b83f78011f74cd5a3dec428b177d7e600922836c1eec61c4238724038221d8ca2e2cdf20462c5911085da3461aa315f7dd19d96d8' + 'SKIP' + '19ebb39f3e53ce77dc3522d15c88270c5ca3fdd76fa8bd417852d08a1d85b30bc6eed3e11c95392b694dfc59bb8b43e41d43df4954f00593c8fbe50bd9e199cc' + 'SKIP' + '4dc42e9bfb5ee0f952bfd7befc9d333044aec11fc3a3852d4636f2e38ab139eb92c497eb3e30921b6ee8ae7d6e5834dc334ad25419bac2c875a9fd7eb7945f9b' + 'SKIP' + 'fea498b5c5ada6d7f57caf652b68f687081aae8552d2d04cd9e96764efd1b896208671f4f501210bb557710cca3ceb0c02ac2b5da6d4c04c210e87ec2d715384' + 'SKIP' + '4a4af88fc8a8c37ae6d20b35d19e60a17672569d5489be120edbcc62ab3ba4bbc43b78cd0799b9fd73c84b089364320bcbad6fcb7128c2cb06d15ed984d6fc8c' + 'SKIP' + '0959ca78ff31299adf1091f4aa1b32cd2e9066aba5e44f0658c91bcb93fe8714f7b671ffa2cd498ecbb6c6e643ac6571bfb193034af206599122fd7c2a3c23a8' + 'SKIP' + '03ca740048e48c5b6948f972f7a826240cf51c9b5c8645d1b1c50366e78cbf45f5b45b7b47f87f231b50aab46ef2f8fd78bc53899ba1c319a19f9f61c7cecb90' + 'SKIP' + '1b63569b589e994d1869e2f8eaf5d4ee225fe68862ac5c848e46544b827ca5fddb4bf060f5f39001608fd9bb284eec005f5f9b095307d3a008c9ea8957c2fad1' + 'SKIP' + '073514dabc88f0206911eb27eb36157e425849221056438340b22ee7bfa6997b58ce7dc4470b09d7ee96f6374f117d91cc19d9032401b2355d1c0aba6fc75771' + 'SKIP' + 'd7dffb68fd22db7ef41d1ac193dd297fb6150f034f257a44e6cb0a4848dc4a94e328c2e26d5296a72993e2db75d09e3ac46456d337a5bae767fb266082d3cc66' + 'SKIP' + 'aca0527f5630671a319e7ecff958a3a64f29df73b427c67c1d29763cc1801fa82e96761c3a61c22d936913943a49261da64d490a0752ba9ffc8e0c1d92f99835' + 'SKIP' + '803736a8e836560b3e50b7c0c8cf1f9ebb16b8642cc7bc6e5167d19037bd757431565fe4581776e50eb2693c49f778a24fecff41a553612e7e498f755b8f2f67' + 'SKIP' + '4bc132d9c34deaae9b13d08dcff8ea83aa8f9ce4398531e0e0a80df30da3808815f3a587fe5627f6553b8e9c2aea6cc1749b752250a91c94f1cf8ba4f3eb5e8d' + 'SKIP' + '2b007e58c0541c6ae71ac07cc8c0762343a50e1f3eb52db5631ce725c4f074f41c639fbf6d8695daa78ee1d6a8cb9a0c31d0a9d9ea2a3a10c05f7f6421dda184' + 'SKIP' + 'd3b2c0e4fd9f26a0eded375522478f8e9403232799d233eed4668920f8a9942ec6bafbc928f14459e4736f63a9a66d3ae2e9f70dc810145acb5fc522b13ecbd9' + 'SKIP' + 'c7b0be7a05a304e9993fe8ebccefa44944bf9d27dad9f7b3cc7111221bf50b2db71ca51cafc89dc03b237aef3aba7114543c8475cc393d13e61726e68898d3af' + 'SKIP' + '4008c7443bc221a142a939deac86918bef8f09ff1ca99a899586b13120edb8007450daf0b9d143770a9f04d616fe96f527b6aac160be45e5cfa66c0da898039c' + 'SKIP' + '029d9e899dd9e7bdbcacee34ee9b468e2e2a8f726e38d014891c8353f6afcc52a58c7d9289fb0487cce4d90c8d51f578c27b1dbcdab42de7308f50b7baf27877' + 'SKIP' + '9ede433f4e01a5a313a133a8029d64250596582b93a33e434b8e2ee5bc53ba7f91947e1be2aaf57776028cc89e7ca62cc90067ee64650b3e503acad858777a8a' + 'SKIP' + '9ac15b3542629c5333cc7d7f53b4f2995d807a31331b9e04d3da051be0ddde7e8c3cbc79837ffb49a4d364db1680cdc6d763465fa56bd4d26b16b765985babe1' + 'SKIP' + '25fc5f03732a584c864ee2ad20a74e16777998b0b34f880bbe9d646cf1944bac1f276bbd62515475337eaa81c0f398d79888aca6dc18bcd06ebb7cee9e4a1d8e' + 'SKIP' + '70e35154f9385156a11280294cc254cb314fd89653daedb46546a6bd64c550e3fc7e7a8df3e653c516687f0317bcc710fce82b4d649472333c501ed85fcd6cf6' + 'SKIP' + '576fd826f28d945782840865f3e5b7c87d7f6f91f08e473ff0d06a37caa2f89cdd9cb4a7aafef5551363aab9c60e887ee794d5b6292874c12baf88c23ea5668e' + 'SKIP' + 'b69cf36cc5633e507866e67f59557646dda7d6ea436b145f3f3356c4b12e1300c94977a7d1214835f2476c94a30dd672076241b920d96d744fbc199539df506f' + 'SKIP') +b2sums_x86_64=('11bfedf70dc9366a42b42573a3677e348fcd7b253ce13118036a75190e6c9372f9549589ff2dbfac5b976fc15d11a1b193baaee2135d331ca90f8a9a9a6e1462') + +# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc +sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' + 'SKIP' + '95f3e9209629044028373af987dc0e270a5a15acb8c8e49c5f05057220c75fe2' + 'SKIP' + 'a9171e731d08a454a50174889af8936fab962b33c37c67169b0cdf21b0b80821' + 'SKIP' + '6a27e1b363f1bb133b9905f84b00e4d01885cac84858db46bdbf764849c9984b' + 'SKIP' + 'dc3facd0cea0f1f99cfd824203db8da8cd0a970d07b2c4c88dab661655687545' + 'SKIP' + '9066c85e02351d86d4c61b86eb34d45fbd8022f42ce1739aee37931e7959298f' + 'SKIP' + 'bc0cc15d29d5b817201daf0f694543f932e6af319c24c1f603fc89e71b7c27cf' + 'SKIP' + 'a101a8e061983d0a1cf5b47efb28840e4a8d3b00daee53a6a7f773e2472ffdc9' + 'SKIP' + '9492d7db11fd327ff06a52cad945bf8ff6dfcabec72c435bf02b4432ee67475e' + 'SKIP' + 'a90ad7d91ecd9c7f64c9f84a65626241bc86c08c5c255e6684b7e26bcf5a47f8' + 'SKIP' + 'c206a0d1ac334a36f9ec5b8b2d706eaf6b726d32e304848d0ada50b6f25885b8' + 'SKIP' + '40177376862e4d7f45684c5e2a743f3da40d59817b0afa3050a3002ac6838085' + 'SKIP' + '720720067a21e8c57d2619f4c0b46846ac060ec6db97088634d62ab8628c70a9' + 'SKIP' + '3d1299d19aa9fbc96d25f67154009813e05e92340340953ac8355649cff837a4' + 'SKIP' + 'dac26fb42f5df71b30c46b3c5894c6016af5a283c57b45173386767ca53b1bf8' + 'SKIP' + '66eb2c49dbf708cc781d411d0e44ed613480f0ef66d522272929d9cb7bb21e93' + 'SKIP' + 'b7ba949eea77e169aceb2c401d4eb83a7413aab6f15ee2a7ab1a96352f0aa12d' + 'SKIP' + '341424b925d506869793686e22ea8f095a34048595ebebec409c0a7bbd346ca2' + 'SKIP' + '7c25d0a53a115bc1f072ff0cfdd9aa88858e754ae8b15026a2582656e8bd9c43' + 'SKIP' + '79799f12cdf42de1cf6c5d483896b439d77bc2e582e10c7215f5375a76612a72' + 'SKIP' + '5e3b455024fab2855b590091c9d14b0d12d5363f3d4bf025b31b9aa88b65eba5' + 'SKIP' + 'abd8c9326cecc9bb85db4b3dc98ddcb66306b34a6d84fb05d0805aed537e2dc4' + 'SKIP' + '60a9df54821cd98581e7476f8a7bb6cb0819762bdbed4d356332e41d164a8182' + 'SKIP' + '929108ae0a8eb4782cafdd89e3d4426d02d86f2c2102ed99e760e31f0bc3794a' + 'SKIP' + 'e1226c836a9c2fb6daff109e56ba21c13fd198e109cbb181de920af04fe153e6' + 'SKIP' + '8d60f7dd26ef904419aa966427a9af2f2c9a079bb092ea0b58274ab905f6047a' + 'SKIP' + '258597006eb96e1fb185ec9303ceb48148eedce95608b57900383f6df59d2989' + 'SKIP' + 'f935409f9aeba3314adfad7db1b3d895557a07029a80d10969804e49c0e270ca' + 'SKIP' + 'd8e64b9da3beab7c33832c019da50d0977b39dc32ec1f649a7c3bc93a2fdb9a5' + 'SKIP' + '1780f66b157de6d249301331e31b7505f6e6fa26686fa4c618623567e4b758ae' + 'SKIP' + '2501c98aaea6a53eeaa18d696f60b677e710d0f2d89f7525e47ba806de1878b3' + 'SKIP' + 'e6b770d37e80509ed7ec5d8b59b4d2e5d6dba54ba659d7392de3b567f1eb6e54' + 'SKIP' + 'b8596b5b5b546fdc309e10326b0e2ccea5a0538d4919630ed2845cded78580aa' + 'SKIP' + '5cc215215fed4247c2d6b9732deff586331a07d54c3b00538a81921093a3a1a1' + 'SKIP' + 'e303da14a3c8a15fd1cb45394138f03dd16e66d2ac0d1ebcd933977df937d771' + 'SKIP' + '1f0c958b64ba48b8dc8b5548e1ad1934b8d4903fc16fec15014759e3681ea275' + 'SKIP' + '47994d576008a377de612ed77e6b2e7bc6b24ad3a30a6d157646d380ae323142' + 'SKIP' + 'd9eb3717ba98e270b0a998f8dd90cbda072f63b6a6ea8f66763bde3ea64e591d' + 'SKIP' + '1e2fddb2e0c5d184a2e8c774886cf9be2f2f292e4f05832b50e5b49c48a7bacc' + 'SKIP' + 'b9b8771b6a8bd7128f4344ad189e32c155abf72abd3ff646dcf5c78b08eb94c4' + 'SKIP' + 'f62837e8ee51070f8139f718e0e80a4ce0c0dea9ce2328c10c63403aca5b3d2e' + 'SKIP' + 'cae59282e3d1afd69f4f5dc00a180c712ef464c98b8fd7ccc9b03de944dc79b9' + 'SKIP' + '5a74c8b2ba11e5876eeeb5ba530ae97e384b48943fa7a31e13396ecb82c5a8ce' + 'SKIP' + '8b954ea37a2190022064c82688bc8cb1cf79a90598f078035be7d4041003cda9' + 'SKIP' + '40ac31789399a5964c4fd820d4cdc7214add91109bbcf4a614619e5d8b367441' + 'SKIP' + '707460d08c15f451d0a0e567c6e35522f3e1409bbe3bfbcf7160f33af06391b7' + 'SKIP' + 'eebbe50deab379d09b5149ef30769ad6dfa11109696934ee66b108ec406da942' + 'SKIP' + 'dab92dbc6eac02771c72801d94e6a53aabcf629a9cd79e85e0ce2ba44a65b06b' + 'SKIP' + '30e2fc90c950869ab2fe1535b609f35dc1d46d6b6816de4afcbf687dbc224f0a' + 'SKIP' + '1fdf8c4d8ee07c02883b827a11a84c1e5f70570545b8a4a232f6b59dd02049fa' + 'SKIP' + '0057dff07a3ce9c7b7085edd1f912958e01ce8f7400151d55dfb74c0c6a084e2' + 'SKIP' + '2762f72620781a672d9a21bcc591b862b04a756b271cd1009a7390eb1b77635e' + 'SKIP' + '541325476efee83232ab8a6c62c14cf3c3c957c49cf053ed2869f7780f86d032' + 'SKIP' + '8a8eb0934424ab6312f26e03a06be5d0401360ee60a20a87b11f7c3c6590103c' + 'SKIP' + 'f7ff13ea46359217d696c6ccd49bc3d74c441ac5893d5151d95138017b0d10aa' + 'SKIP' + 'd17493b3579c730d27dc723447175b70d991f50253c9fd419df5e923bdf7a244' + 'SKIP' + '92971bb4dbde209cf170838cfcd75d8ac9978239821d3493c28cc12623c2bb60' + 'SKIP' + '7245c6e2cb5c0b0ddf800894f087770cbf8ccc6f8b3f53b9a21c704e9dfa42c2' + 'SKIP' + '135140bce40644079be7743b51f8618fc829a8868ac53137050f76c45eca4061' + 'SKIP' + 'e6ec816ae309445c6c11e3433c04d0c9100e16c0e23268226d13c19e7be49edb' + 'SKIP' + '29c61ec984bd342113b3c4310a3d5fddfd6d7db83847cfb26b84cc730385320a' + 'SKIP' + '53425998005a4115bdf6001b9ffbe62c66bb2db205994b2e13005a3417ea6cbb' + 'SKIP' + 'c28fca9ba015e96599627acf42e52737a6a846bdcbfd3d4af4147eda3109e12d' + 'SKIP' + '5ce97dadb9eb1a8b0da41a8446ee74e01a5a7e7ab439bbea7a711318f04ae24f' + 'SKIP' + '44f1c7bd18ea7f8772ce41e24dddd5f314e416ec8dfac6afe1ce8680c1bb4903' + 'SKIP' + '0bb8ae5b6f23e13c60953eb5bbc8939e8f6c8faf2b216a20203af3b6f0217134' + 'SKIP' + '907c92ef681154d10959a5f0cbe2a636688036990ba42e059af00cc2311b4be4' + 'SKIP' + '534e68a989d36823f14a10299803f294eef39391619b4c329d78c20539d9fcd5' + 'SKIP' + '516e6ec51ca0330989128a4eb2a8f6785e5d45d930899cbbbf37b8147ddfd854' + 'SKIP' + 'bda3d6842f15998ce3f3e33d0e6e420df9c9c2abcba13716af478720c5933753' + 'SKIP' + '3e7e7dba1ca88930c2c3f0d5411ddcee8c2e12fca59d859e2383dccaf9a08022' + 'SKIP' + '3a271bc6b0219152047fb2b78d9e695eb791b796ea6d8233096912c77d62a5c0' + 'SKIP' + '4e80f2d05591175d4ed0a1a144509e97260e5593472aae586c07956215d99162' + 'SKIP' + '8ebce8f38e38bd66879d040028c2448e5bc7dcde9df0ccee2b4221015591b162' + 'SKIP' + '0b0e05b615ae6eb825bf7d7fa568f551b0dff6c2cee62c9285c1b0d63224ea1c' + 'SKIP' + '2e1e2a0eb039ce61c1612bf6c7ab5227b99df053bcd5592da7758715dd333fda' + 'SKIP' + '17de1f76da3db42dc3c7829a22953cd2c6de916999d809e3fb0bbb741753ec8f' + 'SKIP' + 'd90cc6570a47c8754e4c5d45834e0434c0a1fcabda9b8933b1fba84e3dabc083' + 'SKIP' + '028bf538c870465e1752514725372fa9eef6b7178d9b8d1e2ecc15edc5a63cc8' + 'SKIP' + 'a92a84405ee9845738c6b52d8f0f0eca16eb40afe0ebe0f4e9e1168597675311' + 'SKIP' + '44a0b10a6dc83465ea86f5fba936b2bfbeffb1b3f0b2e04013bfed99ba4f6c5a' + 'SKIP' + '229b28cee6f2b8afb5888eed453ce7f82de033900383bd91957339007c9e6cbf' + 'SKIP' + 'c5d61db05b19fde06102b0b3dbd76e1989c8f07f3b7d175176bd2d73f1a71e5c' + 'SKIP' + '5988d7a37b4b71a64ed09fea872f08515a3c7029d33fd053a6a1822bb811ddb5' + 'SKIP' + '1ec4fb93700b0696ba2c2a371f218f13b32dcc745523c7ee26a5c3cd7af253fc' + 'SKIP' + 'c67fee35a42866dd3c9056ff099fc504cdad9530c107d959331b5572b21af818' + 'SKIP' + '36cb306751c57de3e61c7a4cd18a7f131eef0dc1de15a4e28751d1e5b08bace8' + 'SKIP' + '07683e522d6dea3d3ce658ee80bf4317f6c3caf248259da960bc4610f4d1e807' + 'SKIP' + '66a0933d2d4f2edc8999b6fdf0fe10b5b607646fd6f1a1d534bf26270543f8f1' + 'SKIP' + 'c5279468d93e562f509a6b599a2b7ca7ee0032d3fe35314c5769fc6c9a9234ab' + 'SKIP' + 'c0111614ec44014cbf621eedf5a4ef302a855423bc1633ee692392950e971d0e' + 'SKIP' + '1cbe1ddd3b37cd0c7e4bad4af08e681eda2f7a61698004322bb87f320aa95dbb' + 'SKIP') + +export KBUILD_BUILD_HOST=omarchy +export KBUILD_BUILD_USER=$pkgbase +export KBUILD_BUILD_TIMESTAMP="$(date -Ru${SOURCE_DATE_EPOCH:+d @$SOURCE_DATE_EPOCH})" + +prepare() { + cd $_srcname + + echo "Setting version..." + echo "-$pkgrel" > 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 $([0-9]+(?:\\.[0-9]+)*)\",\"build\":\"(?P[0-9]+)\"", + "variables": { + "_build": "{build}" + }, + "revision": "{build}", + "revision_variable": "_build" + } + }, + "origin": { + "aur": "lmstudio-bin", + "commit": "8e97652a62a284b0c3436688bed6dfae0b54c669" + } } diff --git a/pkgbuilds/lmstudio-bin/PKGBUILD b/pkgbuilds/lmstudio-bin/PKGBUILD index 1c62765..48b1a59 100644 --- a/pkgbuilds/lmstudio-bin/PKGBUILD +++ b/pkgbuilds/lmstudio-bin/PKGBUILD @@ -1,8 +1,9 @@ # Maintainer: noureddinex pkgname=lmstudio-bin -pkgver=0.4.23 -pkgrel=1 -_pkgver=${pkgver}-${pkgrel} +pkgver=0.4.24 +pkgrel=2 +_build=1 +_pkgver=${pkgver}-${_build} pkgdesc="LM Studio - A desktop app for exploring and running large language models locally" arch=('x86_64') url="https://lmstudio.ai" @@ -15,7 +16,7 @@ conflicts=(lmstudio) source=("https://installers.lmstudio.ai/linux/x64/${_pkgver}/LM-Studio-${_pkgver}-x64.AppImage" "lmstudio.png" "lmstudio.desktop") -sha256sums=('c1bdf195281e25dc921af5c91eb2502035240dce1217140c43036d6cf3038fd8' +sha256sums=('17cb8ac6374f9182fc127efae20680265e3c4c17d96eadf147eb5fe6111a9353' '9f791789c959a11316328692807737a5f1bc1c170ae99ec04c56bfd8ee8263e5' '635dec12f3e3a57136b9e6fd7c2839ed6da7287fa55b482d64debf6eacf36baa') diff --git a/pkgbuilds/localsend-bin/.omarchy/package.json b/pkgbuilds/localsend-bin/.omarchy/package.json index 26d73a1..6e350be 100644 --- a/pkgbuilds/localsend-bin/.omarchy/package.json +++ b/pkgbuilds/localsend-bin/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "a408db7b17712e0d56c49ca3feb5546808a3220c" + "upstream": { + "watch": { + "github": "localsend/localsend", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "localsend-bin", + "commit": "8dc54e7bdcbf24653b9247ec8af07aa012713b1c" + } } diff --git a/pkgbuilds/localsend-bin/PKGBUILD b/pkgbuilds/localsend-bin/PKGBUILD index 7d43117..74f1fd2 100644 --- a/pkgbuilds/localsend-bin/PKGBUILD +++ b/pkgbuilds/localsend-bin/PKGBUILD @@ -7,20 +7,20 @@ # using the .deb instead of the .tar.gz as it already contains the icons as well as the .desktop file. pkgname=localsend-bin -pkgver=1.18.0 +pkgver=1.18.2 pkgrel=1 pkgdesc='An open source cross-platform alternative to AirDrop' url=https://github.com/localsend/localsend arch=('x86_64' 'aarch64') license=(Apache-2.0) -depends=(fuse2 xdg-user-dirs libayatana-appindicator) +depends=(fuse2 xdg-user-dirs libayatana-appindicator libayatana-indicator) conflicts=('localsend') provides=('localsend') options=(!debug) source_x86_64=("${url}/releases/download/v${pkgver}/LocalSend-${pkgver}-linux-x86-64.deb") source_aarch64=("${url}/releases/download/v${pkgver}/LocalSend-${pkgver}-linux-arm-64.deb") -sha256sums_x86_64=('dae68192ad43a59a68df06454eb5fa4e9a9f86a343fe430165efd8b18863d0f4') -sha256sums_aarch64=('eb24e724fbdf830c8fb07dfc66b76b8a649923d083278305a63d5d539db4b606') +sha256sums_x86_64=('cc42a4f3eacdcb25ec31f0016b1272acb003145ab30484db8965450e20c72cd2') +sha256sums_aarch64=('bbd8347b7979f936d3bae176a3f0d9b81431605707c585270d6240f7670feb1f') _pkgdesktop="localsend_app.desktop" @@ -35,6 +35,9 @@ build() { # Adjust .desktop to have it point to the correct icon sed -i -E 's/^Icon=.+/Icon=localsend/' "${srcdir}/usr/share/applications/${_pkgdesktop}" + + # Add StartupWMClass so app can be pinned to taskbar + sed -i -E '/^Exec=localsend/a StartupWMClass=org.localsend.localsend_app' "${srcdir}/usr/share/applications/${_pkgdesktop}" } package() { diff --git a/pkgbuilds/localsend/.omarchy/package.json b/pkgbuilds/localsend/.omarchy/package.json index dc5e190..5e3c138 100644 --- a/pkgbuilds/localsend/.omarchy/package.json +++ b/pkgbuilds/localsend/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "11788eb33c9298d611382884e8775fc34f874703" + "upstream": { + "watch": { + "github": "localsend/localsend", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "localsend", + "commit": "11788eb33c9298d611382884e8775fc34f874703" + } } diff --git a/pkgbuilds/macbook12-spi-driver-dkms/.omarchy/package.json b/pkgbuilds/macbook12-spi-driver-dkms/.omarchy/package.json index d49dd70..811c326 100644 --- a/pkgbuilds/macbook12-spi-driver-dkms/.omarchy/package.json +++ b/pkgbuilds/macbook12-spi-driver-dkms/.omarchy/package.json @@ -1,4 +1,17 @@ { - "source": "aur", - "upstream_commit": "38f085c6470eee409c22a4753eaf58d58b4759fd" + "source": "local", + "upstream": { + "watch": { + "git_branch": "https://github.com/marc-git/macbook12-spi-driver.git", + "branch": "touchbar-driver-hid-driver", + "version": "0+git.{count}", + "variables": { + "_commit": "{commit}" + } + } + }, + "origin": { + "aur": "macbook12-spi-driver-dkms", + "commit": "38f085c6470eee409c22a4753eaf58d58b4759fd" + } } diff --git a/pkgbuilds/macbook12-spi-driver-dkms/PKGBUILD b/pkgbuilds/macbook12-spi-driver-dkms/PKGBUILD index 3254326..a462dcb 100644 --- a/pkgbuilds/macbook12-spi-driver-dkms/PKGBUILD +++ b/pkgbuilds/macbook12-spi-driver-dkms/PKGBUILD @@ -3,7 +3,8 @@ _pkgbase=macbook12-spi-driver pkgname=(${_pkgbase}-dkms) pkgver=0+git.315 -pkgrel=1 +pkgrel=2 +_commit=807b65b6d9d7c29bd7ff633fbb80596a343e9eb9 pkgdesc="Driver for the keyboard, touchpad and touchbar found in newer MacBook (Pro) models" arch=('i686' 'x86_64') url="https://github.com/marc-git/macbook12-spi-driver" @@ -11,15 +12,11 @@ license=('GPL2') depends=('dkms') makedepends=('git') optdepends=() -source=(git+https://github.com/marc-git/macbook12-spi-driver.git#branch=touchbar-driver-hid-driver +source=(git+https://github.com/marc-git/macbook12-spi-driver.git#commit=${_commit} dkms.conf) sha256sums=('SKIP' 'e42243352d015a78e5d13260361d3052d0b84c4105bcb89191a319f2249cc47b') -pkgver() { - cd $_pkgbase - echo "0+git.$(git rev-list HEAD --count)" -} package() { install -dm755 "${pkgdir}"/usr/src/${_pkgbase}-${pkgver} diff --git a/pkgbuilds/macbook8-spi-pxa2xx-nodma-dkms/PKGBUILD b/pkgbuilds/macbook8-spi-pxa2xx-nodma-dkms/PKGBUILD index eb7975a..aa9b7b8 100644 --- a/pkgbuilds/macbook8-spi-pxa2xx-nodma-dkms/PKGBUILD +++ b/pkgbuilds/macbook8-spi-pxa2xx-nodma-dkms/PKGBUILD @@ -2,12 +2,12 @@ _pkgbase=spi-pxa2xx-pci-nodma pkgname=macbook8-spi-pxa2xx-nodma-dkms pkgver=1.0 -pkgrel=1 +pkgrel=2 pkgdesc="Patched SPI PXA2xx PCI driver forcing PIO mode for MacBook8,1 Wildcat Point GSPI (8086:9ce6)" arch=('x86_64') url="https://github.com/basecamp/omarchy" license=('GPL-2.0-only') -depends=('dkms' 'linux-headers') +depends=('dkms') makedepends=() conflicts=('spi_pxa2xx_pci' 'macbook12-spi-driver-dkms') provides=('spi_pxa2xx_pci_nodma') @@ -31,4 +31,4 @@ package() { # Modprobe blacklist: prevent the in-tree spi_pxa2xx_pci from claiming this device install -Dm644 "${srcdir}/macbook8-spi-nodma.conf" "${pkgdir}/usr/lib/modprobe.d/macbook8-spi-nodma.conf" -} \ No newline at end of file +} diff --git a/pkgbuilds/makima-bin/.omarchy/package.json b/pkgbuilds/makima-bin/.omarchy/package.json index d3f3a01..46b6b10 100644 --- a/pkgbuilds/makima-bin/.omarchy/package.json +++ b/pkgbuilds/makima-bin/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "b91ad74a6e85ab1c3697a0c0a7d7bd9cf17a802c" + "source": "local", + "upstream": { + "watch": { + "github": "cyber-sushi/makima", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "makima-bin", + "commit": "b91ad74a6e85ab1c3697a0c0a7d7bd9cf17a802c" + } } diff --git a/pkgbuilds/makima-bin/PKGBUILD b/pkgbuilds/makima-bin/PKGBUILD index 6bca19c..42eac8e 100644 --- a/pkgbuilds/makima-bin/PKGBUILD +++ b/pkgbuilds/makima-bin/PKGBUILD @@ -2,7 +2,7 @@ pkgname=makima-bin _name=${pkgname%-bin} -pkgver=0.10.2 +pkgver=0.10.3 pkgrel=1 pkgdesc='Linux daemon to remap and create macros for keyboards, mice and controllers.' arch=('x86_64') @@ -11,7 +11,7 @@ license=('GPL-3.0-or-later') provides=("$_name") conflicts=("$_name") source=("https://github.com/cyber-sushi/makima/releases/download/v${pkgver}/makima") -sha256sums=('ad8255f44a8a619f7d300bbf81463b61e3e6e7af37697fac8d53f6c7f937edaf') +sha256sums=('1acd96086e46298269b243e010c05a9ddb3e36ddbee5a46fc0cc7826adbe2693') package() { install -Dm755 makima "$pkgdir/usr/bin/makima" diff --git a/pkgbuilds/minecraft-launcher/.omarchy/package.json b/pkgbuilds/minecraft-launcher/.omarchy/package.json index dbde2a6..1bd6241 100644 --- a/pkgbuilds/minecraft-launcher/.omarchy/package.json +++ b/pkgbuilds/minecraft-launcher/.omarchy/package.json @@ -1,5 +1,18 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "c6cac125f8cfc5d9a45e93b7f3ac9b01385dae23" + "upstream": { + "watch": { + "archive": "https://launcher.mojang.com/download/Minecraft.deb", + "pattern": "(?m)^Version: (?P[0-9]+(?:\\.[0-9]+)*)$", + "mutable_sources": [ + "source:0", + "source:1" + ] + } + }, + "origin": { + "aur": "minecraft-launcher", + "commit": "c6cac125f8cfc5d9a45e93b7f3ac9b01385dae23" + } } diff --git a/pkgbuilds/mise-bin/PKGBUILD b/pkgbuilds/mise-bin/PKGBUILD index 9d59d45..1b8c274 100644 --- a/pkgbuilds/mise-bin/PKGBUILD +++ b/pkgbuilds/mise-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Jeff Dickey pkgname=mise-bin -pkgver=2026.9.1 +pkgver=2026.9.9 pkgrel=1 pkgdesc="dev tools, env vars, task runner" arch=('x86_64' 'aarch64') @@ -14,8 +14,8 @@ provides=('mise') conflicts=('mise') source_x86_64=("https://github.com/jdx/mise/releases/download/v${pkgver}/mise-v${pkgver}-linux-x64.tar.xz") source_aarch64=("https://github.com/jdx/mise/releases/download/v${pkgver}/mise-v${pkgver}-linux-arm64.tar.xz") -sha256sums_x86_64=('bbc21f8fe9b2aa09f74250be15f9bf32e87562e1039d858ad91d7944f057c8e2') -sha256sums_aarch64=('7d0c28d44bd7e1c5444a7d867146afabe43592c728329bc0ed5ec59ca7106c48') +sha256sums_x86_64=('ed3ee9b7331182e57f77230cf6af64d90e6e1a01e2c3e4749568ea335837c0fb') +sha256sums_aarch64=('09d0489db27c173d1427b4248e3e1fbf18b00ccc1d887d8746f1d8b311903a50') package() { install -Dm755 "${srcdir}/mise/bin/mise" "${pkgdir}/usr/bin/mise" diff --git a/pkgbuilds/nautilus-dropbox/.omarchy/package.json b/pkgbuilds/nautilus-dropbox/.omarchy/package.json index a9b635d..d7046be 100644 --- a/pkgbuilds/nautilus-dropbox/.omarchy/package.json +++ b/pkgbuilds/nautilus-dropbox/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "f7907953200ffd14f1260691f4ff1b7290fcd887" + "upstream": { + "watch": { + "github": "dropbox/nautilus-dropbox", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "nautilus-dropbox", + "commit": "f7907953200ffd14f1260691f4ff1b7290fcd887" + } } diff --git a/pkgbuilds/nautilus-open-any-terminal/.omarchy/package.json b/pkgbuilds/nautilus-open-any-terminal/.omarchy/package.json index 667476e..8a36764 100644 --- a/pkgbuilds/nautilus-open-any-terminal/.omarchy/package.json +++ b/pkgbuilds/nautilus-open-any-terminal/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "b4bcde82a43129c09e22fd57351e61d5593a3d8f" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://github.com/Stunkymonkey/nautilus-open-any-terminal.git", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "nautilus-open-any-terminal", + "commit": "b4bcde82a43129c09e22fd57351e61d5593a3d8f" + } } diff --git a/pkgbuilds/nautilus-open-any-terminal/PKGBUILD b/pkgbuilds/nautilus-open-any-terminal/PKGBUILD index e643040..d818732 100644 --- a/pkgbuilds/nautilus-open-any-terminal/PKGBUILD +++ b/pkgbuilds/nautilus-open-any-terminal/PKGBUILD @@ -3,7 +3,7 @@ pkgname=(nautilus-open-any-terminal caja-open-any-terminal) _pkgname="${pkgname[0]}" -pkgver=0.8.2 +pkgver=0.8.3 pkgrel=1 pkgdesc="context-menu entry for opening other terminal in nautilus" arch=(any) @@ -11,7 +11,7 @@ url="https://github.com/Stunkymonkey/nautilus-open-any-terminal" license=(GPL3) makedepends=(make gettext) source=("${_pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/${pkgver}.tar.gz") -sha256sums=('f7a48327947658c8d34879dbbc1081c40e726dc1cfd42a0191ef6a9d63faca71') +sha256sums=('3c76a022f8bd4a11e6b75632e4b3f40adf12890c04309d0a7ef9c7cc2d52144a') build() { cd "${_pkgname}-${pkgver}" diff --git a/pkgbuilds/nordvpn-bin/.omarchy/package.json b/pkgbuilds/nordvpn-bin/.omarchy/package.json index eb7e2ce..2edd578 100644 --- a/pkgbuilds/nordvpn-bin/.omarchy/package.json +++ b/pkgbuilds/nordvpn-bin/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "9aa8f42f74cc152e67ac112d026721a8da65d6dc" + "upstream": { + "watch": { + "debian": "https://repo.nordvpn.com/deb/nordvpn/debian/dists/stable/main/binary-amd64/Packages", + "package": "nordvpn" + } + }, + "origin": { + "aur": "nordvpn-bin", + "commit": "8d4c074f4b0a373c04c6f41baba83d3bd7d6d97b" + } } diff --git a/pkgbuilds/nordvpn-bin/PKGBUILD b/pkgbuilds/nordvpn-bin/PKGBUILD index 5628841..636f378 100644 --- a/pkgbuilds/nordvpn-bin/PKGBUILD +++ b/pkgbuilds/nordvpn-bin/PKGBUILD @@ -4,7 +4,7 @@ # Contributor: Martoko pkgname=nordvpn-bin -pkgver=5.3.0 +pkgver=5.4.0 pkgrel=1 pkgdesc="NordVPN CLI tool for Linux" arch=('x86_64' 'i686' 'armv7h' 'aarch64' 'armeabi') @@ -18,11 +18,11 @@ source_i686=("https://repo.nordvpn.com/deb/nordvpn/debian/pool/main/n/nordvpn/no source_armv7h=("https://repo.nordvpn.com/deb/nordvpn/debian/pool/main/n/nordvpn/nordvpn_${pkgver//_/-}_armhf.deb") source_aarch64=("https://repo.nordvpn.com/deb/nordvpn/debian/pool/main/n/nordvpn/nordvpn_${pkgver//_/-}_arm64.deb") source_armeabi=("https://repo.nordvpn.com/deb/nordvpn/debian/pool/main/n/nordvpn/nordvpn_${pkgver//_/-}_armel.deb") -sha256sums_x86_64=('34cdea72a75c9700c285b950ed2ec96acaeed4d607013a3abd18366731f8c334') -sha256sums_i686=('c6e3908876941698a41287e01ca4327d9f2c0116469c2de072e3767ba551c5ae') -sha256sums_armv7h=('34ee5e8ee89b22a1082b67253ca9eb61bcd4c158afb3a163b00c5b6679178c71') -sha256sums_aarch64=('87fc3fbacd289fbf2242d52b62da363b7e799701504dd7a98013db63df372999') -sha256sums_armeabi=('43caf6489ebc9d9f97d97be40312abfbfb7e07022aed76294830818cd993e1ca') +sha256sums_x86_64=('57669d407215f34cd80b56d0b953dcd0ab8dcd3413ed18c646f666e0582e013a') +sha256sums_i686=('2843b52f358f3b0eb20f2ffc8dfbb9937f2a4db11195d359cfe65c04a40b4ad8') +sha256sums_armv7h=('515d4206c8803b27a72564612730f06ff5870ecd9385674142bc171c20045b8f') +sha256sums_aarch64=('0a343574ec985def2d1b8ff9fde404568d95664f7e927655d11f4b01616e394e') +sha256sums_armeabi=('960cc36b3753651a8e85afc13567dc8f5405acce0e7ec7a98f221bd411f0deb5') package() { bsdtar -O -xf *.deb data.tar.gz | bsdtar -C "${pkgdir}" -xJf - diff --git a/pkgbuilds/nvidia-580xx-utils/.omarchy/package.json b/pkgbuilds/nvidia-580xx-utils/.omarchy/package.json index ae8781e..9439b28 100644 --- a/pkgbuilds/nvidia-580xx-utils/.omarchy/package.json +++ b/pkgbuilds/nvidia-580xx-utils/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "85c38619ce2267787d0515baa443739cb9d2883c" + "source": "local", + "upstream": { + "watch": { + "regex": "https://download.nvidia.com/XFree86/Linux-x86_64/", + "pattern": "href=[\\'\"](?P580\\.[0-9]+\\.[0-9]+)/[\\'\"]" + } + }, + "origin": { + "aur": "nvidia-580xx-utils", + "commit": "85c38619ce2267787d0515baa443739cb9d2883c" + } } diff --git a/pkgbuilds/nvidia-580xx-utils/.omarchy/patches/max-zstd-compression.patch b/pkgbuilds/nvidia-580xx-utils/.omarchy/patches/max-zstd-compression.patch deleted file mode 100644 index deb2e8f..0000000 --- a/pkgbuilds/nvidia-580xx-utils/.omarchy/patches/max-zstd-compression.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -13,6 +13,12 @@ - url="http://www.nvidia.com/" - license=('custom') - options=('!strip') -+ -+# Omarchy: the build image compresses with zstd at its default level, which -+# leaves these unstripped driver blobs far larger than they need to be. Maximum -+# zstd saves 89 MB across this pkgbase, most of it on the ISO's offline mirror. -+COMPRESSZST=(zstd -c -z -q --ultra -22 --threads=0 -) -+ - _pkg="NVIDIA-Linux-x86_64-${pkgver}" - _pkg_open="NVIDIA-kernel-module-source-${pkgver}" - source=('nvidia-drm-outputclass.conf' diff --git a/pkgbuilds/obs-studio/.omarchy/package.json b/pkgbuilds/obs-studio/.omarchy/package.json new file mode 100644 index 0000000..2a67d7e --- /dev/null +++ b/pkgbuilds/obs-studio/.omarchy/package.json @@ -0,0 +1,9 @@ +{ + "source": "local", + "upstream": { + "watch": { + "github": "obsproject/obs-studio", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)" + } + } +} diff --git a/pkgbuilds/obs-studio/PKGBUILD b/pkgbuilds/obs-studio/PKGBUILD new file mode 100644 index 0000000..688e890 --- /dev/null +++ b/pkgbuilds/obs-studio/PKGBUILD @@ -0,0 +1,102 @@ +# ARM build from the upstream release source bundle. The vendor CEF bundle +# is x86-only, so this build omits browser sources/docks. AJA is unavailable +# in Arch Linux ARM. Other plugins use upstream architecture detection. + +pkgname=obs-studio +pkgver=32.2.2 +pkgrel=1 +pkgdesc="Free and open source software for video recording and live streaming (without the x86-only CEF browser source)" +arch=('aarch64') +url="https://obsproject.com" +license=('GPL-2.0-or-later') +depends=( + 'alsa-lib' + 'curl' + 'ffmpeg>=8' + 'fontconfig' + 'freetype2' + 'gcc-libs' + 'glib2' + 'glibc' + 'jack' + 'jansson' + 'libdatachannel>=0.24.3' # WebRTC/WHIP output + 'libfdk-aac' + 'libgl' + 'libjuice' + 'libpipewire' + 'libpulse' + 'librist' # MPEGTS output + 'libva' + 'libx11' + 'libxcb' + 'libxcomposite' + 'libxkbcommon' + 'luajit' # Lua scripting + 'mbedtls3>=3.6.1' + 'pciutils' + 'python>=3.14' # Python scripting + 'qrcodegencpp-cmake' # obs-websocket connect QR code + 'qt6-base>=6.8' + 'qt6-svg' + 'qt6-wayland' + 'rnnoise' + 'sndio' + 'speexdsp' + 'srt' # MPEGTS output + 'systemd-libs' + 'util-linux-libs' + 'v4l-utils' + 'wayland' + 'x264' + 'zlib' +) +makedepends=( + 'asio' # header-only, obs-websocket + 'cmake' + 'extra-cmake-modules' + 'nlohmann-json' # header-only, obs-websocket and plugin manager + 'simde' # header-only, SIMD portability layer libobs uses on ARM + 'swig' # scripting bindings + 'uthash' # header-only, libobs + 'vlc' # VLC source plugin builds against libvlc headers, dlopens at runtime + 'websocketpp' # header-only, obs-websocket +) +optdepends=( + 'vlc: VLC media source' + 'v4l2loopback-dkms: V4L2 virtual camera output' +) +source=("https://github.com/obsproject/obs-studio/releases/download/$pkgver/OBS-Studio-$pkgver-Sources.tar.gz") +sha512sums=('6346b5bff255c9178ef57296489af11bc6949076b4084d3dff8d3da923c8b0d2ab64edb312629c54b8d4be1510bb4166b92838a8c6c2db55a7ee9e08108166de') + +build() { + local cmake_options=( + -S "obs-studio-$pkgver-sources" + -B build + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_INSTALL_PREFIX=/usr + -DCMAKE_INSTALL_LIBDIR=lib + -DENABLE_BROWSER=OFF # no linux-aarch64 CEF exists + -DENABLE_AJA=OFF # libajantv2 is not in ALARM + -DENABLE_LIBFDK=ON + -DENABLE_JACK=ON + -DENABLE_SNDIO=ON + -DENABLE_VLC=ON + -DENABLE_WAYLAND=ON + + # The tarball has no .git for the version machinery to read. + -DOBS_VERSION_OVERRIDE="$pkgver" + -DOBS_COMPILE_DEPRECATION_AS_WARNING=ON + + # Use Arch's side-by-side MbedTLS 3 CMake configuration. + -DMbedTLS_DIR=/usr/lib/mbedtls3/cmake/MbedTLS + + -Wno-dev + ) + cmake "${cmake_options[@]}" + cmake --build build +} + +package() { + DESTDIR="$pkgdir" cmake --install build +} diff --git a/pkgbuilds/omakade/PKGBUILD b/pkgbuilds/omakade/PKGBUILD index 5186e46..72e502e 100644 --- a/pkgbuilds/omakade/PKGBUILD +++ b/pkgbuilds/omakade/PKGBUILD @@ -1,5 +1,5 @@ pkgname=omakade -pkgver=1.7.0 +pkgver=1.9.2 pkgrel=1 pkgdesc='A beautiful, local-first game library for Omarchy' arch=('x86_64' 'aarch64') @@ -11,7 +11,7 @@ depends=('glib2' 'hicolor-icon-theme' 'libsecret' 'libzip' 'openssl' 'qt6-base' makedepends=('cmake' 'ninja' 'pkgconf' 'wayland-protocols') options=('!debug') source=("$pkgname-$pkgver.tar.gz::https://github.com/btsouth/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") -sha256sums=('715cd5e0cb860514276b1af57fe08ced71c1c69a6aeebd93aa51f1ca367eefa8') +sha256sums=('4c0ba7eecc036b959b34a8068f5923e8927e59b8ee2f0398d470cd933343c4fc') build() { cmake -S "$pkgname-$pkgver" -B build -G Ninja \ diff --git a/pkgbuilds/omarchy-chromium-bin/.omarchy/package.json b/pkgbuilds/omarchy-chromium-bin/.omarchy/package.json index ef66e39..1cd84ba 100644 --- a/pkgbuilds/omarchy-chromium-bin/.omarchy/package.json +++ b/pkgbuilds/omarchy-chromium-bin/.omarchy/package.json @@ -1,5 +1,19 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "344d4e2b006988df7f3a11952c7776d37891fb7a" + "upstream": { + "watch": { + "github": "omacom/omarchy-chromium", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)-(?P[0-9]+)", + "variables": { + "_build": "{build}" + }, + "revision": "{build}", + "revision_variable": "_build" + } + }, + "origin": { + "aur": "omarchy-chromium-bin", + "commit": "344d4e2b006988df7f3a11952c7776d37891fb7a" + } } diff --git a/pkgbuilds/omarchy-chromium-bin/PKGBUILD b/pkgbuilds/omarchy-chromium-bin/PKGBUILD index 5f634d1..7711a41 100644 --- a/pkgbuilds/omarchy-chromium-bin/PKGBUILD +++ b/pkgbuilds/omarchy-chromium-bin/PKGBUILD @@ -2,7 +2,8 @@ pkgname=omarchy-chromium-bin pkgver=148.0.7778.96 -pkgrel=21 +pkgrel=22 +_build=21 pkgdesc="A web browser built for speed, simplicity, and security, with patches for Omarchy (binary package)" arch=('x86_64' 'aarch64') url="https://www.chromium.org/Home" @@ -14,8 +15,8 @@ provides=('chromium') conflicts=('chromium' 'omarchy-chromium') # Architecture-specific sources -source_x86_64=("https://github.com/omacom-io/omarchy-chromium/releases/download/v148.0.7778.96-21/omarchy-chromium-148.0.7778.96-21-x86_64.pkg.tar.zst") -source_aarch64=("https://github.com/omacom-io/omarchy-chromium/releases/download/v148.0.7778.96-21/omarchy-chromium-148.0.7778.96-21-aarch64.pkg.tar.zst") +source_x86_64=("https://github.com/omacom-io/omarchy-chromium/releases/download/v${pkgver}-${_build}/omarchy-chromium-${pkgver}-${_build}-x86_64.pkg.tar.zst") +source_aarch64=("https://github.com/omacom-io/omarchy-chromium/releases/download/v${pkgver}-${_build}/omarchy-chromium-${pkgver}-${_build}-aarch64.pkg.tar.zst") sha256sums_x86_64=('f3f65950edb50722bbc99dde3beffa65a23e06593dcbe8ed7f17e92cc2613259') sha256sums_aarch64=('30aa14f35a06f03eae5442e845245614c374ffa13dc7a3c367ed4c65e68b7856') @@ -23,7 +24,7 @@ package() { cd "$srcdir" # Extract the package (filename varies by architecture) - tar -xf omarchy-chromium-148.0.7778.96-21-${CARCH}.pkg.tar.zst + tar -xf omarchy-chromium-${pkgver}-${_build}-${CARCH}.pkg.tar.zst # Copy everything to the target directory cp -r usr "$pkgdir/" diff --git a/pkgbuilds/omarchy-emacs/.omarchy/package.json b/pkgbuilds/omarchy-emacs/.omarchy/package.json index 5a35fc1..5d0b9fc 100644 --- a/pkgbuilds/omarchy-emacs/.omarchy/package.json +++ b/pkgbuilds/omarchy-emacs/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "c79a5ad3d523ad9a22fa57274d8b3fab8e87e5b2" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://github.com/scottjones/omarchy-emacs.git", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "omarchy-emacs", + "commit": "c79a5ad3d523ad9a22fa57274d8b3fab8e87e5b2" + } } diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 61dd516..46ee0bf 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -66,6 +66,7 @@ backup=( 'etc/systemd/user.conf.d/20-omarchy-nofile.conf' 'etc/sddm.conf.d/10-theme.conf' 'etc/sddm.conf.d/10-wayland.conf' + 'etc/udev/rules.d/60-omarchy-io-scheduler.rules' 'etc/udev/rules.d/99-omarchy-power-profile.rules' 'etc/udev/rules.d/99-omarchy-wifi-powersave.rules' ) diff --git a/pkgbuilds/omarchy-settings/PKGBUILD b/pkgbuilds/omarchy-settings/PKGBUILD index 346dbe2..88f43e8 100644 --- a/pkgbuilds/omarchy-settings/PKGBUILD +++ b/pkgbuilds/omarchy-settings/PKGBUILD @@ -10,9 +10,9 @@ # provenance only (empty when cut from a bare commit). omarchy and # omarchy-settings must always carry identical _tag/_commit/pkgver/sha256sums. pkgname='omarchy-settings' -_tag='v4.0.3' -_commit='0534987009061cbe2dacdde4ad564092ab698d12' -pkgver=4.0.3 +_tag='v4.0.4' +_commit='c668141e9c42b13c80c9ca4ea108e11708c5e8a5' +pkgver=4.0.4 pkgrel=2 pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers' # Arch-specific because the shipped /etc tree is not the same on every @@ -75,6 +75,7 @@ backup=( 'etc/systemd/user.conf.d/20-omarchy-nofile.conf' 'etc/sddm.conf.d/10-theme.conf' 'etc/sddm.conf.d/10-wayland.conf' + 'etc/udev/rules.d/60-omarchy-io-scheduler.rules' 'etc/udev/rules.d/99-omarchy-power-profile.rules' 'etc/udev/rules.d/99-omarchy-wifi-powersave.rules' ) @@ -128,7 +129,7 @@ if [[ -n "${OMARCHY_SRC:-}" ]]; then sha256sums=() else source=("omarchy::git+https://github.com/basecamp/omarchy.git#commit=${_commit}") - sha256sums=('3558399c3ddc0b9067d63c0d617d695829d8293689e9319a304839c3a6e0f9f3') + sha256sums=('8371b148aa06e9d0627c9e11668ef2dfdf54c4a9e5ffbee5aee08c3945073618') fi prepare() { diff --git a/pkgbuilds/omarchy/PKGBUILD b/pkgbuilds/omarchy/PKGBUILD index 684b448..41314ea 100644 --- a/pkgbuilds/omarchy/PKGBUILD +++ b/pkgbuilds/omarchy/PKGBUILD @@ -10,9 +10,9 @@ # provenance only (empty when cut from a bare commit). omarchy and # omarchy-settings must always carry identical _tag/_commit/pkgver/sha256sums. pkgname='omarchy' -_tag='v4.0.3' -_commit='0534987009061cbe2dacdde4ad564092ab698d12' -pkgver=4.0.3 +_tag='v4.0.4' +_commit='c668141e9c42b13c80c9ca4ea108e11708c5e8a5' +pkgver=4.0.4 pkgrel=1 pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH' # The payload is architecture-independent, but the dependency set is not: the @@ -94,7 +94,7 @@ if [[ -n "${OMARCHY_SRC:-}" ]]; then sha256sums=() else source=("omarchy::git+https://github.com/basecamp/omarchy.git#commit=${_commit}") - sha256sums=('3558399c3ddc0b9067d63c0d617d695829d8293689e9319a304839c3a6e0f9f3') + sha256sums=('8371b148aa06e9d0627c9e11668ef2dfdf54c4a9e5ffbee5aee08c3945073618') fi prepare() { diff --git a/pkgbuilds/omazed/.omarchy/package.json b/pkgbuilds/omazed/.omarchy/package.json index 4d2f659..84d820b 100644 --- a/pkgbuilds/omazed/.omarchy/package.json +++ b/pkgbuilds/omazed/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "cbc25a60a791ccda07ed5e8300e8acb267d23007" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://github.com/aps6/omazed.git", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "omazed", + "commit": "cbc25a60a791ccda07ed5e8300e8acb267d23007" + } } diff --git a/pkgbuilds/once-bin/.omarchy/package.json b/pkgbuilds/once-bin/.omarchy/package.json index 55a25c1..a8dbd34 100644 --- a/pkgbuilds/once-bin/.omarchy/package.json +++ b/pkgbuilds/once-bin/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "24d97ea4671538cec0b827fc40b01a3f3fc8ff49" + "source": "local", + "upstream": { + "watch": { + "github": "basecamp/once", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "once-bin", + "commit": "24d97ea4671538cec0b827fc40b01a3f3fc8ff49" + } } diff --git a/pkgbuilds/openai-codex-bin/.omarchy/package.json b/pkgbuilds/openai-codex-bin/.omarchy/package.json index 75046fb..8acb222 100644 --- a/pkgbuilds/openai-codex-bin/.omarchy/package.json +++ b/pkgbuilds/openai-codex-bin/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "134a0e93624d39306f94dc4384dbd102b8d37bb2" + "upstream": { + "watch": { + "github": "openai/codex", + "pattern": "rust-v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "openai-codex-bin", + "commit": "f72ee6f702ebc825c1680241dd599fb890c5a66a" + } } diff --git a/pkgbuilds/openai-codex-bin/PKGBUILD b/pkgbuilds/openai-codex-bin/PKGBUILD index ef7f851..6cdbbb0 100644 --- a/pkgbuilds/openai-codex-bin/PKGBUILD +++ b/pkgbuilds/openai-codex-bin/PKGBUILD @@ -2,7 +2,7 @@ # shellcheck disable=SC2034 # Maintainer: Chmouel Boudjnah pkgname=openai-codex-bin -pkgver=0.153.4 +pkgver=0.154.0 pkgrel=1 pkgdesc="Arch Linux package for OpenAI's Codex CLI - Auto Updated" arch=('x86_64' 'aarch64') @@ -21,10 +21,10 @@ source_x86_64=( "codex-${pkgver}-x86_64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-x86_64-unknown-linux-musl.tar.gz" "codex-code-mode-host-${pkgver}-x86_64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-code-mode-host-x86_64-unknown-linux-musl.tar.gz" ) -sha256sums_x86_64=('f479424eca092484dc40d87ae28c44f4cc40234a60045d6131e493800d814a30' - 'f95830a869590957664bbfc67bccb08773806b693670baf15908176f89b4cd31') -sha256sums_aarch64=('5cda6182bd94c3a30f2eb63a495489ebf7f691fddb14d70f48c6c1a5071b6cde' - 'd8047b8d33370d6090e729d27eb76de60a2686baa1c143c138c9b05dc70d813b') +sha256sums_x86_64=('d7e18b2597ae8f242f5f31ee9e90deef48dbc9edd634d9868fb6435d08c07f02' + 'a68df7cca23c6da7cde175677df7de61c73a234add1333a1254b86d641af01f7') +sha256sums_aarch64=('583b48df32804213bdcd338c2e5adb06b34340821fa757a726cc0a524fa33c27' + '20aefa302c2022b496e32911bf954a5f76c7fd749c6bdb9fbd711e32b66dcbfa') source_aarch64=( "codex-${pkgver}-aarch64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-aarch64-unknown-linux-musl.tar.gz" diff --git a/pkgbuilds/openai-codex-desktop/PKGBUILD b/pkgbuilds/openai-codex-desktop/PKGBUILD index 9e6a496..f875388 100644 --- a/pkgbuilds/openai-codex-desktop/PKGBUILD +++ b/pkgbuilds/openai-codex-desktop/PKGBUILD @@ -5,7 +5,7 @@ # the version and checksums below from that repository's package index. pkgname=openai-codex-desktop -pkgver=26.901.51231 +pkgver=26.908.70816 pkgrel=1 pkgdesc="Official ChatGPT desktop app with Codex" arch=('x86_64' 'aarch64') @@ -71,8 +71,8 @@ source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64}") source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64}") noextract=("${_deb_x86_64}" "${_deb_aarch64}") sha256sums=('b3a4503b5931f102444bc7015c3cf4e40266cf034e0d682bd2a407dc5b3ee58c') -sha256sums_x86_64=('62580188d87c3d3a9369dab7c73b42a8a32518d4df8a2d5bae6466ddeac5c05e') -sha256sums_aarch64=('02a2f5c6cb69509c62abcbdd13c76b139cdb2ca9edde7537239ddde024077ea0') +sha256sums_x86_64=('10ed0c1a880b9975d1f185bf7911a7f514e06b9863cd4ed9561d40063617c854') +sha256sums_aarch64=('d3ec8f1d73b92f203715c26dbf2e0e64375192d00ddaf26f7fbade7777124de8') package() { cd "${srcdir}" diff --git a/pkgbuilds/openclaw/PKGBUILD b/pkgbuilds/openclaw/PKGBUILD index 2da0c56..71acd80 100644 --- a/pkgbuilds/openclaw/PKGBUILD +++ b/pkgbuilds/openclaw/PKGBUILD @@ -7,7 +7,7 @@ # upstream's release cadence outruns the AUR. pkgname=openclaw -pkgver=2026.9.2 +pkgver=2026.9.4 pkgrel=1 pkgdesc='Multi-channel AI gateway with extensible messaging integrations' arch=(x86_64 aarch64) @@ -29,7 +29,7 @@ optdepends=( 'go: for installing skill tools not packaged for Arch' ) source=($pkgname-$pkgver.tgz::https://registry.npmjs.org/$pkgname/-/$pkgname-$pkgver.tgz) -sha256sums=('3431f4cd2d8dbd6b936def2694ac27e19fa0256295cf4ada0f652ecf1c9ee520') +sha256sums=('4f1f656770461d4677dea755b1899cba12b912b06798c89a59e2f0c18688b761') options=(!debug !strip) install=$pkgname.install noextract=($pkgname-$pkgver.tgz) diff --git a/pkgbuilds/perplexity/PKGBUILD b/pkgbuilds/perplexity/PKGBUILD index bbfb058..8174de1 100644 --- a/pkgbuilds/perplexity/PKGBUILD +++ b/pkgbuilds/perplexity/PKGBUILD @@ -5,8 +5,8 @@ # from that repository's package index. pkgname=perplexity -pkgver=26.9.1+build61614 -pkgrel=2 +pkgver=26.9.4+build72244 +pkgrel=1 pkgdesc="Official Perplexity desktop app" arch=('x86_64' 'aarch64') url="https://www.perplexity.ai" @@ -81,8 +81,8 @@ source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64//+/%2B}") source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64//+/%2B}") noextract=("${_deb_x86_64}" "${_deb_aarch64}") sha256sums=('b180ca6fbd268712a277a34d4215f176001c0031a4f6360686458623f47fad65') -sha256sums_x86_64=('2fc95f5500335e661af6901bbad5c95e8805cc9f43d9f0b979787c7249c15c33') -sha256sums_aarch64=('917562bad5ab319612c22fca49773cdee163b2e7c9f0c3dd5569fc4878f10237') +sha256sums_x86_64=('fe64a09a82f8e6b0e3de5637ee768dc5aee94a326af795dce8bcbaf5c7660409') +sha256sums_aarch64=('855330d95401f8e8360f846031ebbefe84bbe0fd9d6ec7814f0e79ef5c4c8bc6') package() { cd "${srcdir}" diff --git a/pkgbuilds/pinta/.omarchy/package.json b/pkgbuilds/pinta/.omarchy/package.json new file mode 100644 index 0000000..aad5b86 --- /dev/null +++ b/pkgbuilds/pinta/.omarchy/package.json @@ -0,0 +1,9 @@ +{ + "source": "local", + "upstream": { + "watch": { + "github": "PintaProject/Pinta", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)" + } + } +} diff --git a/pkgbuilds/pinta/PKGBUILD b/pkgbuilds/pinta/PKGBUILD new file mode 100644 index 0000000..5f1475f --- /dev/null +++ b/pkgbuilds/pinta/PKGBUILD @@ -0,0 +1,34 @@ +# Pinta for Arch Linux ARM. The official repositories do not supply its +# .NET dependencies on ARM; use the dotnet-core-bin split package family. + +pkgname=pinta +pkgver=3.1.2 +pkgrel=1 +pkgdesc="Drawing/editing program modeled after Paint.NET. It's goal is to provide a simplified alternative to GIMP for casual users" + +arch=('aarch64') +license=('MIT') +url="https://pinta-project.com" + +makedepends=('pkgconf' 'intltool' 'dotnet-sdk-bin') +depends=('dotnet-runtime-bin' 'dotnet-host-bin' 'libadwaita' 'hicolor-icon-theme' 'webp-pixbuf-loader') + +conflicts=($pkgname-git) + +source=("https://github.com/PintaProject/Pinta/releases/download/${pkgver}/pinta-${pkgver}.tar.gz") +sha256sums=('27f55a026b51f6f18197e6bcfdd7c79544e41529d166daf6c93a1117e9ed45f0') + +build() { + cd "${srcdir}/pinta-${pkgver}" + ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var + make PINTA_BUILD_OPTS='--configuration Release -p:BuildTranslations=true -p:RuntimeIdentifier=linux-arm64' +} + +package() { + cd "${srcdir}/pinta-${pkgver}" + + make DESTDIR="${pkgdir}" install + + install -Dm644 -t "${pkgdir}/usr/share/doc/${pkgname}/" readme.md + install -Dm644 -t "${pkgdir}/usr/share/licenses/${pkgname}/" license-*.txt +} diff --git a/pkgbuilds/python-mediapipe/.omarchy/package.json b/pkgbuilds/python-mediapipe/.omarchy/package.json index 6300a69..144d408 100644 --- a/pkgbuilds/python-mediapipe/.omarchy/package.json +++ b/pkgbuilds/python-mediapipe/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "99700efde7dc0200719dfed1a1147ace2cd7b0c0" + "upstream": { + "watch": { + "github": "google-ai-edge/mediapipe", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "python-mediapipe", + "commit": "99700efde7dc0200719dfed1a1147ace2cd7b0c0" + } } diff --git a/pkgbuilds/python-mediapipe/.omarchy/patches/verify-bazel-source.patch b/pkgbuilds/python-mediapipe/.omarchy/patches/verify-bazel-source.patch deleted file mode 100644 index 6cd9fb3..0000000 --- a/pkgbuilds/python-mediapipe/.omarchy/patches/verify-bazel-source.patch +++ /dev/null @@ -1,42 +0,0 @@ ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -2,6 +2,7 @@ - # Maintainer: Hu Butui - - _pkgname=mediapipe -+_bazel_version=7.4.1 - pkgname=python-mediapipe - pkgver=1.0.0 - pkgrel=2 -@@ -27,10 +28,10 @@ makedepends=( - python-installer - python-setuptools - python-wheel -- wget - ) - - source=("${_pkgname}-${pkgver}.tar.gz::https://github.com/google-ai-edge/mediapipe/archive/refs/tags/v${pkgver}.tar.gz" -+ "bazel-${_bazel_version}-linux-x86_64::https://github.com/bazelbuild/bazel/releases/download/${_bazel_version}/bazel-${_bazel_version}-linux-x86_64" - "0004-use-opencv-headers.patch" - "0005-set-hermetic-python-version-and-disable-odml-converter.patch" - "0006-opencv5-geometry-header.patch" -@@ -38,6 +39,7 @@ source=("${_pkgname}-${pkgver}.tar.gz::https://github.com/google-ai-edge/medi - "0007-bump-rules-java.patch" - ) - sha256sums=('6343314dad0f4112610807ca723eee63feea00711dc2be9360928b6f0a5eba12' -+ 'c97f02133adce63f0c28678ac1f21d65fa8255c80429b588aeeba8a1fac6202b' - 'd18e88a217a00dc77cf2bfaa1d33b5fbc912deed5df686b0d2455fb0db75a677' - '62cbd43346e7a7705127656a74bec44852e5bbfc006c4723005fdd23af5258db' - 'fb082d88d9cca47534ae01fca676a3afdb909bfc82d52c97cf0eaedf34571d5f' -@@ -47,9 +49,8 @@ sha256sums=('6343314dad0f4112610807ca723eee63feea00711dc2be9360928b6f0a5eba12' - prepare() { - # bazel in the ArchLinux is not working - mkdir -p ${srcdir}/bin -- bazel_version=$(cat ${srcdir}/${_pkgname}-${pkgver}/.bazelversion) -- wget https://github.com/bazelbuild/bazel/releases/download/${bazel_version}/bazel-${bazel_version}-linux-x86_64 -O ${srcdir}/bin/bazel -- chmod +x ${srcdir}/bin/bazel -+ install -Dm755 "${srcdir}/bazel-${_bazel_version}-linux-x86_64" \ -+ "${srcdir}/bin/bazel" - export PATH=${srcdir}/bin:${PATH} - cd "${srcdir}/${_pkgname}-${pkgver}" - patch -p1 -i "${srcdir}/0004-use-opencv-headers.patch" diff --git a/pkgbuilds/python-sounddevice/.omarchy/package.json b/pkgbuilds/python-sounddevice/.omarchy/package.json index e50ad7b..a7353c7 100644 --- a/pkgbuilds/python-sounddevice/.omarchy/package.json +++ b/pkgbuilds/python-sounddevice/.omarchy/package.json @@ -1,5 +1,13 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "c3a5320f95ada9ccebd629b5722276e645388ced" + "upstream": { + "watch": { + "pypi": "sounddevice" + } + }, + "origin": { + "aur": "python-sounddevice", + "commit": "c3a5320f95ada9ccebd629b5722276e645388ced" + } } diff --git a/pkgbuilds/python-sounddevice/PKGBUILD b/pkgbuilds/python-sounddevice/PKGBUILD index f5552f6..19b71ab 100644 --- a/pkgbuilds/python-sounddevice/PKGBUILD +++ b/pkgbuilds/python-sounddevice/PKGBUILD @@ -3,7 +3,7 @@ # Contributor: HΓ₯vard Pettersson pkgname=python-sounddevice -pkgver=0.5.5 +pkgver=0.5.6 pkgrel=1 pkgdesc='Record and play back sound' url='https://python-sounddevice.rtfd.io/' @@ -13,7 +13,7 @@ depends=(portaudio python-cffi) makedepends=(python-setuptools) optdepends=('python-numpy: to play back and record NumPy arrays') source=("https://files.pythonhosted.org/packages/source/s/sounddevice/sounddevice-$pkgver.tar.gz") -b2sums=('25da7f11bb20f115c828bfb4de00c7ced40224126b5da3a08027c0459d40e1300a4747305c25dab480f73db87c9ad6f0982d0af6adf8c3cc4d5de2e0e9ffaaff') +b2sums=('0a1becc9729e41cc7acd268ba35ef857390f04348e9e48fa3f14f547f9926b7c4731e1063ed33cc054518ed3e7d83099e663efb7b76cf475046e53fcdd0858cb') package() { cd sounddevice-$pkgver diff --git a/pkgbuilds/python-terminaltexteffects/.omarchy/package.json b/pkgbuilds/python-terminaltexteffects/.omarchy/package.json index f305a7c..808589a 100644 --- a/pkgbuilds/python-terminaltexteffects/.omarchy/package.json +++ b/pkgbuilds/python-terminaltexteffects/.omarchy/package.json @@ -1,4 +1,12 @@ { - "source": "aur", - "upstream_commit": "efbe1ae0019e7c44c9e1c7e22904f9fc77150b6a" + "source": "local", + "upstream": { + "watch": { + "pypi": "terminaltexteffects" + } + }, + "origin": { + "aur": "python-terminaltexteffects", + "commit": "efbe1ae0019e7c44c9e1c7e22904f9fc77150b6a" + } } diff --git a/pkgbuilds/quickshell-git/.omarchy/package.json b/pkgbuilds/quickshell-git/.omarchy/package.json index 2bb526a..457456e 100644 --- a/pkgbuilds/quickshell-git/.omarchy/package.json +++ b/pkgbuilds/quickshell-git/.omarchy/package.json @@ -1,6 +1,5 @@ { - "source": "aur", - "upstream_commit": "2494139bce7b7fc71372da00b68fb745a2c72d90", + "source": "local", "sync": false, "rebuild_on": [ "qt6-base", @@ -11,5 +10,9 @@ "qt6-base": "6.11.2-3", "qt6-declarative": "6.11.2-1", "qt6-wayland": "6.11.2-1" + }, + "origin": { + "aur": "quickshell-git", + "commit": "2494139bce7b7fc71372da00b68fb745a2c72d90" } } diff --git a/pkgbuilds/rustdesk/.omarchy/package.json b/pkgbuilds/rustdesk/.omarchy/package.json index 6fd38a8..ec5cb2b 100644 --- a/pkgbuilds/rustdesk/.omarchy/package.json +++ b/pkgbuilds/rustdesk/.omarchy/package.json @@ -1,4 +1,16 @@ { - "source": "aur", - "upstream_commit": "b19996732fccf6c9d436afccb704d65b320cf837" + "source": "local", + "upstream": { + "watch": { + "github": "rustdesk/rustdesk", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)", + "submodules": { + "_hbb_commit": "libs/hbb_common" + } + } + }, + "origin": { + "aur": "rustdesk", + "commit": "b19996732fccf6c9d436afccb704d65b320cf837" + } } diff --git a/pkgbuilds/rustdesk/PKGBUILD b/pkgbuilds/rustdesk/PKGBUILD index d5839fa..27fb9fb 100644 --- a/pkgbuilds/rustdesk/PKGBUILD +++ b/pkgbuilds/rustdesk/PKGBUILD @@ -149,27 +149,13 @@ DLAGENTS="${DLAGENTS[@]/curl /curl -L}" set -u _pkgname='rustdesk' pkgname="${_pkgname}" -_pkgver='1.4.9' -pkgver="${_pkgver//-/.}" -pkgrel=1 +pkgver=1.4.9 +_pkgver=$pkgver +pkgrel=2 _sfx='' #_sfx='-pr1-998b758' -_HBB=( # dates are retrieved from git fetch; tig. Every version gets a specific hbb. - '1.3.7:20250120-49c6b24a7a8c39d4448e07b743007ef1a3febd43' - '1.3.8:20250223-7cf11f7b771e27ecbd14fd1dd0ced55a64f40eb5' - '1.3.9:20250328-81b932b7bfa2ff8bc60189625fd6538db2fa9ea1' - '1.4.0:20250509-6e556f7e1751a3a709cd5cca0df7268ba3cb1c48' - '1.4.1:20250718-f91459c4ab80fc3cfdef0882b2af51f984bc914c' - '1.4.2:20250904-9e7696c7d4e346508ba68e801a53c6d1f1748fb5' - '1.4.3:20251008-5ed0afde0841659e2fb37ae7acaddc005fa1a8d3' - '1.4.4:20251117-a86eda749e6fa33c282bab680e6b504d3ad87539' - '1.4.5:20251117-073403edbf1fffcb3acfe8cbe7582ee873b23398' - '1.4.6:20260302-48c37de3e6c4e399af6f51ca20e8e3e1fd037976' - '1.4.7:20260601-df6badca5bf81b4e9836256cf8e31c993ad70dd1' - '1.4.8:20260604-387603f47cbb15c0d3dc3d67ae3396d3eb707daf' - '1.4.9:20260702-7e1c392c62d39c364127307cd408421dd5f8cfb0' -) -_pkgverhbb="$(_fn_VCL "${_pkgver}" -eq "${_HBB[@]}")"; unset _HBB; test "$(_vercmp "${_pkgver}" '1.3.7')" -lt 0 -o ! -z "${_pkgverhbb}" || exit 1 +_hbb_commit=7e1c392c62d39c364127307cd408421dd5f8cfb0 +_pkgverhbb=${_hbb_commit} pkgdesc='Yet another remote desktop software, written in Rust. Works out of the box, no configuration required. Great alternative to TeamViewer and AnyDesk!' arch=('x86_64') url='https://rustdesk.com/' diff --git a/pkgbuilds/schist-bin/PKGBUILD b/pkgbuilds/schist-bin/PKGBUILD index 06ee0b3..3df4afc 100644 --- a/pkgbuilds/schist-bin/PKGBUILD +++ b/pkgbuilds/schist-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Infrawrench LLC pkgname=schist-bin -pkgver=0.12.0 +pkgver=0.13.0 pkgrel=1 # Upstream's own package release, embedded in the asset name. It is # packages.sh's "release=" and only moves when the packaging changes under @@ -32,8 +32,8 @@ options=(!strip !debug) # script. source_x86_64=("$url/releases/download/v$pkgver/schist-$pkgver-$_relver-x86_64.pkg.tar.zst") source_aarch64=("$url/releases/download/v$pkgver/schist-$pkgver-$_relver-aarch64.pkg.tar.zst") -sha256sums_x86_64=('baff8848274f0121911a6408fd3dd082c5ab023c0ae5c9e5be35968c80beb069') -sha256sums_aarch64=('a7a4f062289a140ff0aa5c0c9c4b3cc9f386759e3bfe929a0382de2d20c04eda') +sha256sums_x86_64=('6070dd346ebd69898437d6dfb6c071cca80c82045581feeeba6542264ae23096') +sha256sums_aarch64=('d5eef7b737b35d32a31f3ae34de0a16ab3178348b44245a8096b8d70fe7b03c1') package() { # makepkg has already extracted the payload into srcdir; its .PKGINFO diff --git a/pkgbuilds/spotify/.omarchy/package.json b/pkgbuilds/spotify/.omarchy/package.json index 78e3705..b3bac26 100644 --- a/pkgbuilds/spotify/.omarchy/package.json +++ b/pkgbuilds/spotify/.omarchy/package.json @@ -1,5 +1,18 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "0af779c21295d2875d06b07f8760a0e98256f3e4" + "upstream": { + "watch": { + "debian": "https://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages", + "package": "spotify-client", + "pattern": "1:(?P[0-9]+(?:\\.[0-9]+)*)\\.(?Pg[0-9a-f]+)", + "variables": { + "_commit": "{commit}" + } + } + }, + "origin": { + "aur": "spotify", + "commit": "0af779c21295d2875d06b07f8760a0e98256f3e4" + } } diff --git a/pkgbuilds/spotify/PKGBUILD b/pkgbuilds/spotify/PKGBUILD index 1255bb1..3594aa5 100644 --- a/pkgbuilds/spotify/PKGBUILD +++ b/pkgbuilds/spotify/PKGBUILD @@ -9,7 +9,7 @@ pkgname=spotify pkgver='1.2.96.518' epoch=1 _commit=g366879e1 -pkgrel=2 +pkgrel=3 pkgdesc='A proprietary music streaming service' arch=('x86_64') license=('custom') @@ -23,17 +23,17 @@ options=('!strip') # NOTE: We switched from stable to testing on 18th march, as the spotify # stable repository is always outdated. Testing seems to be in sync with snap: # https://snapcraft.io/spotify -# http://repository.spotify.com/dists/testing/Release -# http://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages -# http://repository.spotify.com/dists/testing/Release.gpg -source=("${pkgname}-${pkgver}-${_commit}-x86_64.deb::http://repository.spotify.com/pool/non-free/s/spotify-client/spotify-client_${pkgver}.${_commit}_amd64.deb" +# https://repository.spotify.com/dists/testing/Release +# https://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages +# https://repository.spotify.com/dists/testing/Release.gpg +source=("${pkgname}-${pkgver}-${_commit}-x86_64.deb::https://repository.spotify.com/pool/non-free/s/spotify-client/spotify-client_${pkgver}.${_commit}_amd64.deb" "spotify.sh" "spotify.protocol" "LICENSE" # GPG signature check - "${pkgname}-${pkgver}-${pkgrel}-Release::http://repository.spotify.com/dists/testing/Release" - "${pkgname}-${pkgver}-${pkgrel}-Release.sig::http://repository.spotify.com/dists/testing/Release.gpg" - "${pkgname}-${pkgver}-${pkgrel}-x86_64-Packages::http://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages") + "${pkgname}-${pkgver}-${pkgrel}-Release::https://repository.spotify.com/dists/testing/Release" + "${pkgname}-${pkgver}-${pkgrel}-Release.sig::https://repository.spotify.com/dists/testing/Release.gpg" + "${pkgname}-${pkgver}-${pkgrel}-x86_64-Packages::https://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages") sha512sums=('98d25bd748258ca8b8357313cd35c7188bb0b26ed2d4390b0208473aa9fa3491a095559762f8ba06ec0828d73e05ee8fb488f0f357340191da4c65dd1f93c519' 'da48b628a4ea925dd8521133ebf364b261b11aed252d264dde6605d915cdb631919ffe672c58534bcdb60869e5d87a49a60a8198780b99517123f0031e83fdb1' '999abe46766a4101e27477f5c9f69394a4bb5c097e2e048ec2c6cb93dfa1743eb436bde3768af6ba1b90eaac78ea8589d82e621f9cbe7d9ab3f41acee6e8ca20' diff --git a/pkgbuilds/strata/PKGBUILD b/pkgbuilds/strata/PKGBUILD index a9313a4..a94b4ed 100644 --- a/pkgbuilds/strata/PKGBUILD +++ b/pkgbuilds/strata/PKGBUILD @@ -1,10 +1,10 @@ pkgname=strata -pkgver=0.11.2 +pkgver=0.18.0 pkgrel=1 pkgdesc='Fast, keyboard-first file manager for modern Linux desktops' arch=('x86_64' 'aarch64') url='https://github.com/lgse/strata' -license=('GPL-3.0-or-later') +license=('MIT') depends=( 'bash' @@ -20,6 +20,7 @@ depends=( 'graphene' 'gst-libav' 'gst-plugins-good' + 'gvfs' 'gtk4' 'gtksourceview5' 'hicolor-icon-theme' @@ -38,7 +39,7 @@ conflicts=('strata-git') options=('!debug' '!lto') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('cfc8b1c3a9a5fde518c5862b58af7722e942b5f394c60fd53822dd642c1f30a0') +sha256sums=('82cfa5701f537e53d235b30e9450e1fcd5f2e7ccfea64ba317941e8e8766094b') prepare() { cd "$pkgname-$pkgver" @@ -73,7 +74,18 @@ check() { STRATA_BUILD_COMMIT=$(<.build-commit) export STRATA_RELEASE_TAG="v$pkgver" export STRATA_BUILD_KIND=stable - cargo test --frozen --release --all-targets --all-features + # Search fixtures depend on directory enumeration order and fail on overlayfs. + # Run them on tmpfs; other suites execute fixtures, so cannot use noexec /dev/shm. + local search_tmp + search_tmp=$(mktemp -d /dev/shm/strata-tests.XXXXXXXX) || return 1 + local test_status=0 + TMPDIR="$search_tmp" cargo test --frozen --release --all-targets --all-features \ + services::search::tests:: -- --test-threads=1 || test_status=$? + rm -rf -- "$search_tmp" + (( test_status == 0 )) || return "$test_status" + + cargo test --frozen --release --all-targets --all-features \ + -- --test-threads=1 --skip services::search::tests:: } package() { diff --git a/pkgbuilds/sublime-text-4/.omarchy/package.json b/pkgbuilds/sublime-text-4/.omarchy/package.json index 40e745c..94bd8c6 100644 --- a/pkgbuilds/sublime-text-4/.omarchy/package.json +++ b/pkgbuilds/sublime-text-4/.omarchy/package.json @@ -1,5 +1,15 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "a00e0a71a6e923093b855e5fd9ee10441b362271" + "upstream": { + "watch": { + "json": "https://www.sublimetext.com/updates/4/stable_update_check", + "path": "latest_version", + "version": "4.{version}" + } + }, + "origin": { + "aur": "sublime-text-4", + "commit": "a00e0a71a6e923093b855e5fd9ee10441b362271" + } } diff --git a/pkgbuilds/sunshine/.omarchy/package.json b/pkgbuilds/sunshine/.omarchy/package.json index 74758a6..fa0de1a 100644 --- a/pkgbuilds/sunshine/.omarchy/package.json +++ b/pkgbuilds/sunshine/.omarchy/package.json @@ -1,4 +1,16 @@ { - "source": "aur", - "upstream_commit": "42bc63ecad9478b35338d34ed923887bdbd81b29" + "source": "local", + "upstream": { + "watch": { + "github": "LizardByte/Sunshine", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)", + "variables": { + "_commit": "{commit}" + } + } + }, + "origin": { + "aur": "sunshine", + "commit": "42bc63ecad9478b35338d34ed923887bdbd81b29" + } } diff --git a/pkgbuilds/sunshine/.omarchy/patches/arm-dependencies.patch b/pkgbuilds/sunshine/.omarchy/patches/arm-dependencies.patch deleted file mode 100644 index 7b40c4c..0000000 --- a/pkgbuilds/sunshine/.omarchy/patches/arm-dependencies.patch +++ /dev/null @@ -1,56 +0,0 @@ ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -38,7 +38,6 @@ - 'libcap' - 'libdrm' - 'libevdev' -- 'libmfx' - 'libpipewire' - 'libpulse' - 'libva' -@@ -58,6 +57,9 @@ - 'which' - ) - -+# Intel Media SDK is unavailable on ARM. -+depends_x86_64=('libmfx') -+ - makedepends=( - 'appstream' - 'appstream-glib' -@@ -72,6 +74,9 @@ - 'shaderc' - ) - -+# Use distro Vulkan headers instead of the bundled SDK's recursive sources. -+makedepends_aarch64=('vulkan-headers') -+ - checkdepends=( - 'gcovr' - 'imagemagick' -@@ -119,7 +124,13 @@ - - prepare() { - cd "$pkgname" -- git submodule update --recursive --init --depth 1 -+ if [[ $CARCH == aarch64 ]]; then -+ git submodule update --init --depth 1 -+ # Fetch Moonlight's nested sources without pulling in the bundled SDK. -+ git -C third-party/moonlight-common-c submodule update --recursive --init --depth 1 -+ else -+ git submodule update --recursive --init --depth 1 -+ fi - } - - build() { -@@ -149,6 +160,10 @@ - -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' - ) - -+ if [[ $CARCH == aarch64 ]]; then -+ _cmake_options+=(-D SUNSHINE_SYSTEM_VULKAN_HEADERS=ON) -+ fi -+ - if [[ "${_use_cuda::1}" != "t" ]]; then - _cmake_options+=(-DSUNSHINE_ENABLE_CUDA=OFF -DCUDA_FAIL_ON_MISSING=OFF) - else diff --git a/pkgbuilds/sunshine/PKGBUILD b/pkgbuilds/sunshine/PKGBUILD index 79452c4..170bb51 100644 --- a/pkgbuilds/sunshine/PKGBUILD +++ b/pkgbuilds/sunshine/PKGBUILD @@ -6,11 +6,11 @@ : "${_support_headless_testing:=false}" : "${_use_cuda:=detect}" # nvenc -: "${_commit:=cb72dffa3233c5815cd5ba88f09f049dd679ba75}" +_commit=63d35f702ee9e362e43263742981836ec0710384 pkgname='sunshine' -pkgver=2026.906.222525 -pkgrel=1.1 +pkgver=2026.914.233613 +pkgrel=1 pkgdesc="Self-hosted game stream host for Moonlight" arch=('x86_64' 'aarch64') url=https://app.lizardbyte.dev/Sunshine @@ -135,7 +135,7 @@ prepare() { build() { export BRANCH="master" - export BUILD_VERSION="2026.906.222525" + export BUILD_VERSION="${pkgver}" export COMMIT="${_commit}" export CC="gcc${_gcc_env_suffix}" @@ -155,9 +155,9 @@ build() { -D CMAKE_INSTALL_PREFIX=/usr -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -D SUNSHINE_ASSETS_DIR="share/sunshine" - -D SUNSHINE_PUBLISHER_NAME='LizardByte' - -D SUNSHINE_PUBLISHER_WEBSITE='https://app.lizardbyte.dev' - -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' + -D SUNSHINE_PUBLISHER_NAME='Omarchy' + -D SUNSHINE_PUBLISHER_WEBSITE='https://omarchy.org' + -D SUNSHINE_PUBLISHER_ISSUE_URL='https://github.com/omacom/omarchy-pkgs/issues' ) if [[ $CARCH == aarch64 ]]; then diff --git a/pkgbuilds/supergfxctl/.omarchy/package.json b/pkgbuilds/supergfxctl/.omarchy/package.json index a48e412..f72c893 100644 --- a/pkgbuilds/supergfxctl/.omarchy/package.json +++ b/pkgbuilds/supergfxctl/.omarchy/package.json @@ -1,4 +1,8 @@ { - "source": "aur", - "sync": false + "source": "local", + "sync": false, + "origin": { + "aur": "supergfxctl", + "commit": null + } } diff --git a/pkgbuilds/t3code-bin/PKGBUILD b/pkgbuilds/t3code-bin/PKGBUILD index fdff11f..fe90494 100644 --- a/pkgbuilds/t3code-bin/PKGBUILD +++ b/pkgbuilds/t3code-bin/PKGBUILD @@ -7,7 +7,7 @@ # from the release feed the app updates itself from. pkgname=t3code-bin -pkgver=0.0.39 +pkgver=0.0.42 pkgrel=1 pkgdesc="Open-source control plane for coding agents" arch=('x86_64') @@ -61,7 +61,7 @@ noextract=("${_appimage}") sha256sums=('cb905ff341372ef2ef6e402cf485959f8bd1df8f0efebee4cda1afdd5e6abc0a' 'c5b3f2a9f0b14b12cfd973b79319f0f018b7ae49a1d43d8ca346100c3f7de28f' '935d8f2af0c703f9c39517ee57cc4930b19d02d533be930b63f0e82f93614b43') -sha256sums_x86_64=('4f78c9939c0386f9be5a2d8e9baf9644af497344015817a36fea8be84981bda8') +sha256sums_x86_64=('8dc1fccdabc2ed3a59a3944cc772ef11931b9351401c0963ed305d5f96e3cdf4') prepare() { chmod +x "${srcdir}/${_appimage}" diff --git a/pkgbuilds/ttf-ia-writer/.omarchy/package.json b/pkgbuilds/ttf-ia-writer/.omarchy/package.json index 34f9737..2fec430 100644 --- a/pkgbuilds/ttf-ia-writer/.omarchy/package.json +++ b/pkgbuilds/ttf-ia-writer/.omarchy/package.json @@ -1,4 +1,17 @@ { - "source": "aur", - "upstream_commit": "e9aa94080338d7a7f83a561ce64bf30ab4fb24e4" + "source": "local", + "upstream": { + "watch": { + "git_branch": "https://github.com/iaolo/iA-Fonts.git", + "branch": "master", + "version": "{date}.r{count}", + "variables": { + "_commit": "{commit}" + } + } + }, + "origin": { + "aur": "ttf-ia-writer", + "commit": "e9aa94080338d7a7f83a561ce64bf30ab4fb24e4" + } } diff --git a/pkgbuilds/ttf-ia-writer/PKGBUILD b/pkgbuilds/ttf-ia-writer/PKGBUILD index fc23d45..5f73e8e 100644 --- a/pkgbuilds/ttf-ia-writer/PKGBUILD +++ b/pkgbuilds/ttf-ia-writer/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: AdriΓ‘n PΓ©rez de Castro pkgname=ttf-ia-writer pkgdesc='Subset of the fonts provided by iA Writer' -pkgver=20181225 +pkgver=20230616.r62 pkgrel=1 url=https://github.com/iaolo/iA-Fonts conflicts=(ttf-ia-writer-duospace) @@ -9,7 +9,10 @@ provides=(${conflicts[@]}) license=(custom:OFL) arch=(any) -_baseurl="${url}/raw/b337fe1a4c93026268f70e4b2678371f3e89e2ce/" +_commit=f32c04c3058a75d7ce28919ce70fe8800817491b +_baseurl="${url}/raw/${_commit}/" +# Duospace was removed upstream; retain the shipped legacy family at its last pin. +_legacy_baseurl="${url}/raw/b337fe1a4c93026268f70e4b2678371f3e89e2ce/" source=("${_baseurl}/iA%20Writer%20Mono/Static/iAWriterMonoS-Bold.ttf" "${_baseurl}/iA%20Writer%20Mono/Static/iAWriterMonoS-BoldItalic.ttf" "${_baseurl}/iA%20Writer%20Mono/Static/iAWriterMonoS-Italic.ttf" @@ -22,10 +25,10 @@ source=("${_baseurl}/iA%20Writer%20Mono/Static/iAWriterMonoS-Bold.ttf" "${_baseurl}/iA%20Writer%20Duo/Static/iAWriterDuoS-BoldItalic.ttf" "${_baseurl}/iA%20Writer%20Duo/Static/iAWriterDuoS-Italic.ttf" "${_baseurl}/iA%20Writer%20Duo/Static/iAWriterDuoS-Regular.ttf" - "${_baseurl}/iA%20Writer%20Duospace/TTF%20(PC)/iAWriterDuospace-Bold.ttf" - "${_baseurl}/iA%20Writer%20Duospace/TTF%20(PC)/iAWriterDuospace-BoldItalic.ttf" - "${_baseurl}/iA%20Writer%20Duospace/TTF%20(PC)/iAWriterDuospace-Italic.ttf" - "${_baseurl}/iA%20Writer%20Duospace/TTF%20(PC)/iAWriterDuospace-Regular.ttf") + "${_legacy_baseurl}/iA%20Writer%20Duospace/TTF%20(PC)/iAWriterDuospace-Bold.ttf" + "${_legacy_baseurl}/iA%20Writer%20Duospace/TTF%20(PC)/iAWriterDuospace-BoldItalic.ttf" + "${_legacy_baseurl}/iA%20Writer%20Duospace/TTF%20(PC)/iAWriterDuospace-Italic.ttf" + "${_legacy_baseurl}/iA%20Writer%20Duospace/TTF%20(PC)/iAWriterDuospace-Regular.ttf") package () { install -m644 -D -t "${pkgdir}/usr/share/fonts/${pkgname}" *.ttf diff --git a/pkgbuilds/tuxedo-drivers-nocompatcheck-dkms/.omarchy/package.json b/pkgbuilds/tuxedo-drivers-nocompatcheck-dkms/.omarchy/package.json index d150d8e..b86b164 100644 --- a/pkgbuilds/tuxedo-drivers-nocompatcheck-dkms/.omarchy/package.json +++ b/pkgbuilds/tuxedo-drivers-nocompatcheck-dkms/.omarchy/package.json @@ -1,4 +1,18 @@ { - "source": "aur", - "upstream_commit": "58203050bad3d23406a1e2e4357f1b7be2c6a45b" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://gitlab.com/kronerm/tuxedo-drivers-nocompatcheck.git", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)-patched\\.(?P[0-9]+)", + "variables": { + "_patchrel": "{build}" + }, + "revision": "{build}", + "revision_variable": "_patchrel" + } + }, + "origin": { + "aur": "tuxedo-drivers-nocompatcheck-dkms", + "commit": "58203050bad3d23406a1e2e4357f1b7be2c6a45b" + } } diff --git a/pkgbuilds/tuxedo-drivers-nocompatcheck-dkms/PKGBUILD b/pkgbuilds/tuxedo-drivers-nocompatcheck-dkms/PKGBUILD index b6a78fa..c8c30ea 100644 --- a/pkgbuilds/tuxedo-drivers-nocompatcheck-dkms/PKGBUILD +++ b/pkgbuilds/tuxedo-drivers-nocompatcheck-dkms/PKGBUILD @@ -4,7 +4,8 @@ _pkgname=tuxedo-drivers _reponame=${_pkgname}-nocompatcheck pkgname=${_reponame}-dkms pkgver=4.22.1 -pkgrel=1 +pkgrel=2 +_patchrel=1 pkgdesc="TUXEDO Computers kernel module drivers. Compatibility check disabled - works when using identical models from other brands, example being TUXEDO Pulse 14 Gen 4 and Schenker Via 14 Pro (M24)" url="https://gitlab.com/kronerm/${_reponame}" license=("GPL-2.0-or-later") @@ -29,14 +30,14 @@ provides=('tuxedo-drivers-dkms' 'ite_8297' 'ite_829x') conflicts=('tuxedo-drivers-dkms' 'tuxedo-keyboard-dkms' 'tuxedo-keyboard-ite-dkms') -source=($pkgname-$pkgver.tar.gz::https://gitlab.com/kronerm/${_reponame}/-/archive/v${pkgver}-patched.${pkgrel}/tuxedo-drivers-v${pkgver}-patched.${pkgrel}.tar.gz dkms.conf) +source=($pkgname-$pkgver.tar.gz::https://gitlab.com/kronerm/${_reponame}/-/archive/v${pkgver}-patched.${_patchrel}/tuxedo-drivers-v${pkgver}-patched.${_patchrel}.tar.gz dkms.conf) sha256sums=('739e101123cdd44f619c5533dc6509608be517416a5e19807ca012a9f98e59f2' '0b1f2ceb729b1944504d1227e39b202159076846aaa38f8b6237b9535e543a21') sha512sums=('d8fb9a24c8722e27d62267b9e0dfffa131ceaae26e2fc89065badb1fd1545cc617a5be338492592f6a2cd25f8f2ac6cc25b819856b88529e373bb0c8b4bfa145' 'dda443b5966221a355fecdae7a2eb23346666cbcf93707d2fff660a32ec75cee592ae211c8d22e55413d530f59e8667292a0346d8bf6b2d280b45f3bc09efafd') package() { - extracted_source_dir=("$_reponame"-v"$pkgver"-patched."$pkgrel"-*/) + extracted_source_dir=("$_reponame"-v"$pkgver"-patched."$_patchrel"-*/) target_dkms_dir="$pkgdir"/usr/src/"$_pkgname"-v"$pkgver" install -Dm644 "$srcdir"/dkms.conf "$target_dkms_dir"/dkms.conf diff --git a/pkgbuilds/typora/.omarchy/package.json b/pkgbuilds/typora/.omarchy/package.json index 3c3fcb9..fc0627d 100644 --- a/pkgbuilds/typora/.omarchy/package.json +++ b/pkgbuilds/typora/.omarchy/package.json @@ -1,4 +1,14 @@ { - "source": "aur", - "upstream_commit": "029ff738d5928db82edfa34fc365fcb6743a74e5" + "source": "local", + "upstream": { + "watch": { + "debian": "https://downloads.typora.io/linux/Packages", + "package": "typora", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)(?:-[0-9]+)?" + } + }, + "origin": { + "aur": "typora", + "commit": "029ff738d5928db82edfa34fc365fcb6743a74e5" + } } diff --git a/pkgbuilds/ufw-docker/.omarchy/package.json b/pkgbuilds/ufw-docker/.omarchy/package.json index 9e3761d..fcd8057 100644 --- a/pkgbuilds/ufw-docker/.omarchy/package.json +++ b/pkgbuilds/ufw-docker/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "76e18ac8c40a4e8f018f4b35289718a29eca3d6d" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://github.com/chaifeng/ufw-docker.git", + "pattern": "(?P[0-9]{6})" + } + }, + "origin": { + "aur": "ufw-docker", + "commit": "76e18ac8c40a4e8f018f4b35289718a29eca3d6d" + } } diff --git a/pkgbuilds/vi/.omarchy/package.json b/pkgbuilds/vi/.omarchy/package.json index a07823f..5354a14 100644 --- a/pkgbuilds/vi/.omarchy/package.json +++ b/pkgbuilds/vi/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "9aa84b0998874261e94bbbe25e0dcf3de7286b94" + "source": "local", + "upstream": { + "watch": { + "regex": "https://sources.archlinux.org/other/vi/", + "pattern": "ex-(?P[0-9]{6})\\.tar\\.xz" + } + }, + "origin": { + "aur": "vi", + "commit": "9aa84b0998874261e94bbbe25e0dcf3de7286b94" + } } diff --git a/pkgbuilds/visual-studio-code-bin/.omarchy/package.json b/pkgbuilds/visual-studio-code-bin/.omarchy/package.json index 4d00cbf..9daebc8 100644 --- a/pkgbuilds/visual-studio-code-bin/.omarchy/package.json +++ b/pkgbuilds/visual-studio-code-bin/.omarchy/package.json @@ -1,5 +1,14 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "70a29667af6ac26dc2631a1910ac356924066b48" + "upstream": { + "watch": { + "json": "https://update.code.visualstudio.com/api/update/linux-deb-x64/stable/latest", + "path": "productVersion" + } + }, + "origin": { + "aur": "visual-studio-code-bin", + "commit": "4e46b8b4ca50f6aed23c848aa4f2d16fbd2432e5" + } } diff --git a/pkgbuilds/visual-studio-code-bin/PKGBUILD b/pkgbuilds/visual-studio-code-bin/PKGBUILD index 1124e28..4d43a57 100644 --- a/pkgbuilds/visual-studio-code-bin/PKGBUILD +++ b/pkgbuilds/visual-studio-code-bin/PKGBUILD @@ -2,7 +2,7 @@ pkgname=visual-studio-code-bin _pkgname=visual-studio-code -pkgver=1.136.1 +pkgver=1.138.0 pkgrel=1 pkgdesc="Visual Studio Code (vscode): Editor for building and debugging modern web and cloud applications (official binary version)" arch=('x86_64' 'aarch64' 'armv7h') @@ -27,9 +27,9 @@ source_x86_64=(code_${pkgver}_amd64.deb::https://update.code.visualstudio.com/${ source_aarch64=(code_${pkgver}_arm64.deb::https://update.code.visualstudio.com/${pkgver}/linux-deb-arm64/stable) source_armv7h=(code_${pkgver}_armhf.deb::https://update.code.visualstudio.com/${pkgver}/linux-deb-armhf/stable) sha256sums=('bd0d9edf69283ebdf4e73e0a7b168d2fcf50acbd01f63674cad93ed4fe42fdad') -sha256sums_x86_64=('4463c4e249a19d7746603f2ec3745c345d3cd55b009d36e824158d15c35509ef') -sha256sums_aarch64=('baa72f92d3feaa76d015202c57271475ea15809e635587279171a972cdd612a4') -sha256sums_armv7h=('02ddb5c5cbc2d5f4db25a3d7bb365c60eef0a017fa64b1994b131544677319e3') +sha256sums_x86_64=('73389cdcef7e66171a2039d1e49b9530e5ed02937e6159d3e6af93484e63cbad') +sha256sums_aarch64=('09760b73fb96ca19f8c6e483ec5b69123f34edd2c762cc9e0faf73fa3d400145') +sha256sums_armv7h=('bb74a3023aced544c71d4274d5942ce69c59a2ec0ffaf9094fa7c0fddb506366') package() { bsdtar -xf data.tar.xz -C "${pkgdir}/" diff --git a/pkgbuilds/walker/.omarchy/package.json b/pkgbuilds/walker/.omarchy/package.json index ba3a040..30aa091 100644 --- a/pkgbuilds/walker/.omarchy/package.json +++ b/pkgbuilds/walker/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "9dbe9e62a133a14a16f4a6f6e9fe7de04ef651ef" + "source": "local", + "upstream": { + "watch": { + "github": "abenz1267/walker", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "walker", + "commit": "9dbe9e62a133a14a16f4a6f6e9fe7de04ef651ef" + } } diff --git a/pkgbuilds/xdg-terminal-exec/.omarchy/package.json b/pkgbuilds/xdg-terminal-exec/.omarchy/package.json index 8a4d806..5e59cc2 100644 --- a/pkgbuilds/xdg-terminal-exec/.omarchy/package.json +++ b/pkgbuilds/xdg-terminal-exec/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "a52e8f23c0be7d482b944c6cf5ddee172da171e4" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://gitlab.freedesktop.org/Vladimir-csp/xdg-terminal-exec.git", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "xdg-terminal-exec", + "commit": "a52e8f23c0be7d482b944c6cf5ddee172da171e4" + } } diff --git a/pkgbuilds/xpadneo-dkms/.omarchy/package.json b/pkgbuilds/xpadneo-dkms/.omarchy/package.json index e23a5b8..906b421 100644 --- a/pkgbuilds/xpadneo-dkms/.omarchy/package.json +++ b/pkgbuilds/xpadneo-dkms/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "ae15a8de85094bee24c0e24408a22decb21121e7" + "source": "local", + "upstream": { + "watch": { + "github": "atar-axis/xpadneo", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "xpadneo-dkms", + "commit": "ae15a8de85094bee24c0e24408a22decb21121e7" + } } diff --git a/pkgbuilds/xpadneo-dkms/.omarchy/patches/arm-kernel-headers.patch b/pkgbuilds/xpadneo-dkms/.omarchy/patches/arm-kernel-headers.patch deleted file mode 100644 index 9dcc175..0000000 --- a/pkgbuilds/xpadneo-dkms/.omarchy/patches/arm-kernel-headers.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -14,7 +14,9 @@ arch=(any) - license=('GPL-2.0-only AND GPL-3.0-or-later') --checkdepends=('dkms' 'fakeroot' 'LINUX-HEADERS') -+checkdepends=('dkms' 'fakeroot') -+checkdepends_x86_64=('LINUX-HEADERS') -+checkdepends_aarch64=('linux-aarch64-headers') - source=("xpadneo-v${pkgver}.tar.gz::${url}/archive/v${pkgver}.tar.gz") - b2sums=('2858e466c5fde7e3d06d878dd7fb695751375e11a112bd3d389f9db5a82ef3ffcc1e3431b213f4807aeb8f05fd4280c7ca2a0a56305b0e984eb9b70df4843735') - diff --git a/pkgbuilds/yaru-icon-theme/.omarchy/package.json b/pkgbuilds/yaru-icon-theme/.omarchy/package.json index b9d987c..c20fa15 100644 --- a/pkgbuilds/yaru-icon-theme/.omarchy/package.json +++ b/pkgbuilds/yaru-icon-theme/.omarchy/package.json @@ -1,5 +1,13 @@ { - "source": "aur", - "aur": "yaru", - "upstream_commit": "7206d6736121b4564998262d99d6afdbc28b1563" + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://github.com/ubuntu/yaru.git", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*(?:ubuntu)?)" + } + }, + "origin": { + "aur": "yaru", + "commit": "7206d6736121b4564998262d99d6afdbc28b1563" + } } diff --git a/pkgbuilds/yaru-icon-theme/PKGBUILD b/pkgbuilds/yaru-icon-theme/PKGBUILD index 5f5ade4..e421772 100644 --- a/pkgbuilds/yaru-icon-theme/PKGBUILD +++ b/pkgbuilds/yaru-icon-theme/PKGBUILD @@ -10,8 +10,8 @@ pkgname=('yaru-sound-theme' 'yaru-metacity-theme' 'yaru-icon-theme' 'yaru-session') -pkgver=26.04.5.1ubuntu -pkgrel=1 +pkgver=26.10.3 +pkgrel=2 pkgdesc="Yaru default ubuntu theme" arch=(any) url="https://github.com/ubuntu/yaru" @@ -20,7 +20,7 @@ license=('GPL3') makedepends=('meson' 'sassc' 'git') source=("https://github.com/ubuntu/yaru/archive/${pkgver}.tar.gz") -sha256sums=('4e35b71d37aedb44302c811f63d325f4abd7699961f76f130323b361d4c6a7d7') +sha256sums=('ac396932559dd1b515ee4d94d470093dfac407027d9609e756299df9e06c3f7b') build() { arch-meson $pkgbase-$pkgver build @@ -56,7 +56,8 @@ _delete_all_from_pkgdir_except() { rm -r "${pkgdir}"/usr/share/icons fi if [[ "$1" != "session" ]]; then - rm -r "${pkgdir}"/usr/share/{glib-2.0,xsessions,wayland-sessions} + # Newer Yaru releases no longer install the X11 session directory. + rm -rf "${pkgdir}"/usr/share/{glib-2.0,xsessions,wayland-sessions} rm -r "${pkgdir}"/usr/share/gnome-shell/{extensions,modes} fi # Delete remaining empty directories: diff --git a/pkgbuilds/yay/.omarchy/package.json b/pkgbuilds/yay/.omarchy/package.json index 82766e4..eec3de3 100644 --- a/pkgbuilds/yay/.omarchy/package.json +++ b/pkgbuilds/yay/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "cb43f84828ab4f9700f7c6f9c6d7a923d4cfaff0" + "source": "local", + "upstream": { + "watch": { + "github": "Jguer/yay", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "yay", + "commit": "cb43f84828ab4f9700f7c6f9c6d7a923d4cfaff0" + } } diff --git a/pkgbuilds/yt6801-dkms/.omarchy/package.json b/pkgbuilds/yt6801-dkms/.omarchy/package.json index 436ae2c..fa2b446 100644 --- a/pkgbuilds/yt6801-dkms/.omarchy/package.json +++ b/pkgbuilds/yt6801-dkms/.omarchy/package.json @@ -1,4 +1,17 @@ { - "source": "aur", - "upstream_commit": "020a0f11079cc974794e49856a1a98ab677570a9" + "source": "local", + "upstream": { + "watch": { + "archive": "https://www.motor-comm.com/Cn/Skippower/downloadFile.html?id=1817", + "filenames": true, + "pattern": "yt6801-(?P[0-9]+(?:\\.[0-9]+)*)\\.tar\\.gz", + "mutable_sources": [ + "source:0" + ] + } + }, + "origin": { + "aur": "yt6801-dkms", + "commit": "020a0f11079cc974794e49856a1a98ab677570a9" + } } diff --git a/pkgbuilds/yt6801-dkms/PKGBUILD b/pkgbuilds/yt6801-dkms/PKGBUILD index 23b1076..e46a73e 100644 --- a/pkgbuilds/yt6801-dkms/PKGBUILD +++ b/pkgbuilds/yt6801-dkms/PKGBUILD @@ -2,8 +2,8 @@ _pkgbase=yt6801 pkgname=yt6801-dkms -pkgver=1.0.31 -pkgrel=2 +pkgver=1.0.34 +pkgrel=1 pkgdesc="Kernel module for Motorcomm YT6801 ethernet controller (DKMS)" arch=('x86_64') url="https://www.motor-comm.com/product/ethernet-control-chip" @@ -11,8 +11,7 @@ license=('GPL-2.0-or-later') depends=('dkms') source=(${_pkgbase}-${pkgver}.zip::'https://www.motor-comm.com/Cn/Skippower/downloadFile.html?id=1817' 'patch.diff') -sha256sums=('9ea62182bd520483df5fd3ec320262cbdddcc763f3128ae37abd26905a97e14c' - '6cdb77774c483b640c8f0499fd54a79e773cd3f96a426cd99eec385972d9d5bb') +sha256sums=('877e6953fc6eb47232a74e47b0daf33de0e7f9e9a395d3bc10d500d3c4150af8' '597cbcefe2c3f426a20b089b1a24f7bfcaffe81bdfe1bd2c789c9228b3b5708e') prepare() { cd ${srcdir} diff --git a/pkgbuilds/yt6801-dkms/patch.diff b/pkgbuilds/yt6801-dkms/patch.diff index daebe0e..3e2ad95 100644 --- a/pkgbuilds/yt6801-dkms/patch.diff +++ b/pkgbuilds/yt6801-dkms/patch.diff @@ -2,9 +2,7 @@ diff --git a/src/dkms.conf b/src/dkms.conf index 78eaf8b..9d93675 100644 --- a/src/dkms.conf +++ b/src/dkms.conf -@@ -1,8 +1,5 @@ - PACKAGE_NAME="yt6801" - PACKAGE_VERSION="1.0.31" +@@ -3,6 +3,3 @@ -CLEAN="make clean" -MAKE[0]="make" BUILT_MODULE_NAME[0]="yt6801" @@ -26,43 +24,6 @@ index 3e643f9..fea4c97 100644 struct fxgmac_channel *channel = from_timer(channel, t, expansion.tx_hang_timer); #else struct fxgmac_channel *channel = (struct fxgmac_channel *)data; -diff --git a/src/fuxi-gmac-phy.c b/src/fuxi-gmac-phy.c -index 36d4fb3..8bd9533 100644 ---- a/src/fuxi-gmac-phy.c -+++ b/src/fuxi-gmac-phy.c -@@ -324,7 +324,9 @@ static void fxgmac_phy_link_poll(struct timer_list *t) - static void fxgmac_phy_link_poll(unsigned long data) - #endif - { --#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0)) -+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,16,0)) -+ struct fxgmac_pdata *pdata = timer_container_of(pdata, t, expansion.phy_poll_tm); -+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0)) - struct fxgmac_pdata *pdata = from_timer(pdata, t, expansion.phy_poll_tm); - #else - struct fxgmac_pdata *pdata = (struct fxgmac_pdata*)data; -@@ -352,7 +354,9 @@ static void fxgmac_phy_link_poll(unsigned long data) - - int fxgmac_phy_timer_init(struct fxgmac_pdata *pdata) - { --#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0)) -+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,16,0)) -+ timer_init_key(&pdata->expansion.phy_poll_tm, NULL, 0, "fuxi_phy_link_update_timer", NULL); -+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0)) - init_timer_key(&pdata->expansion.phy_poll_tm, NULL, 0, "fuxi_phy_link_update_timer", NULL); - #else - init_timer_key(&pdata->expansion.phy_poll_tm, 0, "fuxi_phy_link_update_timer", NULL); -@@ -370,6 +374,10 @@ int fxgmac_phy_timer_init(struct fxgmac_pdata *pdata) - - void fxgmac_phy_timer_destroy(struct fxgmac_pdata *pdata) - { -+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,15,0)) -+ timer_shutdown_sync(&pdata->expansion.phy_poll_tm); -+#else - del_timer_sync(&pdata->expansion.phy_poll_tm); -+#endif - DPRINTK("fxgmac_phy_timer removed\n"); - } diff --git a/src/fuxi-gmac.h b/src/fuxi-gmac.h index 539b507..2435f0c 100644 --- a/src/fuxi-gmac.h diff --git a/tests/build-isolation.sh b/tests/build-isolation.sh index 27c9df5..7f1250f 100755 --- a/tests/build-isolation.sh +++ b/tests/build-isolation.sh @@ -89,11 +89,17 @@ EOF fixture consumer "checkdepends_${TEST_ARCH}=('broken')" fixture transitive "makedepends=('consumer')" fixture independent '' -if run_build transitive consumer broken independent > "$TEST_ROOT/failure.log" 2>&1; then +failure_status=0 +OMARCHY_BUILD_RESULT_DIR="$TEST_ROOT/results" run_build transitive consumer broken independent > "$TEST_ROOT/failure.log" 2>&1 || failure_status=$? +if [[ "$failure_status" != 2 ]]; then cat "$TEST_ROOT/failure.log" - echo 'FAIL: a failed prerequisite did not fail the run' >&2 + echo "FAIL: expected completed package failure (2), got $failure_status" >&2 exit 1 fi +[[ -f "$TEST_ROOT/results/complete" ]] +[[ $(cat "$TEST_ROOT/results/failed") == broken ]] +[[ $(cat "$TEST_ROOT/results/blocked") == $'transitive\nconsumer' || $(cat "$TEST_ROOT/results/blocked") == $'consumer\ntransitive' ]] +[[ $(cat "$TEST_ROOT/results/artifacts") == "independent-1-1-$TEST_ARCH.pkg.tar.zst" ]] grep -q 'consumer blocked by unsuccessful dependency: broken' "$TEST_ROOT/failure.log" grep -q 'transitive blocked by unsuccessful dependency: consumer' "$TEST_ROOT/failure.log" compgen -G "$TEST_ROOT/build-output/edge/$TEST_ARCH/independent-1-1-*.pkg.tar.zst" >/dev/null diff --git a/tests/partial-release.sh b/tests/partial-release.sh new file mode 100755 index 0000000..c4fc725 --- /dev/null +++ b/tests/partial-release.sh @@ -0,0 +1,154 @@ +#!/bin/bash +# Exercise release/queue handling without publishing to an external repository. +set -euo pipefail +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +TEST_ROOT=$(mktemp -d) +trap 'rm -rf "$TEST_ROOT"' EXIT +export TEST_ROOT +export OMARCHY_STATE_DIR="$TEST_ROOT/state" +unset OMARCHY_REPO_ROOT OMARCHY_KEEP_BUILD_WORKSPACE OMARCHY_DEFER_RUNTIME_DEPS +mkdir -p "$TEST_ROOT/bin" "$TEST_ROOT/state" +cp "$BUILD_ROOT/bin/"{release,auto-release,check-versions} "$TEST_ROOT/bin/" +cp -r "$BUILD_ROOT/helpers" "$BUILD_ROOT/build" "$TEST_ROOT/" +cat >> "$TEST_ROOT/helpers/basecamp-notifier.sh" <<'EOF' +notify_basecamp() { printf '%s\n' "$1" >> "$TEST_ROOT/notifications"; } +EOF + +cat > "$TEST_ROOT/bin/build" <<'EOF' +#!/bin/bash +set -euo pipefail +[[ "$MODE" == plan ]] && exit 0 +out="$TEST_ROOT/build-output/edge/x86_64" +mkdir -p "$out" +echo complete > "$out/good-1-1-x86_64.pkg.tar.zst" +[[ "$MODE" == infrastructure ]] && exit 1 +[[ "$MODE" == interrupted ]] && exit 2 +mkdir -p "$OMARCHY_BUILD_RESULT_DIR" +: > "$OMARCHY_BUILD_RESULT_DIR/artifacts" +: > "$OMARCHY_BUILD_RESULT_DIR/failed" +: > "$OMARCHY_BUILD_RESULT_DIR/blocked" +if [[ "$MODE" != all_failed ]]; then + echo good-1-1-x86_64.pkg.tar.zst > "$OMARCHY_BUILD_RESULT_DIR/artifacts" +fi +touch "$OMARCHY_BUILD_RESULT_DIR/complete" +[[ "$MODE" == success ]] && exit 0 +[[ "$MODE" == missing_artifact ]] && echo missing-1-1-x86_64.pkg.tar.zst >> "$OMARCHY_BUILD_RESULT_DIR/artifacts" +# A failed split build may have copied its first output before failing. +echo incomplete > "$out/bad-first-1-1-x86_64.pkg.tar.zst" +echo bad > "$OMARCHY_BUILD_RESULT_DIR/failed" +echo blocked > "$OMARCHY_BUILD_RESULT_DIR/blocked" +exit 2 +EOF +cat > "$TEST_ROOT/bin/repo" <<'EOF' +#!/bin/bash +shift +exec "$(dirname "$0")/release" "$@" +EOF +cat > "$TEST_ROOT/bin/stage" <<'EOF' +#!/bin/bash +set -euo pipefail +stage=$(basename "$0") +echo "$stage" >> "$TEST_ROOT/stages" +out="$TEST_ROOT/build-output/edge/x86_64" +repo="$TEST_ROOT/pkgs.omarchy.org/edge/x86_64" +case "$stage" in + sign) + [[ "$MODE" == signing ]] && exit 1 + for file in "$out"/*.pkg.tar.zst; do + echo "signed $(basename "$file")" >> "$TEST_ROOT/signed" + touch "$file.sig" + done + ;; + promote-build) + mkdir -p "$repo" + mv "$out"/*.pkg.tar.zst* "$repo/" + ;; + update-repo) + mkdir -p "$TEST_ROOT/db/good-1-1" + printf '%%NAME%%\ngood\n\n%%BASE%%\ngood\n\n%%VERSION%%\n1-1\n' > "$TEST_ROOT/db/good-1-1/desc" + tar --zstd -cf "$repo/omarchy.db.tar.zst" -C "$TEST_ROOT/db" good-1-1 + ln -s omarchy.db.tar.zst "$repo/omarchy.db" + ;; +esac +EOF +for stage in sign promote-build clean-repo update-repo sync-repo; do + cp "$TEST_ROOT/bin/stage" "$TEST_ROOT/bin/$stage" +done +chmod +x "$TEST_ROOT/bin/"* + +for package in good bad blocked; do + mkdir -p "$TEST_ROOT/pkgbuilds/$package/.omarchy" + printf '{"source":"local"}\n' > "$TEST_ROOT/pkgbuilds/$package/.omarchy/package.json" + printf "pkgname=%s\npkgver=1\npkgrel=1\narch=('x86_64')\n" "$package" > "$TEST_ROOT/pkgbuilds/$package/PKGBUILD" +done +echo "depends=('bad')" >> "$TEST_ROOT/pkgbuilds/blocked/PKGBUILD" + +reset_case() { + rm -rf "$TEST_ROOT/build-output" "$TEST_ROOT/pkgs.omarchy.org" "$TEST_ROOT/state" "$TEST_ROOT/db" + mkdir -p "$TEST_ROOT/state" + : > "$TEST_ROOT/stages" + : > "$TEST_ROOT/signed" + : > "$TEST_ROOT/notifications" + printf 'good\nbad\nblocked\n' > "$TEST_ROOT/state/.sync-needed-edge-x86_64" +} + +reset_case +export MODE=partial +if "$TEST_ROOT/bin/auto-release" edge x86_64 > "$TEST_ROOT/partial.log" 2>&1; then + echo 'FAIL: partial release cleared failure status' >&2; exit 1 +fi +[[ $(cat "$TEST_ROOT/stages") == $'sign\npromote-build\nclean-repo\nupdate-repo\nsync-repo' ]] +[[ $(cat "$TEST_ROOT/signed") == 'signed good-1-1-x86_64.pkg.tar.zst' ]] +[[ -f "$TEST_ROOT/pkgs.omarchy.org/edge/x86_64/good-1-1-x86_64.pkg.tar.zst" ]] +[[ ! -e "$TEST_ROOT/pkgs.omarchy.org/edge/x86_64/bad-first-1-1-x86_64.pkg.tar.zst" ]] +[[ -f "$TEST_ROOT/state/.build-failed-edge-x86_64" ]] +grep -q 'Partial release published: edge' "$TEST_ROOT/notifications" +grep -q 'Failed: bad' "$TEST_ROOT/notifications" +grep -q 'Blocked by failed dependencies: blocked' "$TEST_ROOT/notifications" +"$TEST_ROOT/bin/check-versions" > "$TEST_ROOT/versions.log" 2>&1 +[[ $(cat "$TEST_ROOT/state/.sync-needed-edge-x86_64") == $'bad\nblocked' ]] +ARCH=x86_64 MIRROR=edge DRY_RUN=true PKGBUILDS_DIR="$TEST_ROOT/pkgbuilds" \ + FINAL_OUTPUT_DIR="$TEST_ROOT/pkgs.omarchy.org/edge/x86_64" HELPERS_DIR="$TEST_ROOT/helpers" \ + "$TEST_ROOT/build/build.sh" > "$TEST_ROOT/retry.log" 2>&1 +grep -q 'good - already up to date' "$TEST_ROOT/retry.log" +grep -q 'Build order: bad blocked' "$TEST_ROOT/retry.log" +echo 'PASS: successes publish; incomplete outputs stay out; retries skip published versions' + +for MODE in infrastructure interrupted all_failed missing_artifact; do + export MODE + reset_case + if "$TEST_ROOT/bin/release" --mirror edge > "$TEST_ROOT/$MODE.log" 2>&1; then + echo "FAIL: $MODE succeeded" >&2; exit 1 + fi + [[ ! -s "$TEST_ROOT/stages" ]] +done +echo 'PASS: infrastructure failures, missing completion records, and zero successes never publish' + +reset_case +export MODE=signing +if "$TEST_ROOT/bin/release" --mirror edge > "$TEST_ROOT/signing.log" 2>&1; then + echo 'FAIL: signing failure succeeded' >&2; exit 1 +fi +[[ $(cat "$TEST_ROOT/stages") == sign ]] +echo 'PASS: signing failure stops promotion and sync' + +reset_case +export MODE=partial +if OMARCHY_DEFER_RUNTIME_DEPS=true "$TEST_ROOT/bin/release" --mirror edge > "$TEST_ROOT/pair.log" 2>&1; then + echo 'FAIL: incomplete deferred release pair published' >&2; exit 1 +fi +[[ ! -s "$TEST_ROOT/stages" ]] +echo 'PASS: deferred release pairs cannot publish partially' + +reset_case +export MODE=success +"$TEST_ROOT/bin/auto-release" edge x86_64 > "$TEST_ROOT/success.log" 2>&1 +[[ ! -e "$TEST_ROOT/state/.sync-needed-edge-x86_64" ]] +grep -q 'Release published: edge' "$TEST_ROOT/notifications" +echo 'PASS: complete success publishes and clears the queue' + +reset_case +export MODE=plan +"$TEST_ROOT/bin/release" --mirror edge --dry-run > "$TEST_ROOT/plan.log" 2>&1 +[[ ! -s "$TEST_ROOT/stages" && ! -s "$TEST_ROOT/notifications" ]] +echo 'PASS: dry runs neither publish nor notify' diff --git a/tests/published-build-plan.sh b/tests/published-build-plan.sh new file mode 100755 index 0000000..538eff7 --- /dev/null +++ b/tests/published-build-plan.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# The timer and builder must agree when an older archive remains published. +set -euo pipefail +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +TEST_ROOT=$(mktemp -d) +trap 'rm -rf "$TEST_ROOT"' EXIT +mkdir -p "$TEST_ROOT/bin" "$TEST_ROOT/pkgbuilds" "$TEST_ROOT/state" +cp "$BUILD_ROOT/bin/check-versions" "$TEST_ROOT/bin/" +cp -r "$BUILD_ROOT/helpers" "$BUILD_ROOT/build" "$TEST_ROOT/" +export OMARCHY_STATE_DIR="$TEST_ROOT/state" +unset OMARCHY_REPO_ROOT OMARCHY_RC_PINS OMARCHY_DEFER_RUNTIME_DEPS + +fixture() { + local name=$1 version=$2 release=$3 + mkdir -p "$TEST_ROOT/pkgbuilds/$name/.omarchy" + printf '{"source":"local","channels":["edge"]}\n' > "$TEST_ROOT/pkgbuilds/$name/.omarchy/package.json" + cat > "$TEST_ROOT/pkgbuilds/$name/PKGBUILD" < "$TEST_ROOT/db/$name-$version/desc" +} +db_entry omarchy 4.0.4rc1-1 +db_entry omarchy-settings 4.0.4rc1-1 +for package in current update rebuild; do db_entry "$package" 1-1; done +printf '%s\n' new-package other-arch rebuild signature-only update | sort > "$TEST_ROOT/expected" + +for arch in x86_64 aarch64; do + repo="$TEST_ROOT/pkgs.omarchy.org/edge/$arch" + mkdir -p "$repo" + tar --zstd -cf "$repo/omarchy.db.tar.zst" -C "$TEST_ROOT/db" . + touch "$repo/omarchy-4.0.3-1-$arch.pkg.tar.zst" + touch "$repo/omarchy-settings-4.0.3-1-any.pkg.tar.xz" + touch "$repo/unindexed-1-1-$arch.pkg.tar.zst" + touch "$repo/rebuild-1-1-$arch.pkg.tar.zst" + touch "$repo/signature-only-1-1-$arch.pkg.tar.zst.sig" + other=x86_64 + [[ "$arch" == x86_64 ]] && other=aarch64 + touch "$repo/other-arch-1-1-$other.pkg.tar.zst" + + "$TEST_ROOT/bin/check-versions" --arch "$arch" > "$TEST_ROOT/check.log" 2>&1 + sort "$TEST_ROOT/state/.sync-needed-edge-$arch" > "$TEST_ROOT/queue" + diff -u "$TEST_ROOT/expected" "$TEST_ROOT/queue" + + for selection in '' 'omarchy omarchy-settings current update rebuild unindexed signature-only other-arch new-package'; do + ARCH="$arch" MIRROR=edge DRY_RUN=true PACKAGES="$selection" \ + PKGBUILDS_DIR="$TEST_ROOT/pkgbuilds" HELPERS_DIR="$TEST_ROOT/helpers" \ + FINAL_OUTPUT_DIR="$repo" BUILD_PLAN_DIR="$TEST_ROOT/plan" \ + "$TEST_ROOT/build/build.sh" > "$TEST_ROOT/plan.log" 2>&1 + sort "$TEST_ROOT/plan/packages" > "$TEST_ROOT/planned" + diff -u "$TEST_ROOT/queue" "$TEST_ROOT/planned" + grep -q 'omarchy 4.0.3-1 - archive already published' "$TEST_ROOT/plan.log" + done + printf 'PASS: %s scheduler and explicit/unscoped plans skip retained archives, allow new versions and releases\n' "$arch" +done diff --git a/tests/upstream-watch.py b/tests/upstream-watch.py new file mode 100644 index 0000000..856a928 --- /dev/null +++ b/tests/upstream-watch.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python3 +"""Offline integration tests for owned-recipe updates and failure isolation.""" +import importlib.util +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest +from unittest.mock import patch +import zipfile + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("upstream_watch", ROOT / "helpers/upstream-watch.py") +w = importlib.util.module_from_spec(spec) +spec.loader.exec_module(w) + + +class WatchTest(unittest.TestCase): + def setUp(self): + self.work = tempfile.TemporaryDirectory() + self.addCleanup(self.work.cleanup) + self.root = Path(self.work.name) + self.package = self.root / "package" + (self.package / ".omarchy").mkdir(parents=True) + self.fetch = w.Fetcher(self.root / "cache") + self.write_watch({"github": "owner/tool", "pattern": r"v(?P[0-9.]+)"}) + self.recipe = self.package / "PKGBUILD" + self.recipe.write_text("""pkgname=tool +pkgver=1.0 +pkgrel=3.1 +arch=('x86_64' 'aarch64') +source_x86_64=("https://example.test/tool-$pkgver-x64") +source_aarch64=("https://example.test/tool-$pkgver-arm64") +sha256sums_x86_64=('old-x64') +sha256sums_aarch64=('old-arm64') +package() { touch SHOULD-NOT-RUN; } +""") + + def write_watch(self, watch): + (self.package / ".omarchy/package.json").write_text(json.dumps({"source": "local", "upstream": {"watch": watch}})) + + def release(self, version="2.0", **values): + return {"pkgver": version, "values": {"version": version, "tag": "v" + version, **values}, "revision": "", "published_at": "2026-01-01T00:00:00Z"} + + def fake_file(self, url): + p = self.root / ("source-" + url.rsplit("/", 1)[-1]) + p.write_text(url) + return p + + def sync_release(self, release, file=None): + with patch.object(w, "discover", return_value=[release]), patch.object(self.fetch, "file", side_effect=file or self.fake_file): + return w.sync(self.package, self.fetch) + + def test_updates_both_arches_without_running_build_functions(self): + result = self.sync_release(self.release()) + self.assertEqual(result["after"], "0:2.0-1") + recipe = w.read_recipe(self.recipe) + for arch, upstream in [("x86_64", "x64"), ("aarch64", "arm64")]: + file = self.fake_file(f"https://example.test/tool-2.0-{upstream}") + self.assertEqual(recipe["sha256sums_" + arch], [w.hash_file(file, "sha256")]) + self.assertFalse((self.package / "SHOULD-NOT-RUN").exists()) + + def test_missing_arm_payload_preserves_original_recipe(self): + original = self.recipe.read_bytes() + def fetch(url): + if url.endswith('arm64'): + raise ValueError("ARM release not published") + return self.fake_file(url) + with self.assertRaisesRegex(ValueError, "ARM"): + self.sync_release(self.release(), fetch) + self.assertEqual(self.recipe.read_bytes(), original) + self.assertFalse(self.recipe.with_name('PKGBUILD.sync-upstream').exists()) + + def test_changed_checksum_override_is_detected_and_rolled_back(self): + self.recipe.write_text(self.recipe.read_text() + "sha256sums_aarch64[0]='stale'\n") + original = self.recipe.read_bytes() + with self.assertRaisesRegex(ValueError, "differs"): + self.sync_release(self.release()) + self.assertEqual(self.recipe.read_bytes(), original) + + def test_old_or_equal_versions_do_not_download_sources(self): + for version in ['0.9', '1.0']: + with patch.object(w, 'discover', return_value=[self.release(version)]), patch.object(self.fetch, 'file') as fetch: + self.assertEqual(w.sync(self.package, self.fetch)['status'], 'skipped') + fetch.assert_not_called() + + def test_manual_hold_prevents_release_discovery(self): + path = self.package / '.omarchy/package.json' + metadata = json.loads(path.read_text()) + metadata['sync'] = False + path.write_text(json.dumps(metadata)) + with patch.object(w, 'discover') as discover: + self.assertEqual(w.sync(self.package, self.fetch)['status'], 'skipped') + discover.assert_not_called() + + def test_epoch_change_in_recipe_cannot_mask_downgrade(self): + self.recipe.write_text(self.recipe.read_text() + "if [[ $pkgver == 1.0 ]]; then epoch=2; else epoch=1; fi\n") + original = self.recipe.read_bytes() + with self.assertRaisesRegex(ValueError, "complete package version"): + self.sync_release(self.release()) + self.assertEqual(self.recipe.read_bytes(), original) + + def test_same_version_build_revision_advances_downstream_pkgrel(self): + self.write_watch({'github': 'owner/tool', 'pattern': r'v(?P[0-9.]+)-(?P[0-9]+)', 'variables': {'_build': '{build}'}, 'revision': '{build}', 'revision_variable': '_build'}) + self.recipe.write_text(self.recipe.read_text().replace('pkgrel=3.1', 'pkgrel=3.1\n_build=8').replace('tool-$pkgver-', 'tool-$pkgver-$_build-')) + release = self.release('1.0', build='9');release['revision'] = '9' + self.assertEqual(self.sync_release(release)['after'], '0:1.0-3.2') + self.assertEqual(w.scalar(w.read_recipe(self.recipe), '_build'), '9') + release = self.release('1.0', build='7');release['revision'] = '7' + original = self.recipe.read_bytes() + with self.assertRaisesRegex(ValueError, 'without a newer'): + self.sync_release(release) + self.assertEqual(self.recipe.read_bytes(), original) + + def test_sha512_blake2_and_local_hashes_are_preserved(self): + self.recipe.write_text("""pkgver=1.0 +pkgrel=1 +arch=('any') +source=("https://example.test/tool-$pkgver" 'fix.patch') +sha512sums=('old' 'local-sha512') +b2sums=('old' 'local-b2') +""") + (self.package / 'fix.patch').write_text('local patch') + self.sync_release(self.release()) + recipe = w.read_recipe(self.recipe) + file = self.fake_file('https://example.test/tool-2.0') + self.assertEqual(recipe['sha512sums'], [w.hash_file(file, 'sha512'), 'local-sha512']) + self.assertEqual(recipe['b2sums'], [w.hash_file(file, 'b2'), 'local-b2']) + + def test_mutable_sources_are_rehashed_when_version_changes(self): + self.write_watch({'regex': 'https://example.test/feed', 'pattern': r'(?P[0-9.]+)', 'mutable_sources': ['source_x86_64:0']}) + self.recipe.write_text(self.recipe.read_text().replace('tool-$pkgver-x64','tool-current-x64')) + self.sync_release(self.release()) + result = w.read_recipe(self.recipe) + self.assertEqual(result['sha256sums_x86_64'], [w.hash_file(self.fake_file('https://example.test/tool-current-x64'), 'sha256')]) + + def test_release_selection_and_age(self): + a, b = self.release('1.9'), self.release('1.10') + self.assertEqual(w.select_release([a, b])['pkgver'], '1.10') + b['published_at'] = '2 days ago' + with self.assertRaisesRegex(ValueError, 'age'): + w.select_release([b], 86400) + b['published_at'] = '2999-01-01T00:00:00Z' + self.assertIsNone(w.select_release([b], 86400)) + self.assertEqual(w.select_release([b], 86400, bypass=True), b) + + def test_hostile_release_values_do_not_reach_shell(self): + for value in ['1.0\ntouch bad', '$(touch bad)', '`id`', '1.0;id', '1.0"']: + with self.assertRaises(ValueError): + w.candidate({}, {'version': value}) + with self.assertRaises(ValueError): + w.replace_scalar('pkgver=1.0\n', 'pkgver', '$(id)') + for url in ['http://example.test', 'https://user:password@example.test/file', 'https://example.test/\nfile']: + with self.assertRaises(ValueError):w.https(url) + + def test_array_writer_handles_comments_and_quoted_parentheses(self): + text = "sha256sums=( # closing ) in a comment\n 'old(value)'\n) # trailing\npackage() { :; }\n" + output = w.replace_array(text, 'sha256sums', ['abc']) + self.assertEqual(output, "sha256sums=('abc') # trailing\npackage() { :; }\n") + + def test_git_hash_matches_makepkg(self): + repo = self.root / 'git' + repo.mkdir() + subprocess.run(['git', 'init', '-q', str(repo)], check=True) + (repo / 'source').write_text('upstream code') + subprocess.run(['git', '-C', str(repo), 'add', '.'], check=True) + subprocess.run(['git', '-C', str(repo), '-c', 'user.name=Test', '-c', 'user.email=test@example.test', 'commit', '-qm', 'release'], check=True) + subprocess.run(['git', '-C', str(repo), 'tag', 'v1.0'], check=True) + command = w.subprocess.run + def redirect(args, **kwargs): + if 'fetch' in args: + args = [str(repo) if arg == 'https://example.test/tool.git' else arg for arg in args] + return command(args, **kwargs) + with patch.object(w.subprocess, 'run', side_effect=redirect): + archive = w.git_source_file('git+https://example.test/tool.git#tag=v1.0', self.fetch.cache) + expected = subprocess.check_output(['git', '-c', 'core.abbrev=no', '-C', str(repo), 'archive', '--format', 'tar', 'v1.0']) + self.assertEqual(archive.read_bytes(), expected) + + def test_invalid_optional_metadata_fails_validation(self): + valid = {'github': 'owner/tool', 'pattern': r'v(?P[0-9.]+)'} + for extra in [{'variables': []}, {'fields': {'commit': 3}}, {'submodules': {'pkgver': 'libs/common'}}, + {'allow_prerelease': 'false'}, {'sequence': 1}, {'mutable_sources': ['source:no']}, + {'revision_variable': '_build'}, {'submodules': {'_commit': '../outside'}}, + {'allow_prerelase': True}]: + with self.subTest(extra=extra), self.assertRaises(ValueError): + w.validate({**valid, **extra}) + with self.assertRaisesRegex(ValueError, 'numeric'): + w.candidate({'revision': '{build}'}, {'version': '1.0', 'build': 'beta'}) + + def test_github_selects_stable_matching_tag_and_resolves_gitlinks(self): + watch = {'github': 'owner/tool', 'pattern': r'v(?P[0-9.]+)', + 'variables': {'_commit': '{commit}'}, 'submodules': {'_common': 'libs/common'}} + def fetch(url): + if '/releases?' in url: + return [{'tag_name': tag, 'published_at': '2026-01-01T00:00:00Z', **flags} + for tag, flags in [('v1.0', {}), ('v2.0', {}), ('v3.0', {'prerelease': True}), + ('v4.0', {'draft': True}), ('unrelated-v5.0', {})]] + if '/git/ref/' in url: return {'object': {'type': 'tag', 'sha': 'a' * 40}} + if '/git/tags/' in url: return {'object': {'type': 'commit', 'sha': 'b' * 40}} + if '/contents/' in url: + self.assertTrue(url.endswith('?ref=v2.0')) + return {'sha': 'c' * 40, 'submodule_git_url': 'https://example.test/common'} + self.fail(url) + with patch.object(self.fetch, 'json', side_effect=fetch): + release = w.select_release(w.discover(watch, self.fetch)) + self.assertEqual(release['pkgver'], '2.0') + self.assertEqual(w.resolve_release_fields(watch, release, self.fetch)['variables'], + {'_commit': 'b' * 40, '_common': 'c' * 40}) + + def test_vendor_feeds_preserve_version_schemes(self): + cases = [ + ('1password-beta', 'Package: unrelated\nVersion: 99.0\n\nPackage: 1password\nVersion: 8.12.38~25.BETA\n', '8.12.38_25.BETA'), + ('spotify', 'Package: spotify-client\nVersion: 1:1.2.96.518.g366879e1\n', '1.2.96.518'), + ('typora', 'Package: typora\nVersion: 1.12.6-1\n\nPackage: typora\nVersion: 1.9.0-1\n', '1.12.6'), + ('nvidia-580xx-utils', '580.159.04/ 590.1/', '580.159.04'), + ] + for name, feed, expected in cases: + watch = json.loads((ROOT / 'pkgbuilds' / name / '.omarchy/package.json').read_text())['upstream']['watch'] + with self.subTest(package=name), patch.object(self.fetch, 'text', return_value=feed): + self.assertEqual(w.select_release(w.discover(watch, self.fetch))['pkgver'], expected) + with patch.object(self.fetch, 'json', return_value={'release': {'version': '2.0', 'commit': 'a' * 40}}): + release = w.discover({'json': 'https://example.test/feed', 'path': 'release.version', 'fields': {'commit': 'release.commit'}}, self.fetch)[0] + self.assertEqual(release['values']['commit'], 'a' * 40) + + def test_registry_releases_and_archive_filenames(self): + with patch.object(self.fetch, 'json', return_value={'dist-tags': {'latest': '2.0'}, 'time': {'2.0': '2026-01-01T00:00:00Z'}}): + self.assertEqual(w.discover({'npm': '@owner/tool'}, self.fetch)[0]['pkgver'], '2.0') + with patch.object(self.fetch, 'json', return_value={'info': {'version': '2.0'}, 'releases': {'2.0': [{'yanked': True}]}}): + with self.assertRaisesRegex(ValueError, 'unyanked'): + w.discover({'pypi': 'tool'}, self.fetch) + archive = self.root / 'vendor.zip' + with zipfile.ZipFile(archive, 'w') as z: + z.writestr('driver/yt6801-1.0.34.tar.gz', 'not executed or extracted') + watch = json.loads((ROOT / 'pkgbuilds/yt6801-dkms/.omarchy/package.json').read_text())['upstream']['watch'] + with patch.object(self.fetch, 'file', return_value=archive): + self.assertEqual(w.discover(watch, self.fetch)[0]['pkgver'], '1.0.34') + + def test_cursor_same_day_hash_uses_counter_not_hash_order(self): + self.write_watch({'regex': 'https://example.test/feed', 'pattern': r'(?P[0-9.]+)-(?P[a-f0-9]+)', + 'version': '{version}.1.{hash}', 'sequence': True}) + self.recipe.write_text(self.recipe.read_text().replace('pkgver=1.0', 'pkgver=2026.09.14.1.ffff')) + release = w.candidate({'version': '{version}.1.{hash}'}, {'version': '2026.09.14', 'hash': 'aaaa'}) + self.assertEqual(self.sync_release(release)['after'], '0:2026.09.14.2.aaaa-1') + self.assertEqual(self.sync_release(release)['status'], 'skipped') + + def test_existing_signed_metadata_skips_are_retained(self): + self.recipe.write_text(self.recipe.read_text().replace("'old-arm64'", "'SKIP'") + "sha256sums_aarch64[0]='SKIP'\n") + self.sync_release(self.release()) + self.assertEqual(w.read_recipe(self.recipe)['sha256sums_aarch64'], ['SKIP']) + self.assertTrue((self.root / 'source-tool-2.0-arm64').exists()) + + def test_conditional_arm_source_cannot_use_x86_hash(self): + self.recipe.write_text(self.recipe.read_text() + 'if [[ $CARCH == aarch64 ]]; then source_aarch64=("https://example.test/different-$pkgver"); fi\n') + original = self.recipe.read_bytes() + with self.assertRaisesRegex(ValueError, 'conditional source'): + self.sync_release(self.release()) + self.assertEqual(self.recipe.read_bytes(), original) + + def test_cli_continues_after_one_package_fails(self): + for directory in ['bin', 'helpers']: + shutil.copytree(ROOT / directory, self.root / directory) + for name in ['bad', 'good', 'held']: + pkg = self.root / 'pkgbuilds' / name + shutil.copytree(self.package, pkg) + (pkg / '.omarchy/package.json').write_text(json.dumps({'source': 'local', 'sync': name != 'held', 'upstream': {'watch': { + 'regex': 'https://example.test/feed', 'pattern': '(?P[0-9.]+)'}}})) + path = pkg / 'PKGBUILD' + path.write_text(path.read_text().replace('example.test/tool-', f'example.test/{name}-')) + stub = self.root / 'stub' + stub.mkdir() + curl = stub / 'curl' + curl.write_text('''#!/usr/bin/env python3 +import sys +from pathlib import Path +url = sys.argv[-1] +if 'bad-2.0-arm64' in url: sys.exit(22) +Path(sys.argv[sys.argv.index('-o') + 1]).write_text('2.0' if url.endswith('/feed') else url) +''') + curl.chmod(0o755) + bad = self.root / 'pkgbuilds/bad/PKGBUILD' + original = bad.read_bytes() + result = subprocess.run([str(self.root / 'bin/sync-upstream'), 'bad', 'good', 'held'], + env={**os.environ, 'PATH': str(stub) + os.pathsep + os.environ['PATH']}, + text=True, capture_output=True) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertEqual(bad.read_bytes(), original) + self.assertEqual(w.scalar(w.read_recipe(self.root / 'pkgbuilds/good/PKGBUILD'), 'pkgver'), '2.0') + self.assertEqual(w.scalar(w.read_recipe(self.root / 'pkgbuilds/held/PKGBUILD'), 'pkgver'), '1.0') + self.assertIn('Updated: 1', result.stdout) + self.assertIn('Failed: 1', result.stdout) + + def test_aur_import_is_once_only_and_records_local_ownership(self): + for directory in ['bin', 'helpers']: + shutil.copytree(ROOT / directory, self.root / directory) + repo = self.root / 'aur' + repo.mkdir() + subprocess.run(['git', 'init', '-q', str(repo)], check=True) + (repo / 'PKGBUILD').write_text('pkgver=1.0\npkgrel=1\ntouch SHOULD-NOT-RUN\n') + (repo / '.SRCINFO').write_text('AUR only') + subprocess.run(['git', '-C', str(repo), 'add', '.'], check=True) + subprocess.run(['git', '-C', str(repo), '-c', 'user.name=Test', '-c', 'user.email=test@example.test', 'commit', '-qm', 'recipe'], check=True) + stub = self.root / 'stub' + stub.mkdir() + git = stub / 'git' + git.write_text('''#!/usr/bin/env python3 +import os, sys +args = [os.environ['AUR_FIXTURE'] if v == 'https://aur.archlinux.org/upstream-name.git' else v for v in sys.argv[1:]] +os.execv(os.environ['REAL_GIT'], ['git', *args]) +''') + git.chmod(0o755) + env = {**os.environ, 'PATH': str(stub) + os.pathsep + os.environ['PATH'], 'AUR_FIXTURE': str(repo), 'REAL_GIT': shutil.which('git')} + command = [str(self.root / 'bin/add-package'), 'local-name', '--source', 'aur', '--aur', 'upstream-name', '--no-sync', '--fast'] + result = subprocess.run(command, env=env, text=True, capture_output=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + package = self.root / 'pkgbuilds/local-name' + metadata_file = package / '.omarchy/package.json' + metadata = json.loads(metadata_file.read_text()) + self.assertEqual(metadata['source'], 'local') + self.assertEqual(metadata['origin']['aur'], 'upstream-name') + self.assertEqual(metadata['origin']['commit'], subprocess.check_output(['git', '-C', str(repo), 'rev-parse', 'HEAD'], text=True).strip()) + self.assertFalse(metadata['sync']) + self.assertEqual(metadata['release_ring'], 'fast') + self.assertFalse((package / '.SRCINFO').exists()) + self.assertFalse((package / 'SHOULD-NOT-RUN').exists()) + original = metadata_file.read_bytes() + result = subprocess.run([*command, '--force'], env=env, text=True, capture_output=True) + self.assertNotEqual(result.returncode, 0) + self.assertEqual(metadata_file.read_bytes(), original) + + +if __name__ == '__main__': + unittest.main(verbosity=2)