diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td new file mode 100644 index 0000000..188239c --- /dev/null +++ b/.github/VOUCHED.td @@ -0,0 +1,27 @@ +# Trust list for PR builds. +# +# A pull request only builds packages (and spins up builder droplets) when +# its author is trusted: repository collaborators are trusted automatically +# and do not need listing; external contributors listed here are trusted +# too. Anyone else gets the plan only, until a maintainer either adds them +# here or applies the "build-approved" label to that one PR. The label also +# releases GitHub's approval hold for that PR's build and test workflows. +# It remains effective while attached, without vouching for the author's +# other PRs. An explicit denouncement cannot be overridden by the label. +# +# Syntax: +# github:username +# -github:username reason for denouncement +# +# Keep entries sorted alphabetically. +github:bjarneo +github:DanWahlin +github:dhh +github:f-trycua +github:HANCORE-linux +github:kwilczynski +github:ryanrhughes +github:scottjones +github:spencerbull +github:tcballard +github:tobi diff --git a/.github/scripts/approve-pr-workflows.cjs b/.github/scripts/approve-pr-workflows.cjs new file mode 100644 index 0000000..0ee32b3 --- /dev/null +++ b/.github/scripts/approve-pr-workflows.cjs @@ -0,0 +1,78 @@ +const BUILD = '.github/workflows/build-pr.yml'; +const TESTS = '.github/workflows/test.yml'; + +module.exports = async function approve({ github, context, core, vouchStatus, + sleep = ms => new Promise(resolve => setTimeout(resolve, ms)), attempts = 36 }) { + // Missing/failed vouch lookups must not become approval. Denouncements + // remain absolute, just as they are in the package build gate. + if (!['unknown', 'bot', 'collaborator', 'vouched'].includes(vouchStatus)) { + throw new Error(`Cannot approve workflows: vouch status is ${vouchStatus || 'missing'}.`); + } + + const expected = context.payload.pull_request; + const eventTime = Date.parse(expected.updated_at); + if (!Number.isFinite(eventTime)) throw new Error('Missing PR event timestamp.'); + const approved = new Set(); + let precedingBuild; + + const stillApproved = async () => { + const { data: pr } = await github.rest.pulls.get({ + ...context.repo, pull_number: expected.number, + }); + return pr.state === 'open' && pr.head.sha === expected.head.sha && + pr.labels.some(label => label.name === 'build-approved'); + }; + + // The label and PR-run events arrive independently. Wait for the build + // belonging to this event, rather than returning after approving an older + // run and leaving the new label-triggered run stuck behind GitHub's gate. + for (let attempt = 0; attempt < attempts; attempt++) { + if (attempt) await sleep(5000); + if (!await stillApproved()) { + core.info('PR closed, head changed, or build-approved removed; stopping.'); + return; + } + + const all = await github.paginate(github.rest.actions.listWorkflowRunsForRepo, { + ...context.repo, event: 'pull_request', head_sha: expected.head.sha, per_page: 100, + }); + const runs = all.filter(run => + run.event === 'pull_request' && run.head_sha === expected.head.sha && + run.head_repository?.id === expected.head.repo.id && run.head_branch === expected.head.ref && + [BUILD, TESTS].includes(run.path) && + // Fork runs awaiting approval often have no pull_requests entries. + (!run.pull_requests?.length || run.pull_requests.some(pr => pr.number === expected.number)) + ).sort((a, b) => a.id - b.id); + + const newestBuild = runs.findLast(run => run.path === BUILD); + if (!newestBuild || !(Date.parse(newestBuild.created_at) >= eventTime) || + !runs.some(run => run.path === TESTS && + (context.payload.action === 'labeled' || Date.parse(run.created_at) >= eventTime))) continue; + + if (precedingBuild) { + const { data: run } = await github.rest.actions.getWorkflowRun({ + ...context.repo, run_id: precedingBuild, + }); + // Approve older builds first, and let them acquire concurrency before + // releasing a newer build. Otherwise an older queued run could start + // last and cancel the label-triggered build that carries approval. + if (!['in_progress', 'completed'].includes(run.status) || run.conclusion === 'action_required') continue; + precedingBuild = undefined; + } + + const pending = runs.filter(run => run.conclusion === 'action_required' && !approved.has(run.id) && + // If the newest build already runs (e.g. a maintainer approved it), + // don't resurrect an obsolete hold that could cancel that newer run. + (run.path !== BUILD || run.id === newestBuild.id || newestBuild.conclusion === 'action_required')); + if (!pending.length) return; + const run = pending[0]; + // Recheck after the API reads, immediately before exercising write access. + if (!await stillApproved()) return; + await github.rest.actions.approveWorkflowRun({ ...context.repo, run_id: run.id }); + approved.add(run.id); + core.info(`Approved ${run.path} run ${run.id} for PR #${expected.number}.`); + if (run.path === BUILD) precedingBuild = run.id; + if (pending.length === 1) return; + } + throw new Error('Timed out waiting for PR workflows. Remove and reapply build-approved to retry.'); +}; diff --git a/.github/workflows/approve-pr.yml b/.github/workflows/approve-pr.yml new file mode 100644 index 0000000..996fdc9 --- /dev/null +++ b/.github/workflows/approve-pr.yml @@ -0,0 +1,46 @@ +name: Approve PR workflows + +# A pull_request workflow cannot approve itself: GitHub can hold it before +# any job starts. This workflow only runs trusted default-branch code and +# releases the ordinary, unprivileged PR workflows after build approval. +on: + pull_request_target: + types: [opened, synchronize, reopened, labeled] + +permissions: + contents: read + pull-requests: read + actions: write + +concurrency: + group: approve-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + approve: + # Match build-pr.yml's events, including other labels applied while this + # PR still carries build-approved: each labeled event creates a build. + if: contains(github.event.pull_request.labels.*.name, 'build-approved') + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + # Never check out the PR head or its merge ref with this write token. + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + - id: vouch + uses: mitchellh/vouch/action/check-user@f23dbb5e745334f97414ec70463ce7301071a661 # v1 + with: + user: ${{ github.event.pull_request.user.login }} + allow-fail: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Approve this PR's pending build and test runs + uses: actions/github-script@v7 + env: + VOUCH_STATUS: ${{ steps.vouch.outputs.status }} + with: + script: | + const approve = require('./.github/scripts/approve-pr-workflows.cjs'); + await approve({ github, context, core, vouchStatus: process.env.VOUCH_STATUS }); diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 0000000..ab0bec4 --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,254 @@ +name: Build changed packages + +# Build every package directory a PR touches, one job per package per arch, on +# the self-hosted droplet pool. Artifacts are unsigned; publish.yml signs and +# publishes them on merge. +# +# Tooling runs from the base branch; a PR supplies only pkgbuilds/. The +# vouch gate limits who may spend compute; this limits what their PR can run. + +# No paths filter: approved PRs must report the required `result` even when +# no package directory changed. Those PRs get an empty matrix and a passing +# result in seconds; unapproved PRs wait for maintainer approval. +on: + pull_request: + types: [opened, synchronize, reopened, labeled] + workflow_dispatch: + inputs: + packages: + description: "Space-separated package directories to build" + required: true + +concurrency: + group: build-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + # Builds cost real machines, so they run only for trusted authors: + # collaborators, anyone in .github/VOUCHED.td (read from the default + # branch, so a PR cannot vouch for itself), or a PR a maintainer has + # labelled "build-approved". Everyone else gets this job's plan output + # while the required `result` stays pending until a maintainer approves. + changes: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.list.outputs.matrix }} + count: ${{ steps.gate.outputs.count }} + trusted: ${{ steps.gate.outputs.trusted }} + vouch_status: ${{ steps.vouch.outputs.status }} + empty: ${{ steps.list.outputs.empty }} + steps: + # Same rule as the build job: bin/build-matrix comes from the base + # branch tip, the package directories from the PR head. + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.ref || github.sha }} + fetch-depth: 0 + persist-credentials: false + - if: github.event_name == 'pull_request' + run: | + git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.head.sha }}" + git checkout "${{ github.event.pull_request.head.sha }}" -- pkgbuilds/ + # Bootstrap: the PR that introduces this tooling has a base without + # it. Take the plan helper from the PR head in that one case; it + # runs on a hosted runner and only prints a plan. + if [[ ! -x bin/build-matrix ]]; then + git checkout "${{ github.event.pull_request.head.sha }}" -- bin/build-matrix helpers/ + echo "::notice::base branch has no bin/build-matrix; using the PR's copy for planning" + fi + - id: vouch + if: github.event_name == 'pull_request' + uses: mitchellh/vouch/action/check-user@f23dbb5e745334f97414ec70463ce7301071a661 # v1 + with: + user: ${{ github.event.pull_request.user.login }} + allow-fail: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - id: approval + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const { data: pr } = await github.rest.pulls.get({ + ...context.repo, pull_number: context.payload.pull_request.number, + }); + // Approving or rerunning a held run keeps its original event, + // which may predate the label. Read the current approval instead. + core.setOutput('approved', pr.state === 'open' && + pr.head.sha === context.payload.pull_request.head.sha && + pr.labels.some(label => label.name === 'build-approved')); + # One matrix entry per package per architecture. Every package builds + # once, against edge; the channels it ships to on merge are carried + # along for information. A filename means one set of bytes. + - id: list + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [[ -n "${{ github.event.inputs.packages }}" ]]; then + names="${{ github.event.inputs.packages }}" + else + names=$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" -- pkgbuilds \ + | awk -F/ '$1=="pkgbuilds" && NF>2 {print $2}' | sort -u) + fi + matrix=$(printf '%s\n' $names | bin/build-matrix) + # A package directory whose exact tree already has a build artifact + # (label --, uploaded only after a successful + # build) is not built again. Pushing a fix for one package to a PR + # that touches fifty rebuilds one, not fifty; publish.yml finds the + # same artifacts on merge. workflow_dispatch is an explicit request + # and always builds. + if [[ "${{ github.event_name }}" == pull_request ]]; then + head="${{ github.event.pull_request.head.sha }}" + kept=(); reused=() + while read -r entry; do + package=$(jq -r .package <<<"$entry"); arch=$(jq -r .arch <<<"$entry") + label="$package-$arch-$(git rev-parse "$head:pkgbuilds/$package")" + found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \ + | jq -r '[.artifacts[] | select(.expired|not)] | length' || echo 0) + if (( found > 0 )); then reused+=("$label"); else kept+=("$entry"); fi + done < <(jq -c '.include[]' <<<"$matrix") + matrix=$(printf '%s\n' "${kept[@]}" | jq -sc '{include: .}') + if (( ${#reused[@]} )); then + printf '==> already built, reusing the artifact: %s\n' "${reused[@]}" + { echo "Reused existing build artifacts (${#reused[@]}):"; printf -- '- %s\n' "${reused[@]}"; } >> "$GITHUB_STEP_SUMMARY" + fi + fi + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + echo "planned=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT" + jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix" + # A PR whose diff against its base is empty changes nothing: its + # content already landed some other way (a sync PR beat it, or a + # merge from master swallowed it). Merging it would record a change + # that isn't one. Flag it so `result` fails rather than passes. + if [[ "${{ github.event_name }}" == pull_request ]]; then + total=$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" | wc -l) + echo "empty=$([[ $total -eq 0 ]] && echo true || echo false)" >> "$GITHUB_OUTPUT" + echo "files changed vs base: $total" + else + echo "empty=false" >> "$GITHUB_OUTPUT" + fi + - id: gate + env: + STATUS: ${{ github.event_name == 'workflow_dispatch' && 'dispatch' || steps.vouch.outputs.status }} + AUTHOR: ${{ github.event.pull_request.user.login }} + APPROVED: ${{ steps.approval.outputs.approved || 'false' }} + PLANNED: ${{ steps.list.outputs.planned }} + run: | + case "$STATUS" in + bot|collaborator|vouched|dispatch) trusted=true ;; + # A denouncement is absolute: the label cannot override it. + denounced) trusted=false ;; + unknown) trusted=$APPROVED ;; + *) trusted=false ;; + esac + echo "trusted=$trusted" >> "$GITHUB_OUTPUT" + if [[ $trusted == true ]]; then + echo "count=$PLANNED" >> "$GITHUB_OUTPUT" + echo "Author $AUTHOR is trusted ($STATUS); building $PLANNED package(s)." + else + echo "count=0" >> "$GITHUB_OUTPUT" + echo "::warning::Author $AUTHOR is not trusted ($STATUS). $PLANNED package build(s) planned but not run." + if [[ $STATUS == denounced ]]; then + echo "::warning::The author is denounced in .github/VOUCHED.td; the build-approved label does not apply." + else + echo "::warning::A maintainer can add the author to .github/VOUCHED.td, or apply the 'build-approved' label to build this PR." + fi + fi + + build: + needs: changes + if: needs.changes.outputs.count != '0' + runs-on: [self-hosted, omarchy-builder] + timeout-minutes: 180 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.changes.outputs.matrix) }} + steps: + # Tooling from base: everything that executes on this droplet's host + # (bin/, helpers/, build/) comes from the base branch. Only the PR's + # package directories are overlaid. A PR can therefore change what + # gets built, never how the runner builds it. A PR that changes both + # tooling and a package builds the package with the OLD tooling; land + # the tooling first. workflow_dispatch has no PR and runs as checked out. + # The base branch tip, not the event's base.sha: that sha is a snapshot + # taken at the PR's last push, so a tooling fix on master would never + # reach an open PR until someone pushed to it (seen on the daily sync + # PR after the artifact packing fix landed). + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.ref || github.sha }} + persist-credentials: false + - name: Overlay the PR's package directories onto base tooling + if: github.event_name == 'pull_request' + run: | + set -euo pipefail + git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.head.sha }}" + git checkout "${{ github.event.pull_request.head.sha }}" -- pkgbuilds/ + echo "tooling: $(git rev-parse --short HEAD) (base) packages: ${{ github.event.pull_request.head.sha }} (PR head)" + git status --short | head + - name: Build ${{ matrix.package }} (${{ matrix.arch }}, ships to ${{ matrix.channels }}) + id: build + env: + CONTAINER_ENGINE: docker + run: bin/build --mirror edge --arch ${{ matrix.arch }} --package ${{ matrix.package }} + # The artifact label carries the package directory's git tree hash so + # the publish step can find the build for exactly the tree that merged. + # The package file inside keeps makepkg's standard name untouched. + # The artifact label uses the PR head's tree for this package: that is + # the tree that merges, and what publish looks up. + - name: Tree hash + id: tree + run: echo "hash=$(git rev-parse "${{ github.event.pull_request.head.sha || github.sha }}:pkgbuilds/${{ matrix.package }}")" >> "$GITHUB_OUTPUT" + # The upload action rejects a path containing ':', which is how makepkg + # names a package with an epoch. The files ride inside packages.tar + # (helpers/artifact-helpers.sh); publish.yml unpacks it. Only a + # successful build uploads: the artifact's existence is what lets the + # planner above and publish.yml skip rebuilding this exact tree. + - name: Pack artifact + id: pack + run: | + source helpers/artifact-helpers.sh + pack_packages build-output/edge/${{ matrix.arch }} packages.tar + tar -tvf packages.tar + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.package }}-${{ matrix.arch }}-${{ steps.tree.outputs.hash }} + path: packages.tar + if-no-files-found: error + retention-days: 7 + + # `result` is required by branch protection. An unvouched author awaiting + # approval gets a differently named informational check, leaving `result` + # unreported (pending). Skipping or passing a job named `result` would count + # as satisfying the requirement even though no build was authorized. + # Actual planning/build failures and denouncements still report `result`. + result: + name: ${{ needs.changes.result == 'success' && needs.changes.outputs.trusted == 'false' && needs.changes.outputs.vouch_status == 'unknown' && needs.changes.outputs.empty == 'false' && 'Awaiting build approval' || 'result' }} + needs: [changes, build] + if: always() + runs-on: ubuntu-latest + steps: + - run: | + echo "trusted=${{ needs.changes.outputs.trusted }} build=${{ needs.build.result }}" + if [[ "${{ needs.changes.result }}" != "success" ]]; then + echo "::error::Build planning or the trust check failed. See the changes job." + exit 1 + fi + # Nothing to merge: the PR's diff against its base is empty. Its + # change already landed elsewhere. Close it rather than merge it. + if [[ "${{ needs.changes.outputs.empty }}" == "true" ]]; then + echo "::error::This PR changes no files relative to its base. Its content is already on the target branch; close it instead of merging." + exit 1 + fi + if [[ "${{ needs.changes.outputs.trusted }}" == "false" && "${{ needs.changes.outputs.vouch_status }}" == "unknown" && "${{ needs.changes.outputs.empty }}" == "false" ]]; then + echo "::notice::Awaiting maintainer build approval. Apply 'build-approved' to this PR or vouch for the author in .github/VOUCHED.td." + echo "Package builds are waiting for maintainer approval. Apply **build-approved** to this PR to start them. The required **result** check remains pending." >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + if [[ "${{ needs.changes.outputs.trusted }}" != "true" ]]; then + echo "::error::Builds are blocked: the author is denounced or the trust result is invalid. The build-approved label cannot override this." + exit 1 + fi + [[ "${{ needs.build.result }}" == "success" || "${{ needs.build.result }}" == "skipped" ]] diff --git a/.github/workflows/builder-images.yml b/.github/workflows/builder-images.yml new file mode 100644 index 0000000..db2bb08 --- /dev/null +++ b/.github/workflows/builder-images.yml @@ -0,0 +1,118 @@ +name: Refresh builder images + +on: + schedule: + - cron: '23 4 * * *' + push: + branches: [master] + paths: + - build/** + - bin/builder-image + - helpers/paths.sh + - helpers/docker-helpers.sh + - tests/build-isolation.sh + - .github/workflows/builder-images.yml + workflow_dispatch: + pull_request: + paths: + - build/** + - bin/builder-image + - helpers/paths.sh + - helpers/docker-helpers.sh + - tests/build-isolation.sh + - .github/workflows/builder-images.yml + +# Complete each refresh before another can replace its tested image tags. +concurrency: + group: builder-images-${{ github.event.pull_request.number || 'master' }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + # Exercise proposed image changes on native runners with a read-only token. + # Publishing is a separate master-only job with its own write permission. + validate: + if: github.event_name == 'pull_request' + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + runner: ubuntu-24.04 + - arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + env: + CONTAINER_ENGINE: docker + CANDIDATE_IMAGE: omarchy-builder-candidate:${{ matrix.arch }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Build a fresh environment + run: bin/builder-image build --arch "${{ matrix.arch }}" --mirror edge --tag "$CANDIDATE_IMAGE" --fresh + - name: Test isolated package builds + env: + TEST_BUILDER_IMAGE: ${{ env.CANDIDATE_IMAGE }} + run: tests/build-isolation.sh + + refresh: + if: github.repository == 'omacom/omarchy-pkgs' && github.ref == 'refs/heads/master' + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + runner: ubuntu-24.04 + - arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + permissions: + contents: read + packages: write + env: + CONTAINER_ENGINE: docker + REGISTRY_IMAGE: ghcr.io/omacom/omarchy-pkg-builder + CANDIDATE_IMAGE: omarchy-builder-candidate:${{ matrix.arch }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Build a fresh environment + run: bin/builder-image build --arch "${{ matrix.arch }}" --mirror edge --tag "$CANDIDATE_IMAGE" --fresh + - name: Test isolated package builds + env: + TEST_BUILDER_IMAGE: ${{ env.CANDIDATE_IMAGE }} + run: tests/build-isolation.sh + - name: Publish tested image + env: + GH_TOKEN: ${{ github.token }} + GH_ACTOR: ${{ github.actor }} + DOCKER_CONFIG: ${{ runner.temp }}/builder-registry-auth + run: | + set -euo pipefail + mkdir -p "$DOCKER_CONFIG" + trap 'rm -rf "$DOCKER_CONFIG"' EXIT + printf '%s' "$GH_TOKEN" | docker login ghcr.io --username "$GH_ACTOR" --password-stdin + key=$(bin/builder-image key --arch "${{ matrix.arch }}" --mirror edge) + version="$REGISTRY_IMAGE:$key-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" + docker tag "$CANDIDATE_IMAGE" "$version" + docker push "$version" + # GHCR creates new packages private. Do not advertise an image to + # fork PRs until it is public. This is a one-time package setting. + anonymous_config=$(mktemp -d "$RUNNER_TEMP/builder-anonymous.XXXXXX") + if ! DOCKER_CONFIG="$anonymous_config" docker manifest inspect "$version" >/dev/null; then + rm -rf "$anonymous_config" + echo "::error::Make the omacom/omarchy-pkg-builder GHCR package public, then rerun this job. The previous matching image remains selected." + exit 1 + fi + rm -rf "$anonymous_config" + docker tag "$CANDIDATE_IMAGE" "$REGISTRY_IMAGE:$key" + docker push "$REGISTRY_IMAGE:$key" + digest=$(docker image inspect "$version" --format '{{index .RepoDigests 0}}') + printf '### Builder image (%s)\n\nInput key: `%s`\n\nImage: `%s`\n' \ + "${{ matrix.arch }}" "$key" "$digest" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..a61642d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,326 @@ +name: Publish merged packages + +# On every push to master: for each package directory the push touched and +# each architecture it supports, find the PR build artifact for exactly that +# tree (label = --), or build it now when there is +# none, then publish that one artifact into every channel the package ships +# to. One build, one file, several databases: a filename means one set of +# bytes everywhere, and channels are views over a shared pool. +# +# Secrets live in the "publish" environment, restricted to master: +# GPG_PRIVATE_KEY, GPG_PASSPHRASE the channel signing key +# R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_ENDPOINT +# OMARCHY_PUBLISH_PREFIX (environment variable, not secret) points a proof +# run at a scratch prefix inside the live bucket; empty means the real +# channel paths. + +on: + push: + branches: [master] + paths: ["pkgbuilds/**"] + workflow_dispatch: + inputs: + packages: + description: "Space-separated package directories to publish from master" + required: true + +# Merges serialize. Two publishes into one channel at once would race on +# the database; queued is fine, cancelled is not. +concurrency: + group: publish + cancel-in-progress: false + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.list.outputs.matrix }} + count: ${{ steps.list.outputs.count }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - id: list + run: | + if [[ -n "${{ github.event.inputs.packages }}" ]]; then + names="${{ github.event.inputs.packages }}" + else + names=$(git diff --name-only "${{ github.event.before }}..${{ github.sha }}" -- pkgbuilds \ + | awk -F/ '$1=="pkgbuilds" && NF>2 {print $2}' | sort -u) + fi + matrix=$(printf '%s\n' $names | bin/build-matrix) + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + echo "count=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT" + jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix" + + # One job for the whole merge. It collects every PR artifact for the + # merged tree (building only what has none), then walks each channel and + # architecture slot exactly once: pull that database, add every package + # that belongs in it, upload. Six slots, six round trips, however many + # packages the merge carried. One process is the only writer, so there + # is no race between packages; the run-level concurrency group above + # keeps one merge from overlapping the next. + publish: + needs: changes + if: needs.changes.outputs.count != '0' + runs-on: [self-hosted, omarchy-builder] + environment: publish + timeout-minutes: 240 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + # Every matrix entry, as a file the shell steps can loop over: + # package arch channels publish_arches + - name: Plan + run: | + jq -r '.include[] | "\(.package) \(.arch) \(.channels|gsub(" ";",")) \(.publish_arches|gsub(" ";","))"' \ + <<'EOF_MATRIX' > plan.txt + ${{ needs.changes.outputs.matrix }} + EOF_MATRIX + cat plan.txt + + # Fetch each package's PR artifact into build-output/edge//, or + # build it when no artifact exists for exactly this tree. An artifact + # carries its package files inside packages.tar (see build-pr.yml and + # helpers/artifact-helpers.sh: the upload action rejects the colon in + # an epoch filename). + - name: Collect artifacts + env: + GH_TOKEN: ${{ github.token }} + CONTAINER_ENGINE: docker + run: | + set -uo pipefail + source helpers/artifact-helpers.sh + # sources.jsonl: where each package's files came from, or that the + # build failed. A failed build ends the run before any publish, and + # the record says so instead of the report job finding nothing. + : > sources.jsonl + failed=0 + while read -r package arch channels publish_arches; do + hash=$(git rev-parse "HEAD:pkgbuilds/$package") + label="$package-$arch-$hash" + found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \ + | jq -r '[.artifacts[] | select(.expired|not)] | sort_by(.created_at) | last | .archive_download_url // empty') + mkdir -p "build-output/edge/$arch" + if [[ -n "$found" ]]; then + echo "==> $label: PR artifact" + rm -rf /tmp/artifact; mkdir -p /tmp/artifact + if curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -o /tmp/artifact.zip "$found" \ + && unzip -oq /tmp/artifact.zip -d /tmp/artifact \ + && unpack_packages /tmp/artifact "build-output/edge/$arch"; then + jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"pr-artifact"}' >> sources.jsonl + else + jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"artifact-download-failed"}' >> sources.jsonl; failed=1; break + fi + else + # bin/build plans against the public channel first. If the + # channel already holds master's version there is nothing to + # build and nothing to publish: a re-run for a package that + # turned out to be fine. Record it and move on. + plan=$(CONTAINER_ENGINE=docker bin/build --dry-run --mirror edge --arch "$arch" --package "$package" 2>&1 | grep -E '^==> Plan complete' || true) + # "Packages that would build:" followed by nothing means none. + if [[ -n "$plan" && -z "$(sed -E 's/.*would build: *//' <<<"$plan" | tr -d '[:space:]')" ]]; then + echo "==> $label: already published at master's version, nothing to do" + jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"already-published"}' >> sources.jsonl + continue + fi + echo "==> $label: no artifact for this tree, building" + if OMARCHY_KEEP_BUILD_WORKSPACE=1 bin/build --mirror edge --arch "$arch" --package "$package"; then + jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"built"}' >> sources.jsonl + else + jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"build-failed"}' >> sources.jsonl; failed=1; break + fi + fi + done < plan.txt + ls -1 build-output/edge/*/*.pkg.tar.zst 2>/dev/null || true + if (( failed )); then + # Write the record now; the publish step will not run. + jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + --arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \ + --slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \ + '{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:[]}' \ + > publish-record.json + cat publish-record.json + exit 1 + fi + + - name: Publish + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + RCLONE_CONFIG_R2_TYPE: s3 + RCLONE_CONFIG_R2_PROVIDER: Cloudflare + # The token is scoped to the bucket; it may not CreateBucket, and + # rclone's existence check is a CreateBucket in disguise. + RCLONE_CONFIG_R2_NO_CHECK_BUCKET: "true" + RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} + OMARCHY_PUBLISH_PREFIX: ${{ vars.OMARCHY_PUBLISH_PREFIX }} + # repo-add, gpg and bsdtar are Arch tools; run the publish inside the + # builder image (host-native, edge) with the workspace mounted. + run: | + set -euo pipefail + if ! compgen -G "build-output/edge/*/*.pkg.tar.zst" >/dev/null; then + echo "Nothing to publish: every requested package is already published at master's version." + jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + --arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \ + --slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \ + '{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:[]}' \ + > publish-record.json + cat publish-record.json + exit 0 + fi + docker image inspect omarchy-pkg-builder:latest-x86_64-edge >/dev/null 2>&1 \ + || docker buildx build --load -t omarchy-pkg-builder:latest-x86_64-edge --build-arg MIRROR=edge build + + # Group the merge's files by the (channel, architecture) slot each + # belongs to. A package's files live under build-output/edge// and are named --.pkg.tar.zst; a + # split package's outputs share the pkgbase's directory, so match + # on the artifact list rather than the name. + # pkgbase is read inside the builder image: the Ubuntu host has no + # bsdtar. One container call maps every file to its pkgbase. + docker run --rm -v "$PWD:/w:ro" -w /w omarchy-pkg-builder:latest-x86_64-edge bash -c ' + for f in build-output/edge/*/*.pkg.tar.zst; do + printf "%s %s\n" "$f" "$(bsdtar -xOf "$f" .PKGINFO | awk -F" = " "\$1==\"pkgbase\"{print \$2}")" + done' > pkgbase.txt + declare -A slot_files=() + while read -r package arch channels publish_arches; do + for f in build-output/edge/"$arch"/*.pkg.tar.zst; do + # Only files this package produced (its PKGINFO pkgbase). + [[ $(awk -v f="$f" '$1==f{print $2}' pkgbase.txt) == "$package" ]] || continue + for mirror in ${channels//,/ }; do + for parch in ${publish_arches//,/ }; do + slot_files["$mirror/$parch"]+="$f " + done + done + done + done < plan.txt + + # Deterministic slot order: edge before rc before stable, x86_64 + # before aarch64, so a failure leaves the earlier rings consistent. + # Every slot's outcome goes into publish-record.json for the report + # job: what was published, where, from which artifact, and whether + # the slot succeeded. A failing slot stops the loop (set -e) but the + # record still shows everything before it landed. + : > slots.jsonl + record_slot() { jq -nc --arg m "$1" --arg a "$2" --arg st "$3" --arg files "$4" \ + '{mirror:$m, arch:$a, status:$st, packages:[$files | split(" ") | .[] | select(length>0) | sub(".*/";"") | sub("\\.pkg\\.tar\\.zst$";"")]}' >> slots.jsonl; } + status=0 + for mirror in edge rc stable; do + for parch in x86_64 aarch64; do + files=${slot_files["$mirror/$parch"]:-} + [[ -n "$files" ]] || continue + echo "==> $mirror/$parch: $files" + if docker run --rm \ + -e GPG_PRIVATE_KEY -e GPG_PASSPHRASE -e OMARCHY_PUBLISH_PREFIX \ + -e RCLONE_CONFIG_R2_TYPE -e RCLONE_CONFIG_R2_PROVIDER -e RCLONE_CONFIG_R2_ENDPOINT -e RCLONE_CONFIG_R2_NO_CHECK_BUCKET \ + -e RCLONE_CONFIG_R2_ACCESS_KEY_ID -e RCLONE_CONFIG_R2_SECRET_ACCESS_KEY \ + -v "$PWD:/w:ro" -w /w \ + omarchy-pkg-builder:latest-x86_64-edge \ + bin/publish-artifact --remote R2:omarchy-pkgs --mirror "$mirror" --arch "$parch" $files; then + record_slot "$mirror" "$parch" published "$files" + else + record_slot "$mirror" "$parch" failed "$files" + status=1 + break 2 + fi + done + done + jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + --arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \ + --slurpfile slots slots.jsonl --slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \ + '{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:$slots}' \ + > publish-record.json + cat publish-record.json + exit $status + + - name: Keep the publish record + if: always() + uses: actions/upload-artifact@v4 + with: + name: publish-record-${{ github.run_id }} + path: publish-record.json + retention-days: 90 + + # Tell people what happened. A comment on the merged PR (found by the + # merge commit, so squash and rebase merges work too) and a line appended + # to a running JSON log in the bucket, next to the packages it describes, + # so the history is public and can be rendered later. + report: + needs: [changes, publish] + if: always() && needs.publish.result != 'skipped' + runs-on: ubuntu-latest + environment: publish + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/download-artifact@v4 + with: + name: publish-record-${{ github.run_id }} + - name: Render + id: render + run: | + jq -r --arg outcome "${{ needs.publish.result }}" ' + def pkgs: [.packages[] | select(test("-debug-")|not)] | join(", "); + def src: if .source=="pr-artifact" then "PR artifact" elif .source=="built" then "built here" elif .source=="already-published" then "already published, skipped" else "**" + .source + "**" end; + "### Publish " + (if $outcome=="success" then "succeeded" else "FAILED" end) + + " โ†’ **" + .target + "**" + (if .target!="live" then " (proof prefix, not live)" else "" end), + "", + "Packages: " + ([.sources[] | .package + " (" + .arch + ", " + src + ")"] | join("; ")), + "", + (if (.slots|length) > 0 then (.slots[] | "- `" + .mirror + "/" + .arch + "`: " + (if .status=="published" then "" else "**" + .status + "** " end) + pkgs) + elif ([.sources[] | select(.source=="already-published")] | length) == (.sources|length) then "_Nothing to publish: already at master'"'"'s version everywhere._" + else "_Nothing was published._" end), + "", + (if ([.sources[] | select(.source|test("failed"))] | length) > 0 then "_A build failed, so no channel was touched._\n" + elif (.plan|length) > (.slots|length) then "_Some planned slots did not run because an earlier slot failed._\n" else "" end), + "Commit " + .commit[0:7] + " ยท [run](" + .run + ")" + ' publish-record.json > comment.md + cat comment.md + - name: Append to the publish log in the bucket + env: + RCLONE_CONFIG_R2_TYPE: s3 + RCLONE_CONFIG_R2_PROVIDER: Cloudflare + RCLONE_CONFIG_R2_NO_CHECK_BUCKET: "true" + RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} + run: | + curl -fsSL https://downloads.rclone.org/rclone-current-linux-amd64.zip -o rclone.zip && unzip -jq rclone.zip '*/rclone' && chmod +x rclone + # One JSON object per line, newest last. Served at + # https://pkgs.omarchy.org/publish-log.jsonl + ./rclone copy R2:omarchy-pkgs/publish-log.jsonl . --s3-no-head 2>/dev/null || : > publish-log.jsonl + jq -c . publish-record.json >> publish-log.jsonl + ./rclone copyto publish-log.jsonl R2:omarchy-pkgs/publish-log.jsonl --s3-no-head + echo "log now has $(wc -l < publish-log.jsonl) entries" + + - name: Comment on the merged PR + # Only for a push: the merge commit names its PR. A dispatch runs + # from master's head, whose PR merged something else entirely, so + # commenting there would attach this run's report to the wrong PR. + if: github.event_name == 'push' + env: + GH_TOKEN: ${{ github.token }} + run: | + pr=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" --jq '.[0].number // empty') + if [[ -n "$pr" ]]; then + gh pr comment "$pr" -R "${{ github.repository }}" --body-file comment.md + echo "commented on #$pr" + else + echo "no PR for ${{ github.sha }} (manual dispatch?); skipping PR comment" + fi + result: + needs: [changes, publish] + if: always() + runs-on: ubuntu-latest + steps: + - run: | + echo "publish result: ${{ needs.publish.result }}" + [[ "${{ needs.publish.result }}" == "success" || "${{ needs.publish.result }}" == "skipped" ]] 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 a84333d..bba4b01 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,11 @@ name: Tests +# PR-only. Publishing on push has its own workflow and is what verifies the +# merged tree: it resolves every package against the live channel and refuses +# a filename that already exists with different bytes, so two PRs cannot land +# the same version twice. A post-merge test run would only repeat the PR's. on: pull_request: - push: - branches: [master] workflow_dispatch: jobs: @@ -30,6 +32,12 @@ jobs: with: persist-credentials: false + - name: Test PR workflow approval + run: node --test tests/pr-workflow-approval.cjs + + - name: Test builder images + run: node --test tests/builder-image.cjs + # An Arch container for vercmp: version ordering has to be decided by # the same comparator pacman uses on users' machines. - name: Run self-tests @@ -39,11 +47,20 @@ jobs: -w /workspace \ archlinux:base-devel bash -lc ' set -euo pipefail - pacman -Syu --noconfirm git jq neovim tmux python + pacman -Syu --noconfirm git jq python libarchive neovim tmux + python tests/oma-service-removal.py + 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/neovim-remote-clipboard.sh python tests/neovim-clipboard-tmux.py + ./tests/partial-release.sh + ./tests/published-build-plan.sh + ./tests/controller.sh + ./tests/artifact-helpers.sh + ./tests/limine-mkinitcpio-hook.sh + pacman -S --noconfirm --quiet rclone >/dev/null + ./tests/publish-artifact.sh ' diff --git a/.gitignore b/.gitignore index 7873a6d..65cb4ad 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ pkgbuilds/yay/yay/ .srcdest/ .repo-host .worktrees/ + +# Python helpers and offline tests +__pycache__/ +.release-verification/ diff --git a/README.md b/README.md index 64a5d58..8823595 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 ``` @@ -886,6 +825,45 @@ using real containers and pacman transactions. It uses the prepared builder image, or an image named by `TEST_BUILDER_IMAGE`; CI builds the small fixture image in `tests/build-isolation.Dockerfile`. +### Daily builder images + +`Refresh builder images` builds fresh `edge` environments daily at 04:23 UTC, +when their inputs change on `master`, and on manual dispatch. x86_64 and +aarch64 build on native GitHub-hosted runners, without occupying the DO +package-builder pool. Each candidate must pass `tests/build-isolation.sh`, +including real package builds, before publication to +`ghcr.io/omacom/omarchy-pkg-builder`. Only `master` in this repository can +publish; PR workflows cannot replace the shared images. +PRs that change image inputs also build and test both candidates on native +runners, with a read-only token and no registry publication. + +The compatibility tag contains the architecture, mirror, and a hash of the +entire `build/` context, including executable bits and symlink targets but +excluding checkout timestamps and ownership. This deliberately invalidates +images when mounted build scripts change too. `v1` identifies the image build +contract; change it if the invocation or compatibility rules change. Each +successful refresh also gets a run-specific tag for diagnosis and rollback. +A failed build, isolation test, or push leaves the previous compatible image +selected. Scheduled builds use `--pull --no-cache` so unchanged Dockerfiles +still pick up fresh Arch packages. + +To build and test a candidate locally: + +```bash +bin/builder-image key --arch x86_64 --mirror edge +bin/builder-image build --arch x86_64 --mirror edge --tag builder-candidate:test --fresh +CONTAINER_ENGINE=docker TEST_BUILDER_IMAGE=builder-candidate:test tests/build-isolation.sh +``` + +The workflow uses its repository `GITHUB_TOKEN` with `packages: write`; no +registry PAT is needed. **First publication needs one package setting:** GHCR +creates the package private. In the `omacom/omarchy-pkg-builder` package +settings, change visibility to **Public**, then rerun the failed refresh job. +The workflow checks anonymous registry access before advancing the compatible +tag, so fork PRs will not be directed to an image they cannot pull. Subsequent +refreshes preserve that package visibility. This change only produces images; +package jobs keep their existing behavior until image consumption is enabled. + ## Version Management Packages are only rebuilt if: @@ -902,9 +880,24 @@ 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. + +To approve builds for an unvouched contributor's PR, apply **`build-approved`**. +Until approval, the PR shows **Awaiting build approval** and its required +`result` check stays pending, keeping the PR blocked from merging without +reporting a failed build. Actual build failures and denouncements still fail. +Applying the label triggers a package build and automatically releases GitHub's +pending build and test workflows for that PR's current commit. The approval workflow +runs only trusted default-branch code; package builds and tests stay in the +ordinary PR workflows. It may take a few minutes for GitHub to register and +release all the runs. + +The label stays effective for that PR while attached, including later commits; +it does not vouch for the author's other PRs. Removing it stops further label +approvals, but does not cancel runs already released. An explicit denouncement +in `.github/VOUCHED.td` still blocks builds. If the approval workflow times out, +remove and reapply the label to retry. #### 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" < channel to plan and resolve against when no local tree exists" + echo " (default https://pkgs.omarchy.org; empty disables the fallback)" echo "" exit 0 ;; @@ -238,6 +240,9 @@ PACKAGE_CACHE_DIR="$BUILD_ROOT/cache/pacman/$MIRROR/$ARCH" mkdir -p "$PACKAGE_CACHE_DIR" PLAN_DIR=$(mktemp -d "$SRC_DIR/build-plan.XXXXXX") trap 'rm -rf "$PLAN_DIR"' EXIT +# Keep manifest directories host-owned so cleanup also works when Docker's +# builder uid differs from the caller (as on GitHub runners). +mkdir -p "$PLAN_DIR/artifacts" # Rootful Docker writes as the image uid, so retain its existing permission # workaround. Rootless Podman uses keep-id and must leave ownership/modes alone. @@ -253,6 +258,7 @@ DOCKER_ARGS=( -e MIRROR="$MIRROR" -e PACKAGES="$PACKAGES" -e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}" + -e OMARCHY_PUBLISHED_REPO_URL="${OMARCHY_PUBLISHED_REPO_URL-https://pkgs.omarchy.org}" -e DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS" -e BUILD_PLAN_DIR=/build-plan -v "$PLAN_DIR:/build-plan" @@ -320,6 +326,20 @@ echo " Skipped: ${#SKIPPED_PACKAGES[@]} (up-to-date or excluded)" echo " Failed: ${#FAILED_PACKAGES[@]}" echo " Blocked: ${#BLOCKED_PACKAGES[@]}" +# The release caller supplies a fresh directory. A completed result +# distinguishes package failures from an interrupted/failed orchestrator; +# only artifacts belonging to fully successful builds may be published. +if [[ -n "${OMARCHY_BUILD_RESULT_DIR:-}" ]]; then + mkdir -p "$OMARCHY_BUILD_RESULT_DIR" + : > "$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 +350,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/build-matrix b/bin/build-matrix new file mode 100755 index 0000000..e772349 --- /dev/null +++ b/bin/build-matrix @@ -0,0 +1,54 @@ +#!/bin/bash +# Print the PR build matrix for a set of package directories as JSON: one +# entry per package per supported architecture. Every package builds exactly +# once, against edge, and that one artifact is what every channel ships: +# channels are databases over a shared pool of files, and a filename must +# mean one set of bytes. "channels" lists where the artifact is published on +# merge: edge for everything, plus rc and stable immediately for the fast +# ring. Eligibility comes from package_builds_for_mirror, the rule the +# release host uses, so CI and the host cannot disagree. +# +# Usage: build-matrix [--arch |all] ... +# Reads package names on stdin when none are given. With no --arch, every +# architecture in CI_ARCHES (default "x86_64 aarch64") the package supports. +# Output: {"include":[{"package":"x","arch":"x86_64","channels":"edge rc stable","publish_arches":"x86_64"},...]} +# arch is where it builds; publish_arches lists every architecture +# database the file goes into (all of them for arch=any). +set -euo pipefail +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/helpers/paths.sh" +source "$BUILD_ROOT/helpers/package-metadata.sh" + +ARCHES=${CI_ARCHES:-x86_64 aarch64} +if [[ "${1:-}" == --arch ]]; then [[ $2 == all ]] || ARCHES=$2; shift 2; fi +for a in $ARCHES; do require_valid_arch "$a"; done + +if (( $# )); then names=("$@"); else mapfile -t names; fi + +entries=() +for name in "${names[@]}"; do + [[ -n "$name" ]] || continue + pkgdir="$PKGBUILDS_DIR/$name" + [[ -d "$pkgdir" ]] || continue + # skip_build packages still build on their own PR (explicit --package + # semantics); the host's unscoped runs are what skip them. + channels="" + for mirror in $VALID_MIRRORS; do + package_builds_for_mirror "$pkgdir" "$mirror" && channels="$channels $mirror" + done + channels=${channels# } + [[ -n "$channels" ]] || continue + # An arch=any package produces one architecture-independent file, so it + # builds once, on the first architecture, and that file serves every + # channel database of every architecture. + if [[ " $(package_arches "$pkgdir" "${ARCHES%% *}") " == *" any "* ]]; then + entries+=("$(jq -nc --arg p "$name" --arg a "${ARCHES%% *}" --arg c "$channels" --arg pa "$ARCHES" '{package:$p, arch:$a, channels:$c, publish_arches:$pa}')") + continue + fi + for arch in $ARCHES; do + package_supports_arch "$pkgdir" "$arch" || continue + entries+=("$(jq -nc --arg p "$name" --arg a "$arch" --arg c "$channels" '{package:$p, arch:$a, channels:$c, publish_arches:$a}')") + done +done + +printf '%s\n' "${entries[@]}" | jq -sc '{include: .}' diff --git a/bin/builder-image b/bin/builder-image new file mode 100755 index 0000000..f23f64b --- /dev/null +++ b/bin/builder-image @@ -0,0 +1,66 @@ +#!/bin/bash +# Build a reusable package environment from this checkout's own inputs. +set -euo pipefail + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/helpers/paths.sh" + +usage() { + echo "Usage: bin/builder-image {key|build} [--arch x86_64|aarch64] [--mirror edge|rc|stable] [--tag IMAGE] [--fresh]" +} + +command=${1:-} +[[ $# -eq 0 ]] || shift +tag="" +fresh=false +while (( $# )); do + case "$1" in + --arch) ARCH=${2:?Missing architecture}; shift 2 ;; + --mirror) MIRROR=${2:?Missing mirror}; shift 2 ;; + --tag) tag=${2:?Missing image tag}; shift 2 ;; + --fresh) fresh=true; shift ;; + *) usage >&2; exit 1 ;; + esac +done +require_valid_arch "$ARCH" +validate_mirror "$MIRROR" || { echo "Invalid mirror: $MIRROR" >&2; exit 1; } +case "$command" in key|build) ;; *) usage >&2; exit 1 ;; esac +if [[ $command == key && ( -n $tag || $fresh == true ) ]]; then + usage >&2 + exit 1 +fi + +# Include the whole build context, conservatively including mounted build +# scripts too. Normalize timestamps and ownership so fresh checkouts agree; +# retain file contents, names, executable bits and symlink targets. Bump v1 +# if the image build invocation or this compatibility contract changes. +hash=$(tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner \ + --format=gnu -cf - -C "$BUILD_DIR" . | sha256sum | cut -d' ' -f1) +key="v1-$ARCH-$MIRROR-$hash" +if [[ $command == key ]]; then + echo "$key" + exit 0 +fi + +source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/docker-helpers.sh" +check_engine +platform=$(get_platform_arg "$ARCH") +tag=${tag:-omarchy-pkg-builder:latest-$ARCH-$MIRROR} +revision=$(git -C "$BUILD_ROOT" rev-parse HEAD 2>/dev/null || echo unknown) +args=("$platform" --build-arg "MIRROR=$MIRROR" + --label "org.omarchy.builder.key=$key" + --label "org.opencontainers.image.source=https://github.com/omacom/omarchy-pkgs" + --label "org.opencontainers.image.revision=$revision" + --label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" + --tag "$tag" --file "$BUILD_DIR/Dockerfile") +if [[ $fresh == true ]]; then + # A daily build must refresh Arch even when its Dockerfile has not changed. + args+=(--no-cache) + if [[ $CONTAINER_ENGINE == docker ]]; then args+=(--pull); else args+=(--pull=always); fi +fi +if [[ $CONTAINER_ENGINE == docker ]]; then + docker buildx build --load "${args[@]}" "$BUILD_DIR" +else + podman build "${args[@]}" "$BUILD_DIR" +fi 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/publish-artifact b/bin/publish-artifact new file mode 100755 index 0000000..32a4909 --- /dev/null +++ b/bin/publish-artifact @@ -0,0 +1,118 @@ +#!/bin/bash +# Publish built packages into one channel of the remote repository, +# incrementally and immutably. +# +# publish-artifact --mirror --arch +# +# What it does, in order: +# 1. pull the channel's current database from the remote +# 2. refuse if any package filename already exists on the remote +# 3. sign each package (GPG_PRIVATE_KEY / GPG_PASSPHRASE) +# 4. repo-add the packages into the pulled database (replaces the entry +# for that name; nothing else in the channel is touched) +# 5. upload packages, then signatures, then the database last +# +# Never overwrites: uploads use --ignore-existing for packages and the +# pre-check in step 2 makes a same-name collision a hard failure rather than +# a silent skip. The database is the only object rewritten, and it is +# uploaded only after every file it references is present. +# +# The remote is an rclone remote (REMOTE, default the production one); +# OMARCHY_PUBLISH_PREFIX can point a proof run at a scratch prefix. +set -euo pipefail +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/paths.sh" + +REMOTE=${REMOTE:-pkgs.omarchy.org:omarchy-pkgs} +PREFIX=${OMARCHY_PUBLISH_PREFIX:-} +FILES=() +while [[ $# -gt 0 ]]; do + case $1 in + --mirror) MIRROR=$2; require_valid_mirror "$MIRROR"; shift 2 ;; + --arch) ARCH=$2; require_valid_arch "$ARCH"; shift 2 ;; + --remote) REMOTE=$2; shift 2 ;; + -h|--help) sed -n '2,22p' "$0"; exit 0 ;; + -*) print_error "Unknown option: $1"; exit 1 ;; + *) FILES+=("$1"); shift ;; + esac +done +(( ${#FILES[@]} )) || { print_error "No package files given"; exit 1; } +: "${GPG_PRIVATE_KEY:?}"; GPG_PASSPHRASE=${GPG_PASSPHRASE-} + +DEST="$REMOTE/${PREFIX:+$PREFIX/}$MIRROR/$ARCH" +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT +print_header "Publish to $DEST" + +# --- 0. sanity: every file is a package, named as makepkg names it --------- +for f in "${FILES[@]}"; do + [[ -f "$f" && "$f" == *.pkg.tar.* && "$f" != *.sig ]] || { print_error "Not a package file: $f"; exit 1; } + name=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="pkgname"{print $2}') + ver=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="pkgver"{print $2}') + pkgarch=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="arch"{print $2}') + [[ "$(basename "$f")" == "$name-$ver-$pkgarch.pkg.tar."* ]] || { + print_error "Filename does not match PKGINFO ($name-$ver-$pkgarch): $(basename "$f")"; exit 1; } + [[ "$pkgarch" == any || "$pkgarch" == "$ARCH" ]] || { print_error "$f is $pkgarch, publishing to $ARCH"; exit 1; } +done + +# --- 1. pull the current database ----------------------------------------- +mkdir -p "$WORK/repo" +listing=$(rclone lsf "$DEST/" --s3-no-head 2>/dev/null || true) +if grep -q '^omarchy.db.tar.zst$' <<<"$listing"; then + rclone copy "$DEST/omarchy.db.tar.zst" "$WORK/repo" --s3-no-head + rclone copy "$DEST/omarchy.files.tar.zst" "$WORK/repo" --s3-no-head 2>/dev/null || true + print_info "Pulled current database ($(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$') entries)" +else + print_warning "No database at $DEST โ€” creating a new one" +fi + +# --- 2. same-name collisions ---------------------------------------------- +# A filename must mean one set of bytes across every channel. The same file +# reaching a channel that already holds it (a fast-ring publish after edge, +# a re-run, a later promotion) is fine: it is skipped on upload and only the +# database entry is added. Different bytes under a name the channel already +# has is the one thing this must never do. +for f in "${FILES[@]}"; do + b=$(basename "$f") + grep -qxF "$b" <<<"$listing" || continue + remote_sum=$(rclone hashsum md5 "$DEST/$b" --s3-no-head 2>/dev/null | awk '{print $1}') + local_sum=$(md5sum "$f" | awk '{print $1}') + if [[ -n "$remote_sum" && "$remote_sum" == "$local_sum" ]]; then + print_info "Already published with identical bytes, adding to the database only: $b" + else + print_error "Already published with DIFFERENT bytes, refusing to overwrite: $b" + echo " Bump pkgrel; published filenames are immutable." + exit 1 + fi +done + +# --- 3. sign --------------------------------------------------------------- +export GNUPGHOME="$WORK/gnupg"; mkdir -m 700 "$GNUPGHOME" +echo "$GPG_PRIVATE_KEY" | gpg --batch --quiet --import +KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '$1=="sec"{print $5; exit}') +[[ -n "$KEY_ID" ]] || { print_error "No secret key imported"; exit 1; } +for f in "${FILES[@]}"; do + cp "$f" "$WORK/repo/" + gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \ + --detach-sign --no-armor --local-user "$KEY_ID" "$WORK/repo/$(basename "$f")" + print_step "signed $(basename "$f")" +done + +# --- 4. repo-add (replaces the entry for each pkgname) --------------------- +( cd "$WORK/repo" && repo-add --quiet omarchy.db.tar.zst "${FILES[@]/#*\//}" ) +ln -sf omarchy.db.tar.zst "$WORK/repo/omarchy.db" +ln -sf omarchy.files.tar.zst "$WORK/repo/omarchy.files" +print_info "Database now has $(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$') entries" + +# --- 5. upload: packages, signatures, database last ----------------------- +rclone copy "$WORK/repo" "$DEST" --s3-no-head --ignore-existing --filter '- *.sig' --filter '+ *.pkg.tar.*' --filter '- *' +rclone copy "$WORK/repo" "$DEST" --s3-no-head --ignore-existing --filter '+ *.pkg.tar.*.sig' --filter '- *' +# Re-verify every referenced file is really there before the db goes up. +listing=$(rclone lsf "$DEST/" --s3-no-head) +for f in "${FILES[@]}"; do + b=$(basename "$f") + grep -qxF "$b" <<<"$listing" && grep -qxF "$b.sig" <<<"$listing" || { print_error "Upload incomplete: $b"; exit 1; } +done +rclone copy "$WORK/repo" "$DEST" --s3-no-head --checksum --copy-links --filter '+ omarchy.db*' --filter '+ omarchy.files*' --filter '- *' +print_success "Published ${#FILES[@]} package(s) to $DEST" 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 046a39b..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 diff --git a/build/Dockerfile b/build/Dockerfile index af1bb5e..2a81a62 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -129,6 +129,7 @@ RUN pacman -Syu --noconfirm && \ wget \ curl \ jq \ + rclone \ gnupg && \ pacman -Scc --noconfirm && \ rm -rf /var/cache/pacman/pkg/* @@ -146,7 +147,8 @@ RUN useradd -m -G wheel -s /bin/bash builder && \ # be skipped at signing. Pin the extension so both architectures match. RUN sed -i 's/^#MAKEFLAGS=.*/MAKEFLAGS="-j$(nproc)"/' /etc/makepkg.conf && \ sed -i 's/^COMPRESSZST=.*/COMPRESSZST=(zstd -c -z -q --threads=0 -)/' /etc/makepkg.conf && \ - sed -i "s|^PKGEXT=.*|PKGEXT='.pkg.tar.zst'|" /etc/makepkg.conf + sed -i "s|^PKGEXT=.*|PKGEXT='.pkg.tar.zst'|" /etc/makepkg.conf && \ + sed -i 's|^#\?PACKAGER=.*|PACKAGER="Omarchy "|' /etc/makepkg.conf # Pacman wrapper that auto-resolves package conflicts (e.g. rustup vs rust). # makepkg only passes --noconfirm to pacman, which defaults to 'N' on conflict diff --git a/build/build.sh b/build/build.sh index bcf02a4..65d6017 100755 --- a/build/build.sh +++ b/build/build.sh @@ -26,6 +26,29 @@ DEFER_RUNTIME_DEPS=${DEFER_RUNTIME_DEPS:-false} source "$HELPERS_DIR/package-metadata.sh" +# Where the channel's published database is read from for planning. On the +# repository host it is the published tree itself. Anywhere else (a CI runner, +# a fresh clone) that tree is absent, so the database is fetched from the +# public channel and the same URL serves as pacman's dependency repository. +# Set OMARCHY_PUBLISHED_REPO_URL= (empty) to disable the remote fallback. +PUBLISHED_REPO_URL=${OMARCHY_PUBLISHED_REPO_URL-https://pkgs.omarchy.org} +PUBLISHED_DB_DIR="$FINAL_OUTPUT_DIR" +PUBLISHED_REPO_SERVER="" +if [[ ! -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" && ! -f "$FINAL_OUTPUT_DIR/omarchy.db" && -n "$PUBLISHED_REPO_URL" ]]; then + remote_channel="$PUBLISHED_REPO_URL/$MIRROR/$ARCH" + remote_db_dir=$(mktemp -d /tmp/omarchy-published.XXXXXX) || exit 1 + # Cache-bust: the channel sits behind a CDN that serves a stale database + # for a while after a sync. + if curl -fsSL "$remote_channel/omarchy.db.tar.zst?$(date +%s)" -o "$remote_db_dir/omarchy.db.tar.zst"; then + PUBLISHED_DB_DIR="$remote_db_dir" + PUBLISHED_REPO_SERVER="$remote_channel" + echo "==> No local published tree; planning against $remote_channel" + else + rm -rf "$remote_db_dir" + echo "==> No local published tree and $remote_channel is unavailable; treating the channel as empty" + fi +fi + if [[ $DEFER_RUNTIME_DEPS != "false" && $DEFER_RUNTIME_DEPS != "true" ]]; then echo "DEFER_RUNTIME_DEPS must be true or false" >&2 exit 1 @@ -118,10 +141,15 @@ if [[ "$DRY_RUN" != true ]]; then fi touch "$BUILD_PLAN_DIR/repository-initialized" || exit 1 - # Add omarchy repo if it has a database (stable packages) + # Add omarchy repo if it has a database (stable packages). The local tree + # is trusted as-is; the public channel is verified against the omarchy + # keyring the image already carries. if [[ -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" ]] || [[ -f "$FINAL_OUTPUT_DIR/omarchy.db" ]]; then sudo sed -i "/^\[core\]$/i [omarchy]\nSigLevel = Optional TrustAll\nServer = file://$FINAL_OUTPUT_DIR\n" /etc/pacman.conf echo " -> omarchy (priority 2): $FINAL_OUTPUT_DIR" + elif [[ -n "$PUBLISHED_REPO_SERVER" ]]; then + sudo sed -i "/^\[core\]$/i [omarchy]\nSigLevel = Required DatabaseOptional\nServer = $PUBLISHED_REPO_SERVER\n" /etc/pacman.conf + echo " -> omarchy (priority 2): $PUBLISHED_REPO_SERVER" fi # Sync pacman database @@ -159,10 +187,10 @@ LOCAL_VERSION_CACHE_LOADED=false LOCAL_VERSION_CACHE_DB="" load_local_versions() { - local db="$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" + local db="$PUBLISHED_DB_DIR/omarchy.db.tar.zst" if [[ ! -f "$db" ]]; then - db="$FINAL_OUTPUT_DIR/omarchy.db" + db="$PUBLISHED_DB_DIR/omarchy.db" fi [[ -f "$db" ]] || return 0 @@ -411,6 +439,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 +559,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/ci/README.md b/ci/README.md new file mode 100644 index 0000000..0e53988 --- /dev/null +++ b/ci/README.md @@ -0,0 +1,79 @@ +# CI spike: build PRs on ephemeral DigitalOcean droplets + +Status: spike. Nothing here publishes. The repository host keeps building and +signing on merge exactly as before. + +## Pieces + +- `.github/workflows/build-pr.yml` โ€” on a PR touching `pkgbuilds/**`, one job + per changed package on runners labelled `omarchy-builder`. Uploads the + unsigned `.pkg.tar.zst` as a workflow artifact (7 days). +- `runner-cloud-init.yaml` โ€” Ubuntu 24.04 user-data: docker + buildx, the + GitHub runner registered `--ephemeral`, runs one job, powers off. +- `controller.sh` โ€” systemd timer every minute on a small always-on droplet. + Polls for queued jobs with our label, creates one g5-32vcpu-64gb-50gb droplet (ric1) per job up + to `MAX_DROPLETS`, deletes droplets that are powered off or older than + `MAX_AGE_MINUTES`. No inbound endpoint. Plain curl against both APIs, no + doctl and no gh: a token in the environment cannot pick the wrong account + the way a saved doctl context can. Needs curl and jq. + `tests/controller.sh` exercises every decision against canned responses. +- `controller-box/` โ€” the always-on droplet: unit, timer, env template, + cloud-init, and `create.sh` to stand it up with one API call. + +## Standing up the controller box + + DIGITALOCEAN_TOKEN= GITHUB_TOKEN= \ + REPO=omacom/omarchy-pkgs ci/controller-box/create.sh + +The GitHub PAT is fine-grained, scoped to the one repo: Actions read, +Administration read+write (registration tokens). The DO token is baked into +the box's env file, so it is the account that pays for builder droplets. +Watch it with `journalctl -u omarchy-controller -f` on the box. + +## What the spike proved (2026-09-17, fork ryanrhughes/omarchy-pkgs) + +- `bin/build` works from a bare clone: with no local published tree it + plans against and resolves from `https://pkgs.omarchy.org//`. +- Droplet create โ†’ runner registered: ~70 s. omarchy-fish PR job: 2 min + including the builder image build. Droplet powers off after the job. +- linux-omarchy on a c-32 droplet: 30 min wall clock for the build job + (23:39 โ†’ 00:09), 254 MB artifact. Cold start ~90 s before the job began. +- A PR whose PKGBUILD fails to build turns the required check red and GitHub + refuses the merge (`mergeStateStatus=BLOCKED`, `gh pr merge` refuses + without `--admin`). +- Controller: one queued job + one busy droplet โ‡’ creates exactly one more; + reaps powered-off droplets on the next tick. + +## Not done (required before this touches the real repo) + +- Tooling from base: check out master's `bin/ helpers/ build/` and overlay + only the PR's `pkgbuilds/`; today a PR can edit the build script + and it runs on the droplet. The vouch gate limits who can do that, not + what they can do. +- DigitalOcean cloud firewall on the `omarchy-builder` tag: no inbound, no + egress to private ranges or the metadata address. +- A fine-grained GitHub token for the real repository (the one on the + controller box is scoped to the fork), and the publish environment's + secrets set there. +- Disable the host's auto-release timers for any channel CI publishes to, + so two writers never touch one database. + +## Done since the spike README was first written + +- Controller as a systemd timer on its own droplet, plain curl, self-test. +- Build once against edge; one artifact per package per architecture, + published into every channel it belongs to (fast ring: all three at + once). arch=any builds once for every architecture database. +- Publish is incremental and immutable: pull the channel db, refuse + different bytes under an existing name, accept identical bytes, upload + packages then signatures then the db. +- aarch64 under QEMU with credential-preserving binfmt. +- Vouch gate: collaborators, `.github/VOUCHED.td`, or the `build-approved` + label; denounced authors cannot be overridden by the label. +- Tests run on PRs only; `result`, `self-tests`, `build-isolation` are the + required checks with strict up-to-date branches. + +## Cleanup + + doctl compute droplet list --tag-name omarchy-builder + doctl compute droplet delete -f diff --git a/ci/controller-box/cloud-init.yaml b/ci/controller-box/cloud-init.yaml new file mode 100644 index 0000000..fda8c43 --- /dev/null +++ b/ci/controller-box/cloud-init.yaml @@ -0,0 +1,45 @@ +#cloud-config +# The always-on controller droplet (smallest size is fine). Clones the repo +# for ci/controller.sh, installs the unit and timer, and starts polling. +# +# Substitute before use: +# __REPO_URL__ https://github.com/omacom/omarchy-pkgs.git +# __BRANCH__ branch carrying ci/ (master once merged) +# __ENV_B64__ base64 of a filled-in controller.env.example +# __SSH_KEYS_JSON__ JSON array of public keys authorized for root +package_update: true +packages: [curl, jq, git] + +# Root stays reachable by key so the journal can be read. Two things stand +# in the way on DO images: disable_root rewrites root's keys into a stub, and +# with no account ssh key attached DO expires root's password, which makes +# sshd refuse every non-interactive session with "password change required". +disable_root: false +chpasswd: + expire: false +ssh_authorized_keys: __SSH_KEYS_JSON__ + +users: + - name: controller + shell: /bin/bash + +write_files: + # defer: write after the users module has created the controller group, + # otherwise chown to root:controller fails and the unit cannot read this. + - path: /etc/omarchy-controller.env + permissions: "0640" + owner: root:controller + encoding: b64 + defer: true + content: __ENV_B64__ + +runcmd: + - chage -d "$(date +%F)" -M -1 root + - chown root:controller /etc/omarchy-controller.env && chmod 0640 /etc/omarchy-controller.env + - git clone --depth 1 --branch __BRANCH__ __REPO_URL__ /opt/omarchy-pkgs + - mkdir -p /run/omarchy-controller && chown controller:controller /run/omarchy-controller + - echo "d /run/omarchy-controller 0755 controller controller -" > /etc/tmpfiles.d/omarchy-controller.conf + # runcmd is executed by /bin/sh: no brace expansion. + - cp /opt/omarchy-pkgs/ci/controller-box/omarchy-controller.service /opt/omarchy-pkgs/ci/controller-box/omarchy-controller.timer /etc/systemd/system/ + - systemctl daemon-reload + - systemctl enable --now omarchy-controller.timer diff --git a/ci/controller-box/controller.env.example b/ci/controller-box/controller.env.example new file mode 100644 index 0000000..37ae372 --- /dev/null +++ b/ci/controller-box/controller.env.example @@ -0,0 +1,15 @@ +# /etc/omarchy-controller.env โ€” mode 0600, owned by root, read by systemd. +DIGITALOCEAN_TOKEN=dop_v1_... +# Fine-grained PAT scoped to the repo: Actions: read, Administration: read+write +GITHUB_TOKEN=github_pat_... +REPO=omacom/omarchy-pkgs +LABEL=omarchy-builder +TAG=omarchy-builder +REGION=ric1 +SIZE=g5-32vcpu-64gb-50gb +MAX_DROPLETS=6 +MAX_AGE_MINUTES=200 +LOCK=/run/omarchy-controller/lock +# Operator public keys for root on every builder droplet (JSON array). +# create.sh fills this from the operators' GitHub keys. +SSH_KEYS_JSON=[] diff --git a/ci/controller-box/create.sh b/ci/controller-box/create.sh new file mode 100755 index 0000000..9ec4c3d --- /dev/null +++ b/ci/controller-box/create.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# Create the controller droplet with plain curl. Run from a laptop, once. +# +# DIGITALOCEAN_TOKEN=... GITHUB_TOKEN=... ci/controller-box/create.sh [branch] +# +# The DO token given here is baked into the box's env file, so it must be the +# token for the account that should pay for builder droplets. +set -euo pipefail +here=$(dirname "$0") +: "${DIGITALOCEAN_TOKEN:?}" "${GITHUB_TOKEN:?}" +REPO=${REPO:-omacom/omarchy-pkgs} +BRANCH=${1:-master} +REGION=${REGION:-ric1} +NAME=${NAME:-omarchy-controller} +# Optional DO ssh key ids as a JSON array, e.g. SSH_KEYS='[123]', for reading +# the journal while bringing the box up. Not needed once it works. +SSH_KEYS=${SSH_KEYS:-[]} +# Public keys authorized for root: the operators' GitHub keys, fetched at +# creation so the box never depends on an ssh_key API scope. Override with +# ADMIN_GITHUB_USERS. +ADMIN_GITHUB_USERS=${ADMIN_GITHUB_USERS:-ryanrhughes dhh} +ssh_keys_json=$(for u in $ADMIN_GITHUB_USERS; do curl -fsS "https://github.com/$u.keys"; done | jq -R . | jq -sc .) +[[ $(jq length <<<"$ssh_keys_json") -gt 0 ]] || { echo "no ssh keys fetched for $ADMIN_GITHUB_USERS" >&2; exit 1; } + +env_file=$(sed -e "s|^DIGITALOCEAN_TOKEN=.*|DIGITALOCEAN_TOKEN=$DIGITALOCEAN_TOKEN|" \ + -e "s|^GITHUB_TOKEN=.*|GITHUB_TOKEN=$GITHUB_TOKEN|" \ + -e "s|^REPO=.*|REPO=$REPO|" \ + -e "s|^SSH_KEYS_JSON=.*|SSH_KEYS_JSON=$ssh_keys_json|" "$here/controller.env.example") +userdata=$(sed -e "s|__REPO_URL__|https://github.com/$REPO.git|" -e "s|__BRANCH__|$BRANCH|" \ + -e "s|__ENV_B64__|$(printf '%s\n' "$env_file" | base64 -w0)|" \ + -e "s|__SSH_KEYS_JSON__|$ssh_keys_json|" "$here/cloud-init.yaml") +body=$(jq -n --arg name "$NAME" --arg region "$REGION" --arg ud "$userdata" --argjson keys "$SSH_KEYS" \ + '{name:$name, region:$region, size:"s-1vcpu-1gb", image:"ubuntu-24-04-x64", tags:["omarchy-controller"], user_data:$ud, ssh_keys:$keys}') + +# Refuse to create a second one. +existing=$(curl -fsS -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + "https://api.digitalocean.com/v2/droplets?tag_name=omarchy-controller" | jq '.droplets | length') +if (( existing > 0 )); then echo "a controller droplet already exists" >&2; exit 1; fi + +curl -fsS -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" -H "Content-Type: application/json" \ + -X POST -d "$body" https://api.digitalocean.com/v2/droplets | jq -r '"created \(.droplet.name) id=\(.droplet.id)"' diff --git a/ci/controller-box/omarchy-controller.service b/ci/controller-box/omarchy-controller.service new file mode 100644 index 0000000..12d2e9c --- /dev/null +++ b/ci/controller-box/omarchy-controller.service @@ -0,0 +1,12 @@ +[Unit] +Description=Provision ephemeral omarchy-builder runner droplets for queued jobs +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +User=controller +EnvironmentFile=/etc/omarchy-controller.env +ExecStart=/opt/omarchy-pkgs/ci/controller.sh +# The reaper's safety net is time, not state; a hung tick must not hold the lock. +TimeoutStartSec=240 diff --git a/ci/controller-box/omarchy-controller.timer b/ci/controller-box/omarchy-controller.timer new file mode 100644 index 0000000..0534b68 --- /dev/null +++ b/ci/controller-box/omarchy-controller.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Run the omarchy-builder controller every minute + +[Timer] +OnBootSec=1min +OnUnitActiveSec=1min +AccuracySec=5s + +[Install] +WantedBy=timers.target diff --git a/ci/controller.sh b/ci/controller.sh new file mode 100755 index 0000000..cc60950 --- /dev/null +++ b/ci/controller.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# Droplet-per-job controller for the omarchy-builder runner pool. +# +# Run from a systemd timer every minute on a small always-on droplet. No +# inbound endpoint: it polls GitHub for queued jobs wanting our label, creates +# one ephemeral droplet per job (up to MAX_DROPLETS), and deletes droplets +# that have powered off or exceeded MAX_AGE_MINUTES. The reaper does not +# trust its own bookkeeping: it lists by tag and acts on what DigitalOcean +# reports. +# +# Talks to both APIs with curl. No doctl: its saved contexts silently choose +# an account; a token in the environment cannot. Needs curl and jq. +# +# Environment: +# DIGITALOCEAN_TOKEN DO API token for the account that pays for droplets +# GITHUB_TOKEN fine-grained PAT: Actions read, Administration write +# REPO owner/name +set -euo pipefail + +REPO=${REPO:?owner/name} +: "${DIGITALOCEAN_TOKEN:?}" "${GITHUB_TOKEN:?}" +LABEL=${LABEL:-omarchy-builder} +TAG=${TAG:-omarchy-builder} +REGION=${REGION:-ric1} +SIZE=${SIZE:-g5-32vcpu-64gb-50gb} +IMAGE=${IMAGE:-ubuntu-24-04-x64} +MAX_DROPLETS=${MAX_DROPLETS:-4} +MAX_AGE_MINUTES=${MAX_AGE_MINUTES:-200} +RUNNER_VERSION=${RUNNER_VERSION:-2.337.0} +CLOUD_INIT=${CLOUD_INIT:-$(dirname "$0")/runner-cloud-init.yaml} +# Operator public keys authorized on every builder (JSON array of strings). +# The box's env file carries them; empty means no root login. +SSH_KEYS_JSON=${SSH_KEYS_JSON:-[]} +LOCK=${LOCK:-/tmp/omarchy-controller.lock} + +log() { echo "$(date '+%F %T') $*"; } + +# The only two places the outside world is touched. The self-test overrides +# both, so every decision below is exercised against canned responses. +do_api() { # do_api [curl args...] + local path=$1; shift + curl -fsS -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ + -H "Content-Type: application/json" "https://api.digitalocean.com/v2/$path" "$@" +} +gh_api() { # gh_api [curl args...] + local path=$1; shift + curl -fsS -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" "https://api.github.com/$path" "$@" +} + +# --- reap ------------------------------------------------------------------ +reap() { + local now id status created age + now=$(date +%s) + while read -r id status created; do + [[ -n "$id" ]] || continue + age=$(( (now - $(date -d "$created" +%s)) / 60 )) + if [[ $status == off ]] || (( age > MAX_AGE_MINUTES )); then + log "deleting droplet $id (status=$status age=${age}m)" + do_api "droplets/$id" -X DELETE + fi + done < <(do_api "droplets?tag_name=$TAG&per_page=200" | + jq -r '.droplets[] | "\(.id) \(.status) \(.created_at)"') +} + +# --- demand ---------------------------------------------------------------- +queued_jobs() { + local run + gh_api "repos/$REPO/actions/runs?status=queued&per_page=50" --get \ + | jq -r '.workflow_runs[].id' | + while read -r run; do + gh_api "repos/$REPO/actions/runs/$run/jobs" \ + | jq -r --arg l "$LABEL" '.jobs[] | select(.status=="queued") | select(.labels | index($l)) | .id' + done | wc -l +} + +live_droplets() { + do_api "droplets?tag_name=$TAG&per_page=200" | jq '[.droplets[] | select(.status != "off")] | length' +} + +busy_runners() { + gh_api "repos/$REPO/actions/runners?per_page=100" \ + | jq --arg l "$LABEL" '[.runners[] | select(.busy) | select(any(.labels[]; .name == $l))] | length' +} + +# --- create ---------------------------------------------------------------- +create_droplet() { + local token userdata name body + token=$(gh_api "repos/$REPO/actions/runners/registration-token" -X POST | jq -r .token) + userdata=$(sed -e "s|__REPO__|$REPO|g" -e "s|__RUNNER_TOKEN__|$token|g" \ + -e "s|__RUNNER_LABELS__|$LABEL|g" -e "s|__RUNNER_VERSION__|$RUNNER_VERSION|g" \ + -e "s|__SSH_KEYS_JSON__|$SSH_KEYS_JSON|" "$CLOUD_INIT") + name="$TAG-$(date +%s)-$RANDOM" + body=$(jq -n --arg name "$name" --arg region "$REGION" --arg size "$SIZE" --arg image "$IMAGE" \ + --arg tag "$TAG" --arg ud "$userdata" \ + '{name:$name, region:$region, size:$size, image:$image, tags:[$tag], user_data:$ud, monitoring:false}') + log "creating $name ($SIZE)" + do_api droplets -X POST -d "$body" | jq -r '"created droplet \(.droplet.id)"' +} + +controller_tick() { + reap + local queued live busy available need room + queued=$(queued_jobs) + live=$(live_droplets) + busy=$(busy_runners) + # A live droplet whose runner is busy is spoken for. Only droplets still + # booting or listening can absorb a queued job. + available=$(( live - busy )); (( available < 0 )) && available=0 + need=$(( queued - available )) + (( need > 0 )) || return 0 + room=$(( MAX_DROPLETS - live )) + (( need > room )) && need=$room + if (( need <= 0 )); then + log "at cap ($live/$MAX_DROPLETS, $busy busy) with $queued queued" + return 0 + fi + local i + for (( i = 0; i < need; i++ )); do create_droplet; done +} + +if [[ "${CONTROLLER_LIBRARY_ONLY:-}" != 1 ]]; then + exec 9>"$LOCK"; flock -n 9 || exit 0 + controller_tick +fi diff --git a/ci/runner-cloud-init.yaml b/ci/runner-cloud-init.yaml new file mode 100644 index 0000000..c05e32e --- /dev/null +++ b/ci/runner-cloud-init.yaml @@ -0,0 +1,81 @@ +#cloud-config +# Ephemeral GitHub Actions runner for omarchy-pkgs package builds. +# +# Boots an Ubuntu droplet, installs docker and the runner, registers ONCE with +# --ephemeral, runs exactly one job, then powers off. The controller (or the +# reaper) deletes the powered-off droplet. Nothing here holds a long-lived +# credential: the registration token is single-use and expires in an hour. +# +# Substitute before use: +# __REPO__ owner/name +# __RUNNER_TOKEN__ registration token (gh api -X POST repos/O/R/actions/runners/registration-token) +# __RUNNER_LABELS__ e.g. omarchy-builder +# __RUNNER_VERSION__ e.g. 2.329.0 + +# Operators can reach a builder by key while it lives; it powers off after +# one job anyway. Keys are substituted by the controller (__SSH_KEYS_JSON__). +disable_root: false +chpasswd: + expire: false +ssh_authorized_keys: __SSH_KEYS_JSON__ + +package_update: true +packages: + - docker.io + - docker-buildx + - unzip + - git + - curl + - jq + - rsync + +users: + - name: runner + groups: [docker] + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + +write_files: + # defer: write after users/groups exist, so /home/runner is created by + # useradd (owned by runner) rather than by this module as root. + - path: /home/runner/start.sh + permissions: "0755" + owner: runner:runner + defer: true + content: | + #!/bin/bash + set -euo pipefail + cd /home/runner + mkdir -p actions-runner && cd actions-runner + arch=$(uname -m); [[ $arch == x86_64 ]] && arch=x64 + curl -fsSL -o runner.tgz \ + "https://github.com/actions/runner/releases/download/v__RUNNER_VERSION__/actions-runner-linux-${arch}-__RUNNER_VERSION__.tar.gz" + tar xzf runner.tgz && rm runner.tgz + ./config.sh --unattended --ephemeral \ + --url "https://github.com/__REPO__" \ + --token "__RUNNER_TOKEN__" \ + --name "do-$(hostname)" \ + --labels "__RUNNER_LABELS__" \ + --replace + ./run.sh + # One job done. Power off; the controller deletes powered-off droplets. + sudo poweroff + +runcmd: + # With no account ssh key attached, DO expires root's password, and sshd + # then refuses every non-interactive session. Clear it first so operators + # can read the logs of a builder that never registers. + - chage -d "$(date +%F)" -M -1 root + - systemctl enable --now docker + # aarch64 builds run under user-mode emulation (DO has no arm droplets). + # Register QEMU with the F and C flags via tonistiigi/binfmt, exactly as + # helpers/docker-helpers.sh setup_qemu does. Ubuntu's qemu-user-static + # registers without C, so sudo inside the emulated container fails with + # "effective uid is not 0"; multiarch/qemu-user-static is abandoned at QEMU + # 7.2, under which qmake's compiler probe returns nothing on current gcc + # ("failed to parse default include paths", PR #517). Pin the emulator + # version: the tag is the only thing that decides what every aarch64 build + # runs under. Best-effort: an x86-only job never needs it. + - docker run --rm --privileged docker.io/tonistiigi/binfmt:qemu-v10.2.3-68 --uninstall qemu-aarch64 --install arm64 || true + - chown -R runner:runner /home/runner + - sudo -u runner /home/runner/start.sh > /home/runner/runner.log 2>&1 diff --git a/docs/upstream-sources.md b/docs/upstream-sources.md new file mode 100644 index 0000000..9ef208f --- /dev/null +++ b/docs/upstream-sources.md @@ -0,0 +1,182 @@ +# 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. + +`linux-firmware-cirrus` is a deliberate hold: a self-retiring shim that ships Arch's linux-firmware-cirrus 20260910-2 payload to stable while stable's Arch snapshot is on 20260810-2 (Dell XPS 13 DX13260 / 1028:0e54 speaker firmware). It is versioned 20260810-3 so the genuine Arch package supersedes it as soon as the snapshot advances; bumping it to the Arch version would defeat that. Delete the recipe once stable's snapshot carries linux-firmware >= 20260910. + +`m1n1-aurora` and `uboot-asahi` are deliberate holds: Apple Silicon boot code, pinned by hand like `linux-aurora`, and bumped only after a cold boot on the qualification Macs. `m1n1-aurora` pins an aurora-silicon/m1n1 commit plus a local patch. `uboot-asahi` follows asahi-alarm's recipe and patch set (asahi-alarm/PKGBUILDs), which a tag watch on AsahiLinux/u-boot cannot carry. + +## 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/artifact-helpers.sh b/helpers/artifact-helpers.sh new file mode 100644 index 0000000..3d8bb4f --- /dev/null +++ b/helpers/artifact-helpers.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Package files cross from a PR build to publish.yml as one GitHub Actions +# artifact. actions/upload-artifact rejects any path containing ':', and a +# package with an epoch is named `name-1:ver-rel-arch.pkg.tar.zst` by +# makepkg. So the files ride inside a tar with a plain name and keep their +# own names untouched: pacman clients and bin/publish-artifact both rely on +# the filename matching PKGINFO. +# +# Both functions run under the workflow's `bash -e`: nothing in them may +# return non-zero except the final failure. + +# package_files : the *.pkg.tar.zst directly in , one per line. +# Signatures and the scratch database next to them are not packages. +package_files() { + local f + for f in "$1"/*.pkg.tar.zst; do + [[ -e "$f" ]] && printf '%s\n' "$f" + done + return 0 +} + +# pack_packages : every package in into . +pack_packages() { + local dir=$1 out=$2 files=() + mapfile -t files < <(package_files "$dir") + (( ${#files[@]} )) || { echo "pack_packages: no *.pkg.tar.zst in $dir" >&2; return 1; } + tar -cf "$out" -C "$dir" -- "${files[@]##*/}" +} + +# unpack_packages : the packages an unzipped artifact +# carried, into . Packed artifacts hold packages.tar; artifacts from +# builds before packing hold the bare files. The bare form can go once +# those artifacts have expired (7-day retention). +unpack_packages() { + local src=$1 dest=$2 files=() + mkdir -p "$dest" + if [[ -f "$src/packages.tar" ]]; then + tar -xf "$src/packages.tar" -C "$dest" + return 0 + fi + mapfile -t files < <(package_files "$src") + (( ${#files[@]} )) || { echo "unpack_packages: nothing to unpack in $src" >&2; return 1; } + cp -- "${files[@]}" "$dest/" +} diff --git a/helpers/docker-helpers.sh b/helpers/docker-helpers.sh index cc18222..22c8004 100644 --- a/helpers/docker-helpers.sh +++ b/helpers/docker-helpers.sh @@ -91,8 +91,19 @@ setup_qemu() { exit 1 fi - # Register emulators for builds whose target differs from the host. - if ! "$CONTAINER_ENGINE" run --rm --privileged docker.io/multiarch/qemu-user-static --reset -p yes --credential yes >/dev/null 2>&1; then + # Register emulators for builds whose target differs from the host, with + # the F and C flags (tonistiigi/binfmt always sets both). The image tag pins + # the QEMU version every emulated build runs under; multiarch/qemu-user-static + # stopped at QEMU 7.2, which breaks qmake's compiler probe on current gcc. + # Keep ci/runner-cloud-init.yaml on the same tag. Uninstall first: install + # leaves an existing registration (an older emulator) in place and exits 0. + local platform_arch + case "$target_arch" in + aarch64) platform_arch=arm64 ;; + x86_64) platform_arch=amd64 ;; + *) platform_arch="$target_arch" ;; + esac + if ! "$CONTAINER_ENGINE" run --rm --privileged docker.io/tonistiigi/binfmt:qemu-v10.2.3-68 --uninstall "qemu-$target_arch" --install "$platform_arch" >/dev/null 2>&1; then print_error "Failed to set up QEMU emulation" exit 1 fi 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/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/aether/PKGBUILD b/pkgbuilds/aether/PKGBUILD index 8927657..cef077c 100644 --- a/pkgbuilds/aether/PKGBUILD +++ b/pkgbuilds/aether/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Bjarne ร˜verli pkgname=aether -pkgver=4.29.8 +pkgver=4.30.0 pkgrel=1 pkgdesc='Desktop theming application - extract colors from wallpapers and apply cohesive themes' arch=('x86_64' 'aarch64') @@ -10,9 +10,9 @@ depends=('webkit2gtk-4.1' 'gtk3') source=("aether-${pkgver}.tar.gz::https://github.com/omacom/aether/archive/refs/tags/v${pkgver}.tar.gz") source_x86_64=("aether-linux-amd64-${pkgver}::https://github.com/omacom/aether/releases/download/v${pkgver}/aether-linux-amd64") source_aarch64=("aether-linux-arm64-${pkgver}::https://github.com/omacom/aether/releases/download/v${pkgver}/aether-linux-arm64") -sha256sums=('b83e0eeb1332b4ed655389a051d5b9b14a3e109968b7f278005e52c5e69a1e9c') -sha256sums_x86_64=('d3d2d07b32da7a495221ed271ee66f4a1e344c91dcec9b267ec0a74ab6e36462') -sha256sums_aarch64=('ffcfd23d0375a3f0c0ce014821cc5e1670f2e061c35e991340e75352dac9b731') +sha256sums=('f67c8d2c6f27f67a755bc279ece5ddb194f1bb165648280b9a7be86904d36ff5') +sha256sums_x86_64=('75bda600ddd3ecab3338de5c0c5d5e2c9f08cfc0c465b63f8e6cb9c5cb60d68e') +sha256sums_aarch64=('a91d800736def74d86e19d8acbecc4bda3d7c3e64fb95273f809707104c3a5bc') noextract=("aether-linux-amd64-${pkgver}" "aether-linux-arm64-${pkgver}") package() { diff --git a/pkgbuilds/aquamarine/.omarchy/package.json b/pkgbuilds/aquamarine/.omarchy/package.json new file mode 100644 index 0000000..7ddd330 --- /dev/null +++ b/pkgbuilds/aquamarine/.omarchy/package.json @@ -0,0 +1,6 @@ +{ + "source": "local", + "channels": [ + "edge" + ] +} diff --git a/pkgbuilds/aquamarine/0001-drm-re-read-possible-CRTCs-when-rescanning-connectors.patch b/pkgbuilds/aquamarine/0001-drm-re-read-possible-CRTCs-when-rescanning-connectors.patch new file mode 100644 index 0000000..bbf7043 --- /dev/null +++ b/pkgbuilds/aquamarine/0001-drm-re-read-possible-CRTCs-when-rescanning-connectors.patch @@ -0,0 +1,36 @@ +From 375e80ec3826b54618fdd5f2db708c0f2bb936ae Mon Sep 17 00:00:00 2001 +From: Marcelo Alcantara +Date: Wed, 16 Sep 2026 00:39:04 +1000 +Subject: [PATCH] drm: re-read possible CRTCs when rescanning connectors + +A driver can change a connector's possible CRTCs at runtime. Apple's DCP +driver narrows a Type-C port's encoder to the display pipeline the fabric +routed it to and relies on the following hotplug for userspace to re-read +it. possibleCrtcs was only read when the connector was first created, so a +rerouted port kept a stale mask and was never assigned its now-free CRTC +until the compositor restarted. +--- + src/backend/drm/DRM.cpp | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp +index 6618a26..b492dac 100644 +--- a/src/backend/drm/DRM.cpp ++++ b/src/backend/drm/DRM.cpp +@@ -1287,6 +1287,13 @@ void Aquamarine::CDRMBackend::scanConnectors() { + } else { + backend->log(AQ_LOG_DEBUG, std::format("drm: Connector id {} already initialized", connectorID)); + conn = *it; ++ ++ // drivers may narrow or widen these on hotplug, e.g. when a Type-C port is rerouted to another pipeline ++ const auto possibleCrtcs = drmModeConnectorGetPossibleCrtcs(gpu->fd, drmConn); ++ if (possibleCrtcs && possibleCrtcs != conn->possibleCrtcs) { ++ backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} possible CRTCs changed {:#x} -> {:#x}", conn->szName, conn->possibleCrtcs, possibleCrtcs)); ++ conn->possibleCrtcs = possibleCrtcs; ++ } + } + + conn->status = drmConn->connection; +-- +2.50.1 (Apple Git-155) + diff --git a/pkgbuilds/aquamarine/PKGBUILD b/pkgbuilds/aquamarine/PKGBUILD new file mode 100644 index 0000000..2b16e74 --- /dev/null +++ b/pkgbuilds/aquamarine/PKGBUILD @@ -0,0 +1,63 @@ +# Maintainer: Caleb Maclennan +# Contributor: Aaron Blasko +# +# Arch's aquamarine 0.15.1-1 plus one patch, carried on edge for Apple Silicon: +# re-read a connector's possible CRTCs when rescanning, so a Type-C port the +# DCP fabric reroutes to another display pipeline gets a CRTC without +# restarting the compositor. Drop the carry once hyprwm releases the fix. + +pkgname=aquamarine +pkgver=0.15.1 +pkgrel=1.1 +pkgdesc='a very light linux rendering backend library' +arch=(aarch64) +url="https://github.com/hyprwm/$pkgname" +license=(BSD-3-Clause) +depends=(libgcc + libstdc++ + glibc # libc.so libm.so + hyprutils libhyprutils.so + libdisplay-info libdisplay-info.so + libdrm # libdrm.so + libglvnd libEGL.so + libinput # libinput.so + mesa # libgbm.so + opengl-driver + pixman + seatd libseat.so + systemd-libs libudev.so + wayland libwayland-client.so + wayland-protocols) +makedepends=(cmake + hyprwayland-scanner) +provides=("lib$pkgname.so") +_archive="$pkgname-$pkgver" +source=("$url/archive/v$pkgver/$_archive.tar.gz" + 0001-drm-re-read-possible-CRTCs-when-rescanning-connectors.patch) +sha256sums=('2f9de98c0bd1b7b1b09c576e390a2fef436449762fb334163c414f0c300296f2' + '50e9e38ff915b18074dc9b5dd1d07d7f24e340e99f254476d6abe578eece7864') + +prepare() { + cd "$_archive" + patch -Np1 -i ../0001-drm-re-read-possible-CRTCs-when-rescanning-connectors.patch +} + +build() { + cd "$_archive" + # cc1plus needs more than 8 MiB of stack for DRM.cpp. GCC raises its own + # limit natively, but under QEMU user-mode emulation (the aarch64 builder) + # the guest stack is fixed at exec and setrlimit is ignored, so the compiler + # segfaults. QEMU reads this at exec; native builds ignore it. The object + # code is identical either way. + export QEMU_STACK_SIZE=64M + cmake -B build \ + -D CMAKE_INSTALL_PREFIX=/usr \ + -D CMAKE_BUILD_TYPE=Release + cmake --build build +} + +package() { + cd "$_archive" + DESTDIR="$pkgdir" cmake --install build + install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname" LICENSE +} 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/avd-fw/.omarchy/package.json b/pkgbuilds/avd-fw/.omarchy/package.json new file mode 100644 index 0000000..2d609e6 --- /dev/null +++ b/pkgbuilds/avd-fw/.omarchy/package.json @@ -0,0 +1,9 @@ +{ + "source": "local", + "upstream": { + "watch": { + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)", + "git_tags": "https://github.com/AsahiLinux/avd-fw.git" + } + } +} diff --git a/pkgbuilds/avd-fw/PKGBUILD b/pkgbuilds/avd-fw/PKGBUILD new file mode 100644 index 0000000..401e118 --- /dev/null +++ b/pkgbuilds/avd-fw/PKGBUILD @@ -0,0 +1,51 @@ +# Open replacement firmware for the Apple Video Decoder. +# Built from AsahiLinux sources, without extracting proprietary firmware. + +pkgname=avd-fw +pkgver=0.1 +pkgrel=1 +pkgdesc='Open replacement firmware for the Apple Video Decoder (AVD) on Apple Silicon' +# This recipe uses the native ARM linker and serves Apple Silicon systems. +# Restrict both builds and publication to aarch64. +arch=('aarch64') +url='https://github.com/AsahiLinux/avd-fw' +license=('MIT') +# clang cross-compiles to arm-none-eabi out of the box, so no arm-none-eabi +# toolchain is required; llvm supplies llvm-objcopy, which meson.build looks +# up by name to turn each linked ELF into a padded raw image. +makedepends=('meson' 'clang' 'llvm') +# !buildflags is load-bearing: makepkg's CFLAGS/LDFLAGS target the aarch64 host +# and would be injected into a bare-metal Cortex-M3 build. !strip keeps makepkg +# from running the host strip over raw firmware images. +options=('!strip' '!debug' '!buildflags' '!lto') +source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('2e131244275cb15c94243e41eec8a2a665ec895cfe3a818181549db991e62c67') + +build() { + # The cross file selects clang, pins the host machine to cortex-m3 and sets + # libdir=lib, which lands the images in /usr/lib/firmware/apple. + # + # Optimization is pinned to match upstream's Makefile (-O2). Meson's default + # buildtype is "debug" (-O0 -g), which would ship unoptimized firmware; the + # MMIO accessors in src/util.c go through a volatile typedef, so -O2 cannot + # elide register reads or writes. + meson setup \ + --cross-file "$pkgname-$pkgver/llvm.ini" \ + --prefix=/usr \ + -Doptimization=2 \ + -Ddebug=false \ + "$pkgname-$pkgver" build + + meson compile -C build +} + +package() { + meson install -C build --destdir "$pkgdir" + + # meson installs custom_target outputs 0755; these are firmware blobs, not + # programs, and every other firmware file on the system is 0644. + chmod 644 "$pkgdir"/usr/lib/firmware/apple/*.bin + + install -Dm644 "$pkgname-$pkgver/LICENSE" \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} diff --git a/pkgbuilds/bambustudio-bin/.omarchy/package.json b/pkgbuilds/bambustudio-bin/.omarchy/package.json new file mode 100644 index 0000000..ffa5b62 --- /dev/null +++ b/pkgbuilds/bambustudio-bin/.omarchy/package.json @@ -0,0 +1,17 @@ +{ + "source": "local", + "release_ring": "fast", + "origin": { + "aur": "bambustudio-bin", + "commit": "b962c12d14873f94669e0e256a444f957b1843cd" + }, + "upstream": { + "watch": { + "regex": "https://api.github.com/repos/bambulab/BambuStudio/releases/latest", + "pattern": "\"name\"\\s*:\\s*\"BambuStudio_ubuntu24\\.04-v(?P[0-9]+(?:\\.[0-9]+)*)-(?P[0-9]+)\\.AppImage\"", + "variables": { + "_build": "{build}" + } + } + } +} diff --git a/pkgbuilds/bambustudio-bin/BambuStudio.desktop b/pkgbuilds/bambustudio-bin/BambuStudio.desktop new file mode 100644 index 0000000..74ab062 --- /dev/null +++ b/pkgbuilds/bambustudio-bin/BambuStudio.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=Bambu Studio +GenericName=3D Printing Software +Comment=Slicer for Bambu Lab and other 3D printers +Exec=/usr/bin/bambu-studio %U +Icon=BambuStudio +Terminal=false +Type=Application +Categories=Graphics;3DGraphics;Engineering; +MimeType=x-scheme-handler/bambustudio;x-scheme-handler/bambustudioopen;model/stl;model/3mf;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf; +Keywords=3D;Printing;Slicer;gcode;stl;3mf; +StartupWMClass=bambu-studio diff --git a/pkgbuilds/bambustudio-bin/PKGBUILD b/pkgbuilds/bambustudio-bin/PKGBUILD new file mode 100644 index 0000000..f1a7c64 --- /dev/null +++ b/pkgbuilds/bambustudio-bin/PKGBUILD @@ -0,0 +1,48 @@ +# Maintainer: goll +# Contributor: George Woodall +pkgname=bambustudio-bin +pkgver=02.08.02.61 +pkgrel=1 +pkgdesc="PC Software for BambuLab's 3D printers" +arch=("x86_64") +url="https://github.com/bambulab/BambuStudio" +license=('AGPL-3.0-only') +conflicts=('bambustudio' 'bambustudio-git') +depends=('cairo' 'dbus' 'fontconfig' 'gcc-libs' 'glib2' 'glibc' + 'gst-libav' 'gst-plugins-base-libs' 'gstreamer' 'gtk3' 'libglvnd' + 'libx11' 'mesa' 'pango' 'wayland' 'webkit2gtk-4.1') +makedepends=('7zip') +options=('!strip' '!debug') +# The upstream watch updates the timestamp together with pkgver. +_build=20260820225108 +source=("bambustudio-${pkgver}.AppImage::https://github.com/bambulab/BambuStudio/releases/download/v${pkgver}/BambuStudio_ubuntu24.04-v${pkgver}-${_build}.AppImage" + "BambuStudio.desktop" + "bambu-studio") +noextract=("bambustudio-${pkgver}.AppImage") +sha256sums=( + 'd501b103fac5424513ec0e8d6bc145fb30719de2c7d94d7320d723740c81a7fd' + 'f10718a8b201cad64800746fe8167ccc032c545d05f7ad8caa99eb5fb975f2a1' + 'a3a5c8f6a8b287e42b93957e9602621923c766e0b6a3f10c14eca10b023b15f2' +) + +prepare() { + # Read the embedded SquashFS without executing or modifying the AppImage. + rm -rf "$srcdir/squashfs-root" + 7z x "$srcdir/bambustudio-${pkgver}.AppImage" -o"$srcdir/squashfs-root" >/dev/null +} + +package() { + cd "$srcdir/squashfs-root" + install -Dm755 AppRun "$pkgdir/opt/$pkgname/AppRun" + cp -a bin resources "$pkgdir/opt/$pkgname/" + + local icon size + for icon in usr/share/icons/hicolor/*/apps/BambuStudio.png; do + size="${icon#usr/share/icons/hicolor/}" + install -Dm644 "$icon" "$pkgdir/usr/share/icons/hicolor/$size" + done + + install -Dm755 "$srcdir/bambu-studio" "$pkgdir/usr/bin/bambu-studio" + install -Dm644 "$srcdir/BambuStudio.desktop" \ + "$pkgdir/usr/share/applications/BambuStudio.desktop" +} diff --git a/pkgbuilds/bambustudio-bin/bambu-studio b/pkgbuilds/bambustudio-bin/bambu-studio new file mode 100755 index 0000000..d1d3fb9 --- /dev/null +++ b/pkgbuilds/bambustudio-bin/bambu-studio @@ -0,0 +1,2 @@ +#!/bin/bash +exec "/opt/bambustudio-bin/AppRun" "$@" diff --git a/pkgbuilds/basecamp-cli/.omarchy/package.json b/pkgbuilds/basecamp-cli/.omarchy/package.json index 9dce3d6..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": "89c4eb5a0ac9ffe98aecd4ea8b789cf1fee42bf1" + "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 d7a1f7c..b0bd117 100644 --- a/pkgbuilds/basecamp-cli/PKGBUILD +++ b/pkgbuilds/basecamp-cli/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Basecamp pkgname=basecamp-cli pkgver=0.11.0 -pkgrel=1 +pkgrel=2 pkgdesc="CLI for Basecamp project management" arch=('x86_64' 'aarch64') url="https://github.com/basecamp/basecamp-cli" @@ -13,8 +13,8 @@ optdepends=( 'zsh: for zsh shell completions' 'fish: for fish shell completions' ) -source_x86_64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.11.0/basecamp_${pkgver}_linux_amd64.tar.gz") -source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.11.0/basecamp_${pkgver}_linux_arm64.tar.gz") +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') 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 2c28ef3..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": "f7dd445621ab53f9e51b70b351ff6917ff814175" + "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 e756024..10c950a 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.268 +pkgver=2.1.278 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=('9691a2b7bd796712ca8cffb8e32e54ff7fc45b662540233171a16a94a0425653') -sha256sums_aarch64=('116fd031f939ef1e09edf170d62c489e1cc28ed6bfbda49f948773ba168c8f62') +sha256sums_x86_64=('5c4735937844e84f8a93306e841a5b0e12252909b07870f789b190468da147ab') +sha256sums_aarch64=('7de6cab134e48321148e30182c98614118e8f4666819412bead45865190b34ed') package() { install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude" diff --git a/pkgbuilds/claude-desktop/PKGBUILD b/pkgbuilds/claude-desktop/PKGBUILD index e212e2d..919b043 100644 --- a/pkgbuilds/claude-desktop/PKGBUILD +++ b/pkgbuilds/claude-desktop/PKGBUILD @@ -6,7 +6,7 @@ # repository's package index. pkgname=claude-desktop -pkgver=1.52386.0 +pkgver=2.2553.1 pkgrel=1 pkgdesc="Official Claude desktop app with Claude Code" arch=('x86_64' 'aarch64') @@ -63,8 +63,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=('edfdbc63b65891ef7c481b07086c7e630fc102c042b6ed65331a52fcaf72b14a') -sha256sums_x86_64=('9c5d113ea2c31c0d4f6075c02e6180bf4ab53d35736b9a497ce0e84f62e9654b') -sha256sums_aarch64=('71c7d32a8edee57870db54f59a455bd05f92b2e18b45a1510b6ca9099ebd77d7') +sha256sums_x86_64=('6700fdd84e77a6b8c93912c2f69eb5d1e40fa99bcd9d37f438f809ef2a6fe6f8') +sha256sums_aarch64=('0003a6f9605a210f03c38670d62cd59c71153c2702aa4427e4cabe2e2e5f3390') package() { cd "${srcdir}" diff --git a/pkgbuilds/cliamp/.omarchy/package.json b/pkgbuilds/cliamp/.omarchy/package.json index 665cb10..94f9d65 100644 --- a/pkgbuilds/cliamp/.omarchy/package.json +++ b/pkgbuilds/cliamp/.omarchy/package.json @@ -1,4 +1,13 @@ { - "source": "aur", - "upstream_commit": "92fb021a1c78f75043cbbd402aca8b515af069ec" + "source": "local", + "upstream": { + "watch": { + "github": "bjarneo/cliamp", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "cliamp", + "commit": "92fb021a1c78f75043cbbd402aca8b515af069ec" + } } diff --git a/pkgbuilds/crush-bin/.omarchy/package.json b/pkgbuilds/crush-bin/.omarchy/package.json index 82f3393..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": "da836cc4d2b9373143236030cb0dff18a31b5b97" + "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 8426194..6a95b22 100644 --- a/pkgbuilds/crush-bin/PKGBUILD +++ b/pkgbuilds/crush-bin/PKGBUILD @@ -3,7 +3,7 @@ # Maintainer: caarlos0 pkgname='crush-bin' -pkgver=0.93.1 +pkgver=0.96.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=('200b9162815187771d99c813f2d71f4b2efe5a94a7830e7ed34fc25764e670ae') +sha256sums_aarch64=('667062a39d499506b0fe151148f8d7a1c5cb5722902080d44bb3dd2ddafbf5c1') source_armv7h=("${pkgname}_${pkgver}_armv7h.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_armv7.tar.gz") -sha256sums_armv7h=('368bdba39bcf7cbfb2f0522a2a0560f63f8cd11b18e6bff0b886cd18018a89c2') +sha256sums_armv7h=('1de4c1ccb237743e4debb8c302df612fcef5c9b3b5378f5b65c8c6f8bc15cbfa') source_i686=("${pkgname}_${pkgver}_i686.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_i386.tar.gz") -sha256sums_i686=('0ecda04da536cd42b24c8620140720f4fe0af5f5109ca32cee6d53960628313c') +sha256sums_i686=('4ec66431565de5721afb7afdd99e45ff6bc9c7e667bd18d9696ea7c5622e158d') 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=('3086719c3e4ff592b567c22691157457158b87245469009e530447481c975102') +sha256sums_x86_64=('5b33303a404acacf761c027e9fa9e69d4d2dd050c2690abe40877c49574b7475') package() { case "$CARCH" in diff --git a/pkgbuilds/cua-driver-bin/PKGBUILD b/pkgbuilds/cua-driver-bin/PKGBUILD index 59ba5b5..f9679fb 100644 --- a/pkgbuilds/cua-driver-bin/PKGBUILD +++ b/pkgbuilds/cua-driver-bin/PKGBUILD @@ -18,7 +18,7 @@ # binary to point at pm.sh, a stand-in that declines and names pacman instead. pkgname=cua-driver-bin -pkgver=0.27.0 +pkgver=0.28.2 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=('331af6773b44ee781e8d6ca0e47a35aee741355bb423dfe2bd4f4acd5b697677') -sha256sums_aarch64=('2d973a8d82714cc7a80b99d107bfc68abb6bf0a81e98ff225cc85b0acfebefb3') +sha256sums_x86_64=('8f3e5b669e2bcd98d0eecc64f40640aac77f358b6332a06abc6ee79991620f7d') +sha256sums_aarch64=('cadd7e6b757c3ce50f2b5f6e273c154ea48450fb5fcaff744209b382915eddf5') 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..9e0f9b0 --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/PKGBUILD @@ -0,0 +1,208 @@ +# 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=6 +pkgdesc='Cua input candidate for reviewed profile omarchy-hypr0562r3-aq0151-remaps' +arch=('x86_64') +url='https://github.com/trycua/cua' +license=('MIT') +depends=('hyprland=0.56.2-3' 'aquamarine=0.15.1-1' '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='819779b93655d603d9ebb0d33ea052326c3374674a1d473886106af25e0fffdd' +_profile_sha256='a2eade8b8195036be2c666788a1d28ac722403285a35987d92b3344e095f9a5e' +_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' + 'a2eade8b8195036be2c666788a1d28ac722403285a35987d92b3344e095f9a5e') + +# 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') +# Derive the reviewed Hyprland -3/Aquamarine 0.15.1 profile while preserving +# upstream source/tooling and compiler, compositor, header and runtime hashes. +profile_data = Path(profile_path).read_bytes() +require(digest(profile_data) == 'a2eade8b8195036be2c666788a1d28ac722403285a35987d92b3344e095f9a5e', + 'local profile checksum mismatch') +profile = json.loads(payload['PROFILE.json']) +profile.update(profile_id='omarchy-hypr0562r3-aq0151-remaps', package_release=6) +profile['hyprland']['package_version'] = '0.56.2-3' +profile['runtime']['packages']['aquamarine'] = '0.15.1-1' +require(json.loads(profile_data) == profile, 'local profile changes more than the reviewed package versions') +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=6\n'), ('omarchy-stable-20260910', profile['profile_id']), + ('hyprland=0.56.2-2', 'hyprland=0.56.2-3'), + ('aquamarine=0.15.0-2', 'aquamarine=0.15.1-1'), + ('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': 'a2eade8b8195036be2c666788a1d28ac722403285a35987d92b3344e095f9a5e', + 'KIT-PROVENANCE.json': '819779b93655d603d9ebb0d33ea052326c3374674a1d473886106af25e0fffdd', + 'PKGBUILD': 'c3e4149eba3f7def10ef700b30b9d0abcea994e3dd32fb169014874a0b75687c', + 'SHA256SUMS': 'd9057a9534f7a820ee04cbf5d60db567c5072fa96d1f71030a6a85b4bb7ce881'}) +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..c24dc2d --- /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": 6, + "profile_id": "omarchy-hypr0562r3-aq0151-remaps", + "runtime": { + "basename": "libstdc++.so.6.0.36", + "packages": { + "aquamarine": "0.15.1-1", + "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..c6c85fc --- /dev/null +++ b/pkgbuilds/cua-hyprland-plugin/README.md @@ -0,0 +1,215 @@ +# Optional Cua Hyprland plugin + +This package targets **Omarchy x86_64**, with Inkscape `1.4.4-6` and two independent background-input lanes. Release `6` is an edge candidate for Aquamarine `0.15.1-1`; it retains the keyboard-remap patch introduced in release `5`, which 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. The upstream qualification covers the original stable profile; the updated Aquamarine profile needs its own Omabot validation before promotion. + +## 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-hypr0562r3-aq0151-remaps`, kit tooling `1.1.0`, and package release `6` 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, including Aquamarine `0.15.1-1`. + +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 and Aquamarine package versions. Compiler, libstdc++ runtime, upstream source, compositor 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 and Aquamarine update need their own application and Driver replay before promotion. Hyprland `0.56.2-3` and Aquamarine `0.15.1-1` must both 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. + +## Aquamarine dependency refresh + +Release `5` required Aquamarine `0.15.0-2`. When the edge mirror moved to `0.15.1-1`, pacman could no longer resolve that dependency, even after a full database refresh. Release `6` derives a new profile from the same verified upstream kit and pins `0.15.1-1` in both the package dependencies and the installed compatibility verifier. Source, patch, compiler, compositor, headers, and libstdc++ hashes remain pinned; the original upstream qualification does not establish compatibility with the changed Aquamarine package. + +A package release bump alone cannot repair future dependency drift: the checked-in profile and derived kit checksums must agree with the new environment, and affected native checks must pass before publication. Do not remove exact dependencies or selectively downgrade a library to bypass a mismatch. + +## 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 819779b93655d603d9ebb0d33ea052326c3374674a1d473886106af25e0fffdd \ + --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 f25ef1d..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": "efe34ef2f0615a8ec251ef0b632d2999c906df47" + "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 b0bda1c..ccfccc2 100644 --- a/pkgbuilds/cursor-bin/PKGBUILD +++ b/pkgbuilds/cursor-bin/PKGBUILD @@ -1,34 +1,53 @@ # Maintainer: Gunther Schulz pkgname=cursor-bin -pkgver=3.20.10 -pkgrel=1 +pkgver=3.21.16 +pkgrel=3 pkgdesc='AI-first coding environment' -arch=('x86_64') +arch=('x86_64' 'aarch64') url="https://www.cursor.com" license=('LicenseRef-Cursor_EULA') # upstream uses Electron newer than internal VSCode _electron=electron42 -depends=(xdg-utils ripgrep $_electron nodejs - 'gcc-libs' 'hicolor-icon-theme' 'libxkbfile') +depends=(xdg-utils gcc-libs hicolor-icon-theme libxkbfile) +depends_x86_64=(ripgrep $_electron nodejs) +# ARM retains bundled Electron because electron42 is unavailable in ALARM. +depends_aarch64=( + alsa-lib at-spi2-core ca-certificates cairo curl dbus expat glib2 glibc gtk3 + libcups libsecret libx11 libxcb libxcomposite libxdamage libxext libxfixes + libxkbcommon libxrandr mesa nspr nss pango systemd-libs which +) options=(!strip !debug) # Don't break ext of VSCode -_commit=d6f462cdd0a6a6d1cff570daf980e671d0a63ded -source=("https://downloads.cursor.com/production/${_commit}/linux/x64/deb/amd64/deb/cursor_${pkgver}_amd64.deb" +_commit=8ae78e8eee1e63479c7e0504b664bc0a80c6800f +source_x86_64=("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]=cee826a72bdc9502b968cd1c3b185fa576861a92dfa5a751b0422fceb23e45328fed7e16917cebe605c5fc674b86f0717bc74c03a0545b9242bcc635861d246f -noextract=(cursor_${pkgver}_amd64.deb) # avoid double tarball +sha512sums_x86_64=('032c86a5d51f154ce36b1a0bf34aa06b2d117666b4372a787ea3117fc0b2b1686e952c721388f2eaf7787f2e0581378c98608048126f7470df2e85e9dbd75ca4' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a') +source_aarch64=("https://downloads.cursor.com/production/${_commit}/linux/arm64/deb/arm64/deb/cursor_${pkgver}_arm64.deb") +sha512sums_aarch64=('88a163c130e7ee8d9f29b93ccf1258e9f5eb0138d4de5d2333f5bc2992c0a4d0c3a72e8f5912b2e9ac9819a8d758de8e7249cd33bdf3ac631271001ca92700f7') +noextract=(cursor_${pkgver}_amd64.deb cursor_${pkgver}_arm64.deb) # avoid double tarball _app=usr/share/cursor/resources/app package() { - # Exclude electron - bsdtar -xOf ${noextract[0]} data.tar.xz | tar -xJf - -C "$pkgdir" \ - --exclude 'usr/share/cursor/[^r]*' --exclude 'usr/share/cursor/*.pak' + local deb="cursor_${pkgver}_amd64.deb" + local -a excludes=(--exclude 'usr/share/cursor/[^r]*' --exclude 'usr/share/cursor/*.pak') + if [[ "$CARCH" == aarch64 ]]; then + deb="cursor_${pkgver}_arm64.deb" + # Keep bundled Electron; omit Debian's AppArmor and sysctl settings. + excludes=(--exclude './etc') + fi + bsdtar -xOf "${srcdir}/${deb}" data.tar.xz | tar -xJf - -C "$pkgdir" "${excludes[@]}" cd "$pkgdir" + # Disable Cursor's bundled updater; Omarchy manages updates via pacman (#238). + sed -i '/^[[:space:]]*"\(backupUpdateUrl\|updateUrl\)":/d' \ + "${_app}/product.json" mv usr/share/zsh/{vendor-completions,site-functions} + if [[ "$CARCH" == aarch64 ]]; then + install -d usr/bin + ln -s /usr/share/cursor/bin/cursor usr/bin/cursor + # Use Electron's unprivileged namespace sandbox. + chmod 0755 usr/share/cursor/chrome-sandbox + return + fi ln -sf /usr/bin/node ${_app}/resources/helpers/node install -Dm755 "${srcdir}/rg.sh" ${_app}/node_modules/@vscode/ripgrep/bin/rg ln -sf /usr/bin/xdg-open ${_app}/node_modules/open/xdg-open 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..c8fc1d5 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.18.1.9a7762b # 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=('3fccee6929df1042d03461895e56c222a996d3ae9e4f9c61dcc5e6ab7b1d075d3265c21a44f48dd94de0dcde4f8012cc39bfbf323e2bb0c6106cac79885c35ae') +b2sums_aarch64=('3d3bb0a3cb7e2409acf4925f207eaa4e3f41782c3c947e2834b69664b116b972da0b67eea17147fc524ea248af9919494570adc7c48d12c44f12deca17ba2c28') 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/dell-xps-touchpad-haptics/PKGBUILD b/pkgbuilds/dell-xps-touchpad-haptics/PKGBUILD index 68d9967..9d35b3a 100644 --- a/pkgbuilds/dell-xps-touchpad-haptics/PKGBUILD +++ b/pkgbuilds/dell-xps-touchpad-haptics/PKGBUILD @@ -2,7 +2,7 @@ pkgname=dell-xps-touchpad-haptics pkgver=1.0.0 -pkgrel=3 +pkgrel=4 pkgdesc="Synaptics haptic touchpad presets for Dell XPS on Omarchy" arch=('x86_64') url="https://github.com/omacom-io/omarchy-pkgs" diff --git a/pkgbuilds/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install b/pkgbuilds/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install index 587f96c..25ddfa4 100644 --- a/pkgbuilds/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install +++ b/pkgbuilds/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install @@ -3,6 +3,7 @@ _default_level="high" _env_path="/etc/dell-xps-touchpad-haptics.env" _legacy_env_path="/etc/omarchy-dell-haptic-touchpad.env" _legacy_override_dir="/etc/systemd/system/dell-xps-haptic-touchpad.service.d" +_runuser_path="/usr/bin/runuser" _existing_home() { local line value @@ -124,18 +125,13 @@ _ensure_user_config() { local config_dir="$home/.config/omarchy" local config_path="$config_dir/dell-haptic.conf" - if [[ ! -f $config_path ]] && ! env HOME="$home" USER="$user" LOGNAME="$user" \ + if [[ ! -f $config_path ]] && ! "$_runuser_path" --user "$user" -- \ + /usr/bin/env HOME="$home" USER="$user" LOGNAME="$user" \ /usr/bin/dell-xps-touchpad-haptics set "$_default_level"; then echo ":: Failed to create ${config_path} for user '$user'." >&2 return 1 fi - if [[ -f $config_path ]]; then - chown "$user:$user" "$home/.config" 2>/dev/null || true - chown "$user:$user" "$config_dir" 2>/dev/null || true - chown "$user:$user" "$config_path" 2>/dev/null || true - fi - return 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..216605a --- /dev/null +++ b/pkgbuilds/dotnet-core-bin/PKGBUILD @@ -0,0 +1,123 @@ +# 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=2 +arch=('aarch64') +url='https://www.microsoft.com/net/core' +license=('MIT') +options=('staticlibs' '!debug') +source=('dotnet.sh') +source_aarch64=("https://builds.dotnet.microsoft.com/dotnet/Sdk/${_sdkver}/dotnet-sdk-${_sdkver}-linux-arm64.tar.gz") +sha512sums=('768151c7179fb6a126b3de9cae01e363e8894f6fab384b1e2c5066c2adca4578638983b1b62aea10dd18045e6d6e8f8ea13280481134de94f004a118919b2c06') +sha512sums_aarch64=('58ace73ced6b4360754689a686bdfb8a317f4da6cb8bb416dbc7d0ba9f47e43e3c09f5eb1f1a1cfaacbd10df9558da4882bf2a5e195d6ab56a02c1f9f76102ed') + +# 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}) + + install -dm 755 "${pkgdir}"/usr/share/{dotnet,dotnet/packs,licenses} + cp -dr --no-preserve='ownership' packs/Microsoft.NETCore.App.{Host.linux-arm64,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/elsewhen/.omarchy/README.md b/pkgbuilds/elsewhen/.omarchy/README.md new file mode 100644 index 0000000..9195490 --- /dev/null +++ b/pkgbuilds/elsewhen/.omarchy/README.md @@ -0,0 +1,17 @@ +# elsewhen + +Installs the Elsewhen world clock plugin from the `v{pkgver}` GitHub tag archive into `/usr/share/omarchy/shell/plugins/omacom.elsewhen/` (the directory name is the plugin id the shell scans for, not the package name), plus `LICENSE` under `/usr/share/licenses/elsewhen/` and the upstream `README.md` under `/usr/share/doc/elsewhen/`. The shell scans this directory alongside its bundled plugins. + +`package()` copies an explicit allow-list (`manifest.json`, every `*.qml` and `*.js`, `cities.json`, `world.json`, `worldclock-data.py`), so `tests/`, `.github/` and `.gitignore` never ship, and it fails the build if `manifest.json` is missing, does not declare `omacom.elsewhen`, or does not name a present `Panel.qml` as the entry point. An upstream release that adds a runtime file outside those patterns needs the allow-list extended here; the sync only moves versions and checksums. Every file is 0644: `Panel.qml` runs the script as `python3 /worldclock-data.py`, so it needs no execute bit. No install hook: Omarchy restarts the shell after `omarchy update`, and nothing here may write into a user home. The script's only writes go to `$XDG_CACHE_HOME/omacom-elsewhen/`, which it creates itself at runtime. + +Dependencies, cited as `file: tool` in the upstream tree: + +- `omarchy`: `ArcText.qml`, `Chip.qml`, `EarthRow.qml`, `Globe.qml`, `MiniGlobe.qml`, `MoonDot.qml`, `Panel.qml: import qs.Commons`; `EarthRow.qml`, `Globe.qml`, `Panel.qml: import qs.Ui`. Owns `/usr/share/omarchy` and the shell plugin directory. +- `quickshell`: `Globe.qml`, `Panel.qml: import Quickshell`; `Globe.qml`, `MiniGlobe.qml`, `Panel.qml: import Quickshell.Io` (`Process`, `FileView`, `StdioCollector`). +- `python`: `Panel.qml: python3 /worldclock-data.py` (the facts process). `worldclock-data.py` imports only `json`, `os`, `sys`, `time`, `urllib`. +- Left implicit as members of `base`, per Arch convention: `bash` (`Panel.qml`, `Globe.qml: bash -c` wraps every probe), `coreutils` (`Panel.qml`, `Globe.qml: date`, one probe per refresh), `systemd` (`Panel.qml: timedatectl show`, `timedatectl list-timezones`, each with a fallback: `/etc/localtime` for the home zone and `find /usr/share/zoneinfo` for the catalog), `sed` and `grep` (`Panel.qml`: the symlink target of `/etc/localtime` and the zoneinfo catalog filter), `findutils` and `tzdata` (that fallback; `worldclock-data.py: /usr/share/zoneinfo/zone1970.tab`). `omarchy` cannot run without any of them either. +- Not a dependency: `iso-codes`. Only `tests/currency_check.py` reads `/usr/share/iso-codes/json`, to validate the currency table before a release; the runtime never touches it. + +Release tracking: `bin/sync-upstream` follows `omacom/elsewhen` through the `upstream.watch.github` provider, which reads the GitHub Releases feed (drafts and prereleases excluded; a tag with no published Release is not seen) and matches exactly `vX.Y.Z`, the grammar upstream's `scripts/set-version.sh` enforces. A newer release rewrites `pkgver`, resets `pkgrel` to 1, fetches `archive/refs/tags/v{pkgver}.tar.gz` again and rewrites `sha256sums` from the download. The Release's `published_at` is what lets `min_release_age: 24h` hold a fresh release for a day; `release_ring: fast` builds it straight to rc and stable as well as edge. + +The watch only moves on a version increase, so the first release's digest is filled in by hand (`curl -fsSL | sha256sum`), which is why the recipe carries a placeholder until the `v0.1.0` tag exists. Until upstream has published at least one Release, the watch finds nothing and fails the scheduled `sync-upstream` run for every package in the batch, so this recipe stays a draft until then. diff --git a/pkgbuilds/elsewhen/.omarchy/package.json b/pkgbuilds/elsewhen/.omarchy/package.json new file mode 100644 index 0000000..b1e7e71 --- /dev/null +++ b/pkgbuilds/elsewhen/.omarchy/package.json @@ -0,0 +1,11 @@ +{ + "source": "local", + "release_ring": "fast", + "min_release_age": "24h", + "upstream": { + "watch": { + "github": "omacom/elsewhen", + "pattern": "v(?P[0-9]+\\.[0-9]+\\.[0-9]+)" + } + } +} diff --git a/pkgbuilds/elsewhen/PKGBUILD b/pkgbuilds/elsewhen/PKGBUILD new file mode 100644 index 0000000..93639b1 --- /dev/null +++ b/pkgbuilds/elsewhen/PKGBUILD @@ -0,0 +1,63 @@ +# Maintainer: Spencer Bull + +pkgname=elsewhen +pkgver=1.0.0 +pkgrel=2 +pkgdesc='World clock plugin for the Omarchy shell' +arch=('any') +url='https://github.com/omacom/elsewhen' +license=('MIT') + +# What the plugin needs to load and run. It also shells out to bash, date +# (coreutils) and timedatectl (systemd) and reads /usr/share/zoneinfo +# (tzdata); those are members of the base group and stay implicit, per Arch +# convention. The citations for each entry are in .omarchy/README.md. +depends=( + 'omarchy' + 'python' + 'quickshell' +) + +options=('!debug') + +source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('3124f0c0a19ebc1b158bcf04151cddd6c733ceeead88052186b6a54c46bee263') + +package() { + # Install alongside the bundled plugins in the shell's plugin directory. + local plugin="$pkgdir/usr/share/omarchy/shell/plugins/omacom.elsewhen" + cd "$srcdir/$pkgname-$pkgver" || return 1 + + # The shell loads the entry point each manifest declares. A tree without + # either would install cleanly and never load, so fail the build instead of + # shipping it. + [[ -f manifest.json ]] || { + echo "release tree is missing manifest.json" >&2 + return 1 + } + grep -Eq '"id"[[:space:]]*:[[:space:]]*"omacom\.elsewhen"' manifest.json || { + echo "manifest.json does not declare the plugin id omacom.elsewhen" >&2 + return 1 + } + grep -Eq '"barWidget"[[:space:]]*:[[:space:]]*"Panel\.qml"' manifest.json || { + echo "manifest.json does not name Panel.qml as the bar widget entry point" >&2 + return 1 + } + [[ -f Panel.qml ]] || { + echo "release tree is missing the entry point Panel.qml" >&2 + return 1 + } + + # An explicit allow-list of runtime files, so tests/, .github/ and the rest + # of the repository never reach the package. Directories end up 0755 and + # every file 0644: worldclock-data.py runs as `python3 ` and needs no + # execute bit. An unmatched glob is left literal and fails install, which + # is the right outcome for a release tree missing its QML or JS. + local file + for file in manifest.json cities.json world.json worldclock-data.py *.qml *.js; do + install -Dm644 "$file" "$plugin/$file" + done + + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" +} diff --git a/pkgbuilds/flea/.omarchy/upstream.sh b/pkgbuilds/flea/.omarchy/upstream.sh index 3acff3e..15c8853 100755 --- a/pkgbuilds/flea/.omarchy/upstream.sh +++ b/pkgbuilds/flea/.omarchy/upstream.sh @@ -85,6 +85,14 @@ sharelink_qml=$(tar -xOzf "$tarball" "$expected_root/ui/ShareLink.qml") copyfile_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/copyfile.rs") regfile_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/regfile.rs") +# Every check below pins a literal line except the O_NOFOLLOW one. That check +# guards a property -- the copy opens its source with O_NOFOLLOW, so a symlink +# swapped in cannot redirect the read -- and pinning the exact call expression +# made it assert the spelling instead. v0.3.0 moved the first argument from +# `src` to `src.at` when directory-relative opens landed, kept O_NOFOLLOW, and +# hardened symlink handling further; the literal still refused it. Match the +# call and the flag together so a rename cannot read as a removed fix, while +# dropping O_NOFOLLOW still fails. if ! grep -Fq 'a.push("--".to_string());' <<<"$archive_rs" || ! grep -Fq 'let input = std::fs::canonicalize(input)' <<<"$archiveops_rs" || ! grep -Fq 'if op != "compress" && op != "extract"' <<<"$run_rs$archivereq_rs" || @@ -92,7 +100,7 @@ if ! grep -Fq 'a.push("--".to_string());' <<<"$archive_rs" || ! grep -Fq 'if !sandbox::available()' <<<"$mediaprobe_rs" || ! grep -Fq 'if !sandbox::available()' <<<"$metareq_rs" || ! grep -Fq 'copyToClipboard.command = ["wl-copy", url]' <<<"$sharelink_qml" || - ! grep -Fq 'regfile::open_if_regular(src, O_NOFOLLOW)' <<<"$copyfile_rs" || + ! grep -Eq 'open_if_regular\(.*O_NOFOLLOW' <<<"$copyfile_rs" || ! grep -Fq '.custom_flags(O_NONBLOCK | extra_flags)' <<<"$regfile_rs"; then printf 'Release %s does not contain every required upstream security fix\n' "$best_tag" >&2 exit 1 diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD index a525033..a8cd3fd 100644 --- a/pkgbuilds/flea/PKGBUILD +++ b/pkgbuilds/flea/PKGBUILD @@ -1,8 +1,8 @@ # Maintainer: GM pkgname=flea -pkgver=0.2.1 -pkgrel=3 +pkgver=0.3.1 +pkgrel=2 pkgdesc='Fast, keyboard-first file manager for Omarchy' arch=('x86_64' 'aarch64') url='https://github.com/thisisgm/flea' @@ -15,7 +15,10 @@ depends=( 'glib2' 'glibc' 'gvfs' + 'gvfs-afc' 'gvfs-dnssd' + 'gvfs-gphoto2' + 'gvfs-mtp' 'gvfs-nfs' 'gvfs-smb' 'hicolor-icon-theme' @@ -28,6 +31,7 @@ depends=( 'qt6-multimedia' 'qt6-webengine' 'shared-mime-info' + 'usbmuxd' 'util-linux' 'wl-clipboard' 'xdg-terminal-exec' @@ -48,7 +52,7 @@ options=('!debug') source=( "$url/releases/download/v$pkgver/$pkgname-v$pkgver.tar.gz" ) -sha256sums=('75f9ac0274a09a0d55cf7d9187943983c1b78a9e443465738b3ac8af8f2a77e9') +sha256sums=('b146ac3f5025da987eae623c4392c44ce69a6a7275a8df3ec1a84563920a4dd2') build() { cd "$pkgname-$pkgver" @@ -60,7 +64,13 @@ build() { check() { cd "$pkgname-$pkgver" - export CARGO_TARGET_DIR=target + # Upstream's release profile uses fat LTO, whose final link runs on one + # thread. build() keeps it for the shipped binary; the test harness is a + # throwaway second compile, so build it in parallel in its own target + # directory. + export CARGO_TARGET_DIR=target-check + export CARGO_PROFILE_RELEASE_LTO=thin + export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16 local -a test_args=() if ! /usr/bin/prlimit --cpu=30 --as=1073741824 \ @@ -99,6 +109,12 @@ check() { # 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. + # shelfundo::tests::undo_refuses_to_walk_a_stranger_back deletes a file, + # creates another under the same name, and expects undo to tell them + # apart by (dev, ino, kind). ext4 hands the freed inode number straight + # back to the next create, so on the builder's /tmp the stranger is + # identical and undo walks it back. tmpfs allocates inode numbers from a + # counter and never reuses one, which is what the test assumes. local -a filesystem_tests=( backend::menu_actions::tests:: backend::menudelete::tests:: @@ -106,6 +122,7 @@ check() { backend::trashbrowse::tests:: backend::trashdelete:: backend::trashmanifest::tests:: + shelfundo::tests:: ) # redo_refuses_changed_sources_and_destination_collisions writes a file and # then immediately asks redo to notice the edit. flea decides "changed" from @@ -123,6 +140,19 @@ check() { --skip backend::redo::tests::redo_refuses_changed_sources_and_destination_collisions ) + # These three expect spawning a missing or failing program to be reported + # as such. aarch64 builds run under QEMU user-mode emulation, where + # glibc's posix_spawn cannot see the child's failed execve: the spawn + # "succeeds" with exit 127, and the timing test's child never sleeps. + # Passes natively. + if [[ $CARCH == aarch64 ]]; then + test_args+=( + --skip backend::child::tests::a_child_is_noticed_when_it_exits_rather_than_at_the_next_poll_boundary + --skip backend::child::tests::a_child_that_never_started_is_told_apart_from_one_that_ran_and_failed + --skip backend::menu_registry::tests::unavailable_failed_and_oversized_queries_are_named_errors + ) + fi + 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 -- \ @@ -158,6 +188,9 @@ package() { install -Dm644 ui/qmldir ui/*.qml -t "$pkgdir/usr/share/flea/ui" install -Dm644 ui/js/*.js -t "$pkgdir/usr/share/flea/ui/js" + # Flea's Shelf setting installs this plugin into the user's plugin directory. + install -Dm644 shelf/manifest.json shelf/README.md shelf/*.qml shelf/*.js \ + -t "$pkgdir/usr/share/flea/shelf" ln -s /usr/share/omarchy/shell/Commons "$pkgdir/usr/share/flea/ui/Commons" ln -s /usr/share/omarchy/shell/Ui "$pkgdir/usr/share/flea/ui/Ui" } 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..71f4f52 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.86 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' + '4c6433345f08199e96dcf8db1c3e46a337dfab96cea32132d6127ffcd63a6200' + 'b94d2aab574cf3e0c8d950e72430186cdd918261a1376146de971fa90ba0a672' ) # Document: https://wiki.archlinux.org/title/Node.js_package_guidelines diff --git a/pkgbuilds/gliff/.omarchy/package.json b/pkgbuilds/gliff/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/gliff/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/gliff/PKGBUILD b/pkgbuilds/gliff/PKGBUILD new file mode 100644 index 0000000..c9d957b --- /dev/null +++ b/pkgbuilds/gliff/PKGBUILD @@ -0,0 +1,53 @@ +# Maintainer: David Heinemeier Hansson + +pkgname=gliff +pkgver=0.1.0 +pkgrel=1 +pkgdesc="Hyprland remote desktop over SSH (Vulkan Video, 4:4:4)" +arch=('x86_64') +url="https://github.com/kevinmcconnell/gliff" +license=('MIT') +depends=('gcc-libs' 'glibc' 'wayland' 'libxkbcommon' 'libdrm' 'mesa' + 'gtk4>=4.14' 'libadwaita>=1.5' 'vulkan-icd-loader' 'openssh') +makedepends=('cargo' 'pkgconf') +optdepends=('vulkan-radeon: Vulkan Video on AMD' + 'vulkan-intel: Vulkan Video on Intel' + 'vulkan-tools: vulkaninfo for debugging' + 'vulkan-validation-layers: driver call validation for development') +options=('!debug') + +# Upstream only publishes a rolling prerelease; pin its source for reproducible builds. +_commit=2edbfba52780c7ace15dbb05ca92177f493d4bc9 +source=("$pkgname-$_commit.tar.gz::$url/archive/$_commit.tar.gz") +sha256sums=('2d75961085a72fb8e34149de63e312a7336daa70fd6e157e30c2cd08c14eb1c7') + +prepare() { + cd "$pkgname-$_commit" + + cargo fetch --locked --target "$CARCH-unknown-linux-gnu" +} + +build() { + cd "$pkgname-$_commit" + + export CARGO_TARGET_DIR=target + cargo build --frozen --release +} + +check() { + cd "$pkgname-$_commit" + + export CARGO_TARGET_DIR=target + cargo test --frozen --release --workspace +} + +package() { + cd "$pkgname-$_commit" + + install -Dm755 target/release/gliff "$pkgdir/usr/bin/gliff" + install -Dm755 target/release/gliff-server "$pkgdir/usr/bin/gliff-server" + install -Dm755 target/release/gliff-probe "$pkgdir/usr/bin/gliff-probe" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" + install -Dm644 docs/hardware-quirks.md "$pkgdir/usr/share/doc/$pkgname/hardware-quirks.md" +} 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 02cc4d3..94e5e31 100644 --- a/pkgbuilds/herdr/PKGBUILD +++ b/pkgbuilds/herdr/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: David Heinemeier Hansson pkgname=herdr -pkgver=0.9.0 +pkgver=0.9.1 pkgrel=1 pkgdesc="Herdr terminal workspace manager for AI coding agents" arch=('x86_64' 'aarch64') @@ -13,13 +13,13 @@ replaces=('omarchy-herdr') conflicts=('omarchy-herdr') options=('!debug' '!lto') -_zigver=0.15.2 +_zigver=0.16.0 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=('1e83bff4b05834ed8281e16f1680e8f3e58375a94b2e3f2b3d021e28e293ef9a') -sha256sums_x86_64=('02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239') -sha256sums_aarch64=('958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f') +sha256sums=('03403d3ef80dcf2b954dd5d27eb636e6c4f5279d240b48de272b7f53e4b73093') +sha256sums_x86_64=('70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00') +sha256sums_aarch64=('ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17') prepare() { cd "herdr-$pkgver" 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..6c18559 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.3 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=('ed17ce083a71dd7e49a89218052ab475edd5a654cedf443853f6baacbb0a00e9') options=(!strip) depends=( which @@ -27,4 +27,3 @@ package() { } # vim:set ts=2 sw=2 et: syntax=sh - diff --git a/pkgbuilds/hype/.omarchy/package.json b/pkgbuilds/hype/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/hype/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/hype/PKGBUILD b/pkgbuilds/hype/PKGBUILD new file mode 100644 index 0000000..af2508f --- /dev/null +++ b/pkgbuilds/hype/PKGBUILD @@ -0,0 +1,42 @@ +# Maintainer: David Heinemeier Hansson + +pkgname=hype +pkgver=0.4.1 +pkgrel=1 +pkgdesc='Simple Markdown presentations with a visual slide editor' +arch=('x86_64' 'aarch64') +url='https://github.com/omacom/hype' +license=('MIT') +install='hype.install' +options=('!debug') +depends=( + 'ffmpeg' + 'hicolor-icon-theme' + 'qt6-base>=6.9' + 'qt6-declarative>=6.9' + 'qt6-multimedia>=6.9' + 'qt6-imageformats' + 'qt6-svg' + 'source-highlight' + 'zlib' + 'libwebp' + 'xdg-desktop-portal' +) +makedepends=('gcc' 'make') +source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('0724a9d18df7657b50dff21ffe3dec1c107e9191a9e9fd5ad40b56ed9d5d6f51') + +build() { + cd "$srcdir/$pkgname-$pkgver" + ./bin/build +} + +package() { + cd "$srcdir/$pkgname-$pkgver" + + install -Dm755 build/hype "$pkgdir/usr/bin/hype" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 pkgbuild/hype.svg "$pkgdir/usr/share/icons/hicolor/scalable/apps/hype.svg" + install -Dm644 pkgbuild/hype.desktop "$pkgdir/usr/share/applications/hype.desktop" + install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" +} diff --git a/pkgbuilds/hype/hype.install b/pkgbuilds/hype/hype.install new file mode 100644 index 0000000..5357602 --- /dev/null +++ b/pkgbuilds/hype/hype.install @@ -0,0 +1,12 @@ +post_install() { + command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q + command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q -t -f usr/share/icons/hicolor +} + +post_upgrade() { + post_install +} + +post_remove() { + post_install +} diff --git a/pkgbuilds/hyprland-preview-share-picker/PKGBUILD b/pkgbuilds/hyprland-preview-share-picker/PKGBUILD index b3d80be..0862780 100644 --- a/pkgbuilds/hyprland-preview-share-picker/PKGBUILD +++ b/pkgbuilds/hyprland-preview-share-picker/PKGBUILD @@ -2,7 +2,7 @@ pkgname="hyprland-preview-share-picker" pkgver=0.2.1 -pkgrel=1 +pkgrel=2 pkgdesc="An alternative share picker for hyprland with window and monitor previews" arch=(x86_64 aarch64) url="https://github.com/WhySoBad/hyprland-preview-share-picker" @@ -32,14 +32,14 @@ fn main() { } EOF - export RUSTUP_TOOLCHAIN=nightly + export RUSTUP_TOOLCHAIN=stable cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')" } build() { cd "$pkgname-$pkgver" - export RUSTUP_TOOLCHAIN=nightly + export RUSTUP_TOOLCHAIN=stable export CARGO_TARGET_DIR=target cargo build --frozen --release 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/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/learn-omarchy/.omarchy/package.json b/pkgbuilds/learn-omarchy/.omarchy/package.json new file mode 100644 index 0000000..7fed645 --- /dev/null +++ b/pkgbuilds/learn-omarchy/.omarchy/package.json @@ -0,0 +1,12 @@ +{ + "source": "local", + "release_ring": "fast", + "min_release_age": "24h", + "upstream": { + "github": "DanWahlin/learn-omarchy", + "checksums": "SHA256SUMS", + "assets": { + "any": "learn-omarchy-{pkgver}.tar.gz" + } + } +} diff --git a/pkgbuilds/learn-omarchy/PKGBUILD b/pkgbuilds/learn-omarchy/PKGBUILD new file mode 100644 index 0000000..90d4071 --- /dev/null +++ b/pkgbuilds/learn-omarchy/PKGBUILD @@ -0,0 +1,34 @@ +# Maintainer: Dan Wahlin + +pkgname=learn-omarchy +pkgver=0.2.2 +pkgrel=1 +pkgdesc="Interactive, theme-aware courses for learning Omarchy" +arch=('any') +url="https://github.com/DanWahlin/learn-omarchy" +license=('MIT' 'CC-BY-4.0' 'CC0-1.0') +depends=( + 'bash' 'coreutils' 'sudo' 'hyprland' 'mpv' 'nodejs>=22.6' 'omarchy' + 'quickshell>=0.3' 'qt6-declarative' 'qt6-multimedia' 'qt6-multimedia-ffmpeg' 'xdg-utils' + 'xdg-terminal-exec' 'nautilus' + 'ttf-liberation' 'noto-fonts-emoji' + 'grim' 'slurp' 'gpu-screen-recorder' 'util-linux' 'ffmpeg' +) +makedepends=('make') +optdepends=( + 'btop: activity-monitor practice' + 'tesseract: OCR practice' + 'tesseract-data-eng: English OCR sample recognition' + 'zbar: QR recognition (zbarimg)' + 'qrencode: QR sample creation' + 'voxtype: optional dictation practice' +) +options=('!strip') +source=("$pkgname-$pkgver.tar.gz::$url/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") +sha256sums=('67f14778c2b66d56c1504695b0e11cab603f1a002570abc94b4bae9b0bec6066') + +package() { + cd "$srcdir/$pkgname-$pkgver" + node tools/prepare-release.mjs --check . + make DESTDIR="$pkgdir" PREFIX=/usr install +} 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/libva-v4l2_request-avd/.omarchy/package.json b/pkgbuilds/libva-v4l2_request-avd/.omarchy/package.json new file mode 100644 index 0000000..5dc4867 --- /dev/null +++ b/pkgbuilds/libva-v4l2_request-avd/.omarchy/package.json @@ -0,0 +1,12 @@ +{ + "source": "local", + "upstream": { + "watch": { + "git_tags": "https://github.com/sofus13/libva-v4l2_request.git", + "pattern": "(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "rebuild_on": [ + "libva" + ] +} diff --git a/pkgbuilds/libva-v4l2_request-avd/PKGBUILD b/pkgbuilds/libva-v4l2_request-avd/PKGBUILD new file mode 100644 index 0000000..255f542 --- /dev/null +++ b/pkgbuilds/libva-v4l2_request-avd/PKGBUILD @@ -0,0 +1,55 @@ +# VA-API driver with Apple Video Decoder fixes from the sofus13 fork. +# The asahi alias lets libva discover it on Apple Silicon. + +_fork=sofus13 +pkgname=libva-v4l2_request-avd +pkgver=1.3 +pkgrel=1 +pkgdesc='VA-API driver for V4L2 stateless decoders, with Apple Video Decoder (AVD) support' +arch=('aarch64') +url="https://github.com/$_fork/libva-v4l2_request" +license=('GPL-3.0-or-later') +# The driver is a dlopen()ed plugin: it links only libc and exports the VA +# backend entry point (__vaDriverInit_1_24), so libva is a runtime rather than +# a link-time dependency -- and that symbol is versioned, so this needs a +# rebuild whenever libva's driver ABI is bumped. libdrm is only used for +# drm_fourcc.h constants at compile time. +depends=('glibc' 'libva') +makedepends=('meson' 'libdrm') +provides=('libva-v4l2_request') +conflicts=('libva-v4l2_request') +install="$pkgname.install" +source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/$pkgver.tar.gz") +sha256sums=('3670a1712f9f0a5a61bd44a781eba81dd9b2e0fd8271417ca8a9fbbed9dccfe9') + +build() { + # driverdir is left unset so meson takes it from libva's pkg-config + # (/usr/lib/dri); the codec set is decided by configure-time checks against + # the kernel uapi headers, so linux-api-headers governs what gets built in. + # Plain meson rather than arch-meson: the latter lives in devtools, which is + # not a safe assumption outside a clean chroot. buildtype=plain hands the + # compiler makepkg's own CFLAGS/LDFLAGS, which is what arch-meson does too. + meson setup \ + --prefix=/usr \ + --buildtype=plain \ + --wrap-mode=nodownload \ + "libva-v4l2_request-$pkgver" build + meson compile -C build +} + +package() { + meson install -C build --destdir "$pkgdir" + + # libva picks its driver from the DRM driver name of the render node, which + # on Apple Silicon is "asahi", so it looks for asahi_drv_video.so. Nothing + # else ships that name (mesa provides no VA driver for asahi), so aliasing it + # here makes every VA-API application find the decoder with no environment + # variable at all. If mesa ever ships its own asahi_drv_video.so this will + # collide and pacman will say so at install time. + ln -sf v4l2_request_drv_video.so "$pkgdir/usr/lib/dri/asahi_drv_video.so" + + install -Dm644 "libva-v4l2_request-$pkgver/COPYING" \ + "$pkgdir/usr/share/licenses/$pkgname/COPYING" + install -Dm644 "libva-v4l2_request-$pkgver/README" \ + "$pkgdir/usr/share/doc/$pkgname/README" +} diff --git a/pkgbuilds/libva-v4l2_request-avd/libva-v4l2_request-avd.install b/pkgbuilds/libva-v4l2_request-avd/libva-v4l2_request-avd.install new file mode 100644 index 0000000..e56488b --- /dev/null +++ b/pkgbuilds/libva-v4l2_request-avd/libva-v4l2_request-avd.install @@ -0,0 +1,21 @@ +post_install() { + cat <<'MSG' +==> This package installs itself as asahi_drv_video.so as well, which is the + name libva looks for on Apple Silicon, so VA-API applications should pick + the decoder up with no configuration. + + If something does not, select it explicitly for the session: + + LIBVA_DRIVER_NAME=v4l2_request + +==> Check with: vainfo +==> mpv: mpv --hwdec=vaapi video.mp4 +==> Firefox: additionally needs MOZ_DISABLE_RDD_SANDBOX=1, because the RDD + sandbox blocks the /dev/video* and /dev/media* access this + driver requires. That weakens Firefox's media sandbox. +MSG +} + +post_upgrade() { + post_install +} 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..caa9255 100644 --- a/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD +++ b/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD @@ -1,18 +1,18 @@ # 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=2 pkgdesc="Install kernels for the Limine bootloader." arch=('x86_64' 'aarch64') 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" - "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip") + "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip" + "limine-apple-gate" + "mkinitcpio-install.hook") license=("GPL3") provides=('limine-entry-tool') options=(!debug !strip) @@ -33,10 +33,12 @@ 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') + 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a' + '199fa2122e4a20ed80e6cf5df235b4e50739593803522bfa65700b1ef242b9ce' + '9fc607e81d2f09aa0dfd313805277af92977535bd222f1e86caf5fbb28138cb4') prepare() { [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" @@ -79,4 +81,58 @@ package() { # limine hook symlinks ln -sf /usr/bin/limine-reset-enroll "$pkgdir/etc/boot/hooks/pre.d/10-limine-reset-enroll" ln -sf /usr/bin/limine-enroll-config "$pkgdir/etc/boot/hooks/post.d/90-limine-enroll-config" + + if [[ $CARCH == aarch64 ]]; then + _apple_silicon_hooks + fi +} + +# An Apple Silicon Mac boots GRUB until Omarchy activates Limine, and keeps +# the kernel and initramfs that mkinitcpio's presets install under /boot +# after that too (GRUB, the boot check and the snapshot tools read them). +# The aarch64 hooks and mkinitcpio wrapper run through limine-apple-gate, +# which leaves every other machine as upstream ships it. On a Mac: +# - Limine's kernel and removal hooks wait until Limine is active; +# - mkinitcpio's own kernel hook, which Limine's takes over by name, runs from +# a copy of mkinitcpio 42's (mkinitcpio-install.hook: keep its triggers in +# step with mkinitcpio's), just before Limine's; +# - Limine's EFI deploy hook stays out: omarchy-mac-boot puts Limine in +# U-Boot's EFI/BOOT/BOOTAA64.EFI slot; +# - the wrapper is plain mkinitcpio: it would run limine-mkinitcpio after +# every preset build, mkinitcpio's kernel hook included. +_apple_silicon_hooks() { + local scripts=/usr/share/libalpm/scripts + local gate=$scripts/limine-apple-gate + local hooks="$pkgdir/usr/share/libalpm/hooks" + local kernel_hook="$pkgdir/etc/pacman.d/hooks/90-mkinitcpio-install.hook" + local mac_hook="$hooks/90-mkinitcpio-apple-install.hook" + local wrapper="$pkgdir/usr/local/bin/mkinitcpio" + + install -Dm755 "$srcdir/limine-apple-gate" "$pkgdir$gate" + install -Dm644 "$srcdir/mkinitcpio-install.hook" "$mac_hook" + sed -i -e 's|^Description = .*|Description = Updating linux initcpios under /boot (Apple Silicon)...|' \ + -e "s|^Exec = $scripts/mkinitcpio install\$|Exec = $gate --mac $scripts/mkinitcpio install|" "$mac_hook" + sed -i "s|^Exec = $scripts/limine-mkinitcpio-|Exec = $gate $scripts/limine-mkinitcpio-|" \ + "$kernel_hook" "$hooks/60-limine-mkinitcpio-remove-pre.hook" "$hooks/90-limine-mkinitcpio-remove-post.hook" + sed -i "s|^Exec = /usr/bin/limine-install\$|Exec = $gate --not-mac /usr/bin/limine-install|" "$hooks/80-limine-efi-deploy.hook" + sed -i "1a $gate --mac \\&\\& exec /usr/bin/mkinitcpio \"\$@\"" "$wrapper" + + local expected=( + "$mac_hook|Exec = $gate --mac $scripts/mkinitcpio install" + "$kernel_hook|Exec = $gate $scripts/limine-mkinitcpio-install" + "$hooks/60-limine-mkinitcpio-remove-pre.hook|Exec = $gate $scripts/limine-mkinitcpio-remove pre" + "$hooks/90-limine-mkinitcpio-remove-post.hook|Exec = $gate $scripts/limine-mkinitcpio-remove post" + "$hooks/80-limine-efi-deploy.hook|Exec = $gate --not-mac /usr/bin/limine-install" + ) + local entry + for entry in "${expected[@]}"; do + [[ $(grep '^Exec = ' "${entry%%|*}") == "${entry#*|}" ]] || { + echo "${entry%%|*}: expected '${entry#*|}'" >&2 + return 1 + } + done + [[ $(sed -n 1,2p "$wrapper") == $'#!/usr/bin/env bash\n'"$gate --mac && exec /usr/bin/mkinitcpio \"\$@\"" ]] || { + echo "the mkinitcpio wrapper does not start with the Apple Silicon gate" >&2 + return 1 + } } diff --git a/pkgbuilds/limine-mkinitcpio-hook/limine-apple-gate b/pkgbuilds/limine-mkinitcpio-hook/limine-apple-gate new file mode 100644 index 0000000..380e45e --- /dev/null +++ b/pkgbuilds/limine-mkinitcpio-hook/limine-apple-gate @@ -0,0 +1,69 @@ +#!/bin/bash +# The aarch64 package runs its pacman hooks and its mkinitcpio wrapper through +# this, so that only an Apple Silicon Mac behaves differently from upstream: +# +# limine-apple-gate COMMAND... COMMAND, except on a Mac that has not activated Limine +# limine-apple-gate --mac COMMAND... COMMAND on a Mac only +# limine-apple-gate --not-mac COMMAND... COMMAND everywhere but a Mac +# limine-apple-gate --mac succeeds on a Mac only +# +# A Mac boots GRUB until Omarchy activates Limine: the gate +# /var/lib/omarchy/limine.enabled and the /etc/default/limine the activation +# writes, the same test as omarchy-mac-limine-active. A command that does not +# run reads the targets pacman streams to it and succeeds quietly. + +gate=/var/lib/omarchy/limine.enabled +limine_default=/etc/default/limine +compatible=(/proc/device-tree/compatible /sys/firmware/devicetree/base/compatible) + +# Fixtures fake the machine for an unprivileged caller only; as root, which +# pacman hooks always are, the live system decides. +if ((EUID != 0)); then + gate=${OMARCHY_LIMINE_GATE:-$gate} + limine_default=${OMARCHY_LIMINE_DEFAULT:-$limine_default} + if [[ -n ${OMARCHY_PROC_ROOT:-}${OMARCHY_SYS_ROOT:-} ]]; then + compatible=() + [[ -z ${OMARCHY_PROC_ROOT:-} ]] || compatible+=("$OMARCHY_PROC_ROOT/device-tree/compatible") + [[ -z ${OMARCHY_SYS_ROOT:-} ]] || compatible+=("$OMARCHY_SYS_ROOT/firmware/devicetree/base/compatible") + fi +fi + +# m1n1 names every Apple Silicon Mac "apple," in the root node's +# NUL-separated compatible list, as omarchy-hw-platform reads it. +is_mac() { + local source + for source in "${compatible[@]}"; do + [[ -r $source ]] && tr '\0' '\n' <"$source" | grep -q '^apple,' && return 0 + done + return 1 +} + +# pacman writes NeedsTargets to a socket and fails the write if the hook has +# gone; hooks without NeedsTargets get no stdin at all. +pass() { + if [[ -S /dev/stdin || -p /dev/stdin ]]; then + cat >/dev/null + fi + exit 0 +} + +mode=limine +case ${1:-} in +--mac | --not-mac) + mode=${1#--} + shift + ;; +esac + +if (($# == 0)); then + [[ $mode == mac ]] && { is_mac; exit; } + echo "usage: limine-apple-gate [--mac|--not-mac] COMMAND [ARG]..." >&2 + exit 2 +fi + +case $mode in +mac) is_mac || pass ;; +not-mac) ! is_mac || pass ;; +limine) ! is_mac || [[ -e $gate && -f $limine_default ]] || pass ;; +esac +exec "$@" diff --git a/pkgbuilds/limine-mkinitcpio-hook/mkinitcpio-install.hook b/pkgbuilds/limine-mkinitcpio-hook/mkinitcpio-install.hook new file mode 100644 index 0000000..3607cd5 --- /dev/null +++ b/pkgbuilds/limine-mkinitcpio-hook/mkinitcpio-install.hook @@ -0,0 +1,38 @@ +[Trigger] +Type = Path +Operation = Install +Operation = Upgrade +Operation = Remove +Target = usr/lib/initcpio/* +Target = usr/lib/firmware/* +Target = usr/lib/modules/*/extramodules/ +Target = usr/src/*/dkms.conf +Target = usr/lib/systemd/systemd +Target = usr/lib/systemd/systemd-udevd +Target = usr/bin/cryptsetup +Target = usr/bin/lvm +Target = usr/bin/mdadm +Target = usr/bin/pdata_tools +Target = usr/lib/libcryptsetup.so +Target = usr/lib/libp11-kit.so +Target = usr/lib/libpcsclite.so +Target = usr/lib/modprobe.d/ + +[Trigger] +Type = Path +Operation = Install +Operation = Upgrade +Target = usr/lib/modules/*/vmlinuz + +[Trigger] +Type = Package +Operation = Install +Operation = Upgrade +Target = mkinitcpio +Target = mkinitcpio-git + +[Action] +Description = Updating linux initcpios... +When = PostTransaction +Exec = /usr/share/libalpm/scripts/mkinitcpio install +NeedsTargets 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/limine-snapper-sync/PKGBUILD b/pkgbuilds/limine-snapper-sync/PKGBUILD index 97d9add..51fd558 100644 --- a/pkgbuilds/limine-snapper-sync/PKGBUILD +++ b/pkgbuilds/limine-snapper-sync/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Zesko pkgname="limine-snapper-sync" -pkgver=1.31.0 -pkgrel=1.1 +pkgver=1.32.0 +pkgrel=1 _gradle_version=9.7.1 pkgdesc="Integrates Limine boot entries with Snapper snapshots." arch=('x86_64' 'aarch64') @@ -31,7 +31,7 @@ makedepends=('git') makedepends_x86_64=('gradle') backup=(etc/limine-snapper-sync.conf) conflicts=('limine-snapper-cli' 'limine-snapper-sync-git') -sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40') +sha256sums=('6bcc1d3ace58030204260a9a4d40d500c4822416be1b1c6edd153d1df0796b3d') sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0') sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71' 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a') diff --git a/pkgbuilds/linux-aurora/.omarchy/package.json b/pkgbuilds/linux-aurora/.omarchy/package.json new file mode 100644 index 0000000..04e6ea9 --- /dev/null +++ b/pkgbuilds/linux-aurora/.omarchy/package.json @@ -0,0 +1,5 @@ +{ + "source": "local", + "skip_build": true, + "channels": ["edge"] +} diff --git a/pkgbuilds/linux-aurora/PKGBUILD b/pkgbuilds/linux-aurora/PKGBUILD new file mode 100644 index 0000000..372885c --- /dev/null +++ b/pkgbuilds/linux-aurora/PKGBUILD @@ -0,0 +1,226 @@ +# AArch64 Apple Silicon (Aurora Silicon) +# Based on the asahi-alarm linux-asahi package by Jos Dehaes and Matthias Kurz. +# +# Aurora Silicon maintain a fork of the Asahi kernel carrying DisplayPort +# alt-mode and USB4 support for external monitors, variable refresh rate, and +# the ISP and AOP drivers. Those changes touch device trees, the type-C stack +# and DRM, so they cannot ship as a module: this is a whole alternative kernel +# for Macs on the Omarchy edge channel. It reaches rc and stable only after +# cold-boot qualification on M1 and M2. + +_auroraver=7.1.12 +_aurorarel=2 +pkgrel=10 + +# Immutable pin on aurora-silicon/linux (branch aurora-wip). Every bump is a +# PR that moves _commit and the archive checksum; the pinned commit is cold-boot +# qualified on MacBook Pro 14" M1 Pro (j314s) and 16" M2 Max (j416c) first. +_commit=2439016d2e8d3a0d8aebcd243f29604a91f201c9 + +_m1n1_version=1.6.1 + +pkgbase=linux-aurora +_srcname=linux-${_commit} +_kernelname=${pkgbase#linux} +_desc="AArch64 Apple Silicon (Aurora kernel)" +pkgver="${_auroraver}.aurora${_aurorarel}" +arch=('aarch64') +url="https://github.com/aurora-silicon/linux" +license=('GPL2') + +makedepends=( + base-devel bc dtc kmod libelf pahole cpio perl rustup rust-bindgen tar xz xmlto python +) +options=('!strip') +source=( + https://github.com/aurora-silicon/linux/archive/${_commit}.tar.gz + config # the main kernel config file + rust-toolchain.toml +) +sha256sums=('6347354537148120fc5448afcb8671a558ce336f731c70690cd88062a903f84d' + '4228006ad9c5fcc2237b91b3fd1f70934d23e6d8bdb012faf5a763e042a6edee' + '2a95563bb9f5b216cc3c170a17e9471448bc63a7084bdd67df23d074939b366c') + +# Kbuild takes ARCH literally and knows arm64, not aarch64. +export ARCH=arm64 +export KBUILD_BUILD_HOST=archlinux +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 "-$_aurorarel-$pkgrel" > localversion.10-pkgrel + + local src + for src in "${source[@]}"; do + src="${src%%::*}" + src="${src##*/}" + [[ $src = *.patch ]] || continue + echo "Applying patch $src..." + patch -Np1 < "../$src" + done + + echo "Pinning the Rust toolchain..." + cp ../rust-toolchain.toml . + + echo "Creating build directories..." + mkdir -p build/base + + echo "Setting config (base)..." + cp ../config build/base/.config + make olddefconfig prepare O=$PWD/build/base + diff -u ../config build/base/.config || : + make -s kernelrelease O=$PWD/build/base > build/base/version + + echo "Prepared $pkgbase version $(=$_m1n1_version") + optdepends=('crda: to set the correct wireless channels of your country' + 'linux-firmware: firmware images needed for some devices') + # No generic linux provide: on a shared aarch64 repository it would compete + # with linux-aarch64 for any dependency on linux. + provides=(WIREGUARD-MODULE linux-asahi=${pkgver}) + conflicts=(linux-asahi) + + local kernver="$(<$O/version)" + local modulesdir="$pkgdir/usr/lib/modules/$kernver" + + echo "Installing boot image..." + # systemd expects to find the kernel here to allow hibernation + # https://github.com/systemd/systemd/commit/edda44605f06a41fb86b7ab8128dcf99161d2344 + install -Dm644 "$O"/arch/arm64/boot/Image "$modulesdir/vmlinuz" + + # Used by mkinitcpio to name the kernel + echo "$1" | install -Dm644 /dev/stdin "$modulesdir/pkgbase" + + echo "Installing modules..." + make O="$O" INSTALL_MOD_PATH="$pkgdir/usr" INSTALL_MOD_STRIP=1 modules_install + + echo "Installing device trees..." + install -Dt "$modulesdir/dtbs" "$O"/arch/arm64/boot/dts/apple/*.dtb + + # remove build and source links + rm -f "$modulesdir"/{source,build} +} + +_package() { + cd $_srcname + export O="$PWD/build/base" + _package_kernel "$pkgbase" +} + +_package-headers() { + pkgdesc="Headers and scripts for building modules for the $pkgdesc kernel" + depends=(pahole) + provides=(linux-asahi-headers=${pkgver}) + conflicts=(linux-asahi-headers) + + KARCH=arm64 + + cd $_srcname + O=build/base + local builddir="$pkgdir/usr/lib/modules/$(<${O}/version)/build" + + echo "Installing build files..." + install -Dt "$builddir" -m644 $O/.config Makefile $O/Module.symvers $O/System.map \ + localversion.* $O/version $O/vmlinux + install -Dt "$builddir/kernel" -m644 kernel/Makefile + cp -t "$builddir" -a scripts $O/scripts + + # required when STACK_VALIDATION is enabled + #install -Dt "$builddir/tools/objtool" tools/objtool/objtool + + # required when DEBUG_INFO_BTF_MODULES is enabled + install -Dt "$builddir/tools/bpf/resolve_btfids" $O/tools/bpf/resolve_btfids/resolve_btfids + + echo "Installing headers..." + cp -t "$builddir" -a include $O/include + + install -Dt "${builddir}/arch/${KARCH}" -m644 arch/${KARCH}/Makefile + install -Dt "${builddir}/arch/${KARCH}/kernel" -m644 $O/arch/${KARCH}/kernel/asm-offsets.s + cp -t "${builddir}/arch/${KARCH}" -a arch/${KARCH}/include $O/arch/${KARCH}/include + + install -Dt "$builddir/drivers/md" -m644 drivers/md/*.h + install -Dt "$builddir/net/mac80211" -m644 net/mac80211/*.h + + # https://bugs.archlinux.org/task/13146 + install -Dt "$builddir/drivers/media/i2c" -m644 drivers/media/i2c/msp3400-driver.h + + # https://bugs.archlinux.org/task/20402 + install -Dt "$builddir/drivers/media/usb/dvb-usb" -m644 drivers/media/usb/dvb-usb/*.h + install -Dt "$builddir/drivers/media/dvb-frontends" -m644 drivers/media/dvb-frontends/*.h + install -Dt "$builddir/drivers/media/tuners" -m644 drivers/media/tuners/*.h + + # https://bugs.archlinux.org/task/71392 + install -Dt "$builddir/drivers/iio/common/hid-sensors" -m644 drivers/iio/common/hid-sensors/*.h + + echo "Installing KConfig files..." + find . -name 'Kconfig*' -exec install -Dm644 {} "$builddir/{}" \; + + echo "Installing unstripped VDSO..." + make O="$O" INSTALL_MOD_PATH="$pkgdir/usr" vdso_install link= + + echo "Removing unneeded architectures..." + local arch + for arch in "$builddir"/arch/*/; do + [[ $arch = */${KARCH}/ ]] && continue + echo "Removing $(basename "$arch")" + rm -r "$arch" + done + + echo "Removing documentation..." + rm -r "$builddir/Documentation" + + echo "Removing broken symlinks..." + find -L "$builddir" -type l -printf 'Removing %P\n' -delete + + echo "Removing loose objects..." + find "$builddir" -type f -name '*.o' -printf 'Removing %P\n' -delete + + echo "Removing cmd files..." + find "$builddir" -type f -name '*.cmd' -printf 'Removing %P\n' -delete + + echo "Stripping build tools..." + local file + while read -rd '' file; do + case "$(file --no-sandbox -bi "$file")" in + application/x-sharedlib\;*) # Libraries (.so) + strip -v $STRIP_SHARED "$file" ;; + application/x-archive\;*) # Libraries (.a) + strip -v $STRIP_STATIC "$file" ;; + application/x-executable\;*) # Binaries + strip -v $STRIP_BINARIES "$file" ;; + application/x-pie-executable\;*) # Relocatable binaries + strip -v $STRIP_SHARED "$file" ;; + esac + done < <(find "$builddir" -type f -perm -u+x ! -name vmlinux -print0) + + echo "Stripping vmlinux..." + strip -v $STRIP_STATIC "$builddir/vmlinux" + + echo "Adding symlink..." + mkdir -p "$pkgdir/usr/src" + ln -sr "$builddir" "$pkgdir/usr/src/$pkgbase" + +} + +pkgname=("$pkgbase" "$pkgbase-headers") +for _p in "${pkgname[@]}"; do + eval "package_$_p() { + $(declare -f "_package${_p#$pkgbase}") + _package${_p#$pkgbase} + }" +done + +# vim:set ts=8 sts=2 sw=2 et: diff --git a/pkgbuilds/linux-aurora/config b/pkgbuilds/linux-aurora/config new file mode 100644 index 0000000..04e5fba --- /dev/null +++ b/pkgbuilds/linux-aurora/config @@ -0,0 +1,9280 @@ +# +# Automatically generated file; DO NOT EDIT. +# Linux/arm64 7.1.6 Kernel Configuration +# +CONFIG_CC_VERSION_TEXT="gcc (GCC) 15.2.1 20260209" +CONFIG_CC_IS_GCC=y +CONFIG_GCC_VERSION=150201 +CONFIG_CLANG_VERSION=0 +CONFIG_AS_IS_GNU=y +CONFIG_AS_VERSION=24600 +CONFIG_LD_IS_BFD=y +CONFIG_LD_VERSION=24600 +CONFIG_LLD_VERSION=0 +CONFIG_RUSTC_VERSION=109301 +CONFIG_RUST_IS_AVAILABLE=y +CONFIG_RUSTC_LLVM_VERSION=210108 +CONFIG_RUSTC_LLVM_MAJOR_VERSION=21 +CONFIG_CC_CAN_LINK=y +CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y +CONFIG_CC_HAS_ASM_GOTO_TIED_OUTPUT=y +CONFIG_TOOLS_SUPPORT_RELR=y +CONFIG_CC_HAS_ASM_INLINE=y +CONFIG_CC_HAS_ASSUME=y +CONFIG_CC_HAS_NO_PROFILE_FN_ATTR=y +CONFIG_CC_HAS_COUNTED_BY=y +CONFIG_CC_HAS_MULTIDIMENSIONAL_NONSTRING=y +CONFIG_LD_CAN_USE_KEEP_IN_OVERLAY=y +CONFIG_RUSTC_HAS_SPAN_FILE=y +CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES=y +CONFIG_RUSTC_HAS_FILE_WITH_NUL=y +CONFIG_RUSTC_HAS_FILE_AS_C_STR=y +CONFIG_PAHOLE_VERSION=131 +CONFIG_IRQ_WORK=y +CONFIG_BUILDTIME_TABLE_SORT=y +CONFIG_THREAD_INFO_IN_TASK=y + +# +# General setup +# +CONFIG_INIT_ENV_ARG_LIMIT=32 +# CONFIG_COMPILE_TEST is not set +# CONFIG_WERROR is not set +CONFIG_LOCALVERSION="-ARCH" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_BUILD_SALT="" +CONFIG_DEFAULT_INIT="" +CONFIG_DEFAULT_HOSTNAME="(none)" +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +CONFIG_POSIX_MQUEUE=y +CONFIG_POSIX_MQUEUE_SYSCTL=y +CONFIG_WATCH_QUEUE=y +CONFIG_CROSS_MEMORY_ATTACH=y +CONFIG_AUDIT=y +CONFIG_HAVE_ARCH_AUDITSYSCALL=y +CONFIG_AUDITSYSCALL=y + +# +# IRQ subsystem +# +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_GENERIC_IRQ_SHOW=y +CONFIG_GENERIC_IRQ_SHOW_LEVEL=y +CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y +CONFIG_GENERIC_IRQ_MIGRATION=y +CONFIG_GENERIC_IRQ_INJECTION=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_IRQ_DOMAIN=y +CONFIG_IRQ_DOMAIN_HIERARCHY=y +CONFIG_GENERIC_IRQ_IPI=y +CONFIG_GENERIC_IRQ_IPI_MUX=y +CONFIG_GENERIC_MSI_IRQ=y +CONFIG_IRQ_MSI_IOMMU=y +CONFIG_GENERIC_IRQ_STAT_SNAPSHOT=y +CONFIG_IRQ_FORCED_THREADING=y +CONFIG_SPARSE_IRQ=y +# CONFIG_GENERIC_IRQ_DEBUGFS is not set +CONFIG_GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD=y +# end of IRQ subsystem + +CONFIG_GENERIC_TIME_VSYSCALL=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_ARCH_HAS_TICK_BROADCAST=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y +CONFIG_HAVE_POSIX_CPU_TIMERS_TASK_WORK=y +CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y +CONFIG_CONTEXT_TRACKING=y +CONFIG_CONTEXT_TRACKING_IDLE=y + +# +# Timers subsystem +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ_COMMON=y +# CONFIG_HZ_PERIODIC is not set +# CONFIG_NO_HZ_IDLE is not set +CONFIG_NO_HZ_FULL=y +CONFIG_CONTEXT_TRACKING_USER=y +# CONFIG_CONTEXT_TRACKING_USER_FORCE is not set +# CONFIG_NO_HZ is not set +CONFIG_HIGH_RES_TIMERS=y +# CONFIG_POSIX_AUX_CLOCKS is not set +# end of Timers subsystem + +CONFIG_BPF=y +CONFIG_HAVE_EBPF_JIT=y +CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y + +# +# BPF subsystem +# +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +# CONFIG_BPF_JIT_ALWAYS_ON is not set +CONFIG_BPF_JIT_DEFAULT_ON=y +# CONFIG_BPF_UNPRIV_DEFAULT_OFF is not set +# CONFIG_BPF_PRELOAD is not set +# CONFIG_BPF_LSM is not set +# end of BPF subsystem + +CONFIG_PREEMPT_BUILD=y +CONFIG_ARCH_HAS_PREEMPT_LAZY=y +CONFIG_PREEMPT=y +# CONFIG_PREEMPT_LAZY is not set +# CONFIG_PREEMPT_RT is not set +CONFIG_PREEMPT_COUNT=y +CONFIG_PREEMPTION=y +CONFIG_PREEMPT_DYNAMIC=y +# CONFIG_SCHED_CORE is not set +# CONFIG_SCHED_CLASS_EXT is not set + +# +# CPU/Task time and stats accounting +# +CONFIG_VIRT_CPU_ACCOUNTING=y +CONFIG_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_HAVE_SCHED_AVG_IRQ=y +CONFIG_SCHED_HW_PRESSURE=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +# CONFIG_PSI_DEFAULT_DISABLED is not set +# end of CPU/Task time and stats accounting + +CONFIG_CPU_ISOLATION=y + +# +# RCU Subsystem +# +CONFIG_TREE_RCU=y +CONFIG_PREEMPT_RCU=y +# CONFIG_RCU_EXPERT is not set +CONFIG_TREE_SRCU=y +CONFIG_TASKS_RCU_GENERIC=y +CONFIG_NEED_TASKS_RCU=y +CONFIG_TASKS_RCU=y +CONFIG_TASKS_TRACE_RCU=y +CONFIG_RCU_STALL_COMMON=y +CONFIG_RCU_NEED_SEGCBLIST=y +CONFIG_RCU_NOCB_CPU=y +# CONFIG_RCU_NOCB_CPU_DEFAULT_ALL is not set +# CONFIG_RCU_LAZY is not set +# end of RCU Subsystem + +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +# CONFIG_IKHEADERS is not set +CONFIG_LOG_BUF_SHIFT=18 +CONFIG_LOG_CPU_MAX_BUF_SHIFT=12 +# CONFIG_PRINTK_INDEX is not set +CONFIG_GENERIC_SCHED_CLOCK=y + +# +# Scheduler features +# +CONFIG_UCLAMP_TASK=y +CONFIG_UCLAMP_BUCKETS_COUNT=5 +# CONFIG_SCHED_PROXY_EXEC is not set +# end of Scheduler features + +CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y +CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y +CONFIG_CC_HAS_INT128=y +CONFIG_CC_IMPLICIT_FALLTHROUGH="-Wimplicit-fallthrough=5" +CONFIG_CC_MS_EXTENSIONS="-fms-extensions" +CONFIG_GCC10_NO_ARRAY_BOUNDS=y +CONFIG_CC_NO_ARRAY_BOUNDS=y +CONFIG_GCC_NO_STRINGOP_OVERFLOW=y +CONFIG_CC_NO_STRINGOP_OVERFLOW=y +CONFIG_ARCH_SUPPORTS_INT128=y +CONFIG_SLAB_OBJ_EXT=y +CONFIG_CGROUPS=y +CONFIG_PAGE_COUNTER=y +# CONFIG_CGROUP_FAVOR_DYNMODS is not set +CONFIG_MEMCG=y +# CONFIG_MEMCG_V1 is not set +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_WRITEBACK=y +CONFIG_CGROUP_SCHED=y +CONFIG_GROUP_SCHED_WEIGHT=y +CONFIG_GROUP_SCHED_BANDWIDTH=y +CONFIG_FAIR_GROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +# CONFIG_RT_GROUP_SCHED is not set +CONFIG_SCHED_MM_CID=y +CONFIG_UCLAMP_TASK_GROUP=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +# CONFIG_CGROUP_DMEM is not set +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_HUGETLB=y +CONFIG_CPUSETS=y +# CONFIG_CPUSETS_V1 is not set +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +# CONFIG_CGROUP_DEBUG is not set +CONFIG_SOCK_CGROUP_DATA=y +CONFIG_NAMESPACES=y +CONFIG_UTS_NS=y +CONFIG_TIME_NS=y +CONFIG_TIME_NS_VDSO=y +CONFIG_IPC_NS=y +CONFIG_USER_NS=y +CONFIG_PID_NS=y +CONFIG_NET_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_RD_GZIP=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_RD_XZ=y +CONFIG_RD_LZO=y +CONFIG_RD_LZ4=y +CONFIG_RD_ZSTD=y +CONFIG_BOOT_CONFIG=y +# CONFIG_BOOT_CONFIG_FORCE is not set +# CONFIG_BOOT_CONFIG_EMBED is not set +CONFIG_CMDLINE_LOG_WRAP_IDEAL_LEN=1021 +CONFIG_INITRAMFS_PRESERVE_MTIME=y +CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y +# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set +CONFIG_LD_ORPHAN_WARN=y +CONFIG_LD_ORPHAN_WARN_LEVEL="warn" +CONFIG_SYSCTL=y +CONFIG_SYSCTL_EXCEPTION_TRACE=y +CONFIG_SYSFS_SYSCALL=y +CONFIG_EXPERT=y +CONFIG_MULTIUSER=y +# CONFIG_SGETMASK_SYSCALL is not set +CONFIG_FHANDLE=y +CONFIG_POSIX_TIMERS=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +# CONFIG_BASE_SMALL is not set +CONFIG_FUTEX=y +CONFIG_FUTEX_PI=y +CONFIG_FUTEX_PRIVATE_HASH=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_IO_URING=y +# CONFIG_IO_URING_MOCK_FILE is not set +CONFIG_ADVISE_SYSCALLS=y +CONFIG_MEMBARRIER=y +CONFIG_KCMP=y +CONFIG_RSEQ=y +# CONFIG_RSEQ_STATS is not set +# CONFIG_RSEQ_DEBUG_DEFAULT_ENABLE is not set +# CONFIG_DEBUG_RSEQ is not set +CONFIG_CACHESTAT_SYSCALL=y +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_SELFTEST is not set +CONFIG_KALLSYMS_ALL=y +CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y +CONFIG_ARCH_SUPPORTS_MSEAL_SYSTEM_MAPPINGS=y +CONFIG_HAVE_PERF_EVENTS=y +CONFIG_GUEST_PERF_EVENTS=y + +# +# Kernel Performance Events And Counters +# +CONFIG_PERF_EVENTS=y +# CONFIG_DEBUG_PERF_USE_VMALLOC is not set +# end of Kernel Performance Events And Counters + +CONFIG_SYSTEM_DATA_VERIFICATION=y +CONFIG_PROFILING=y +CONFIG_RUST=y +CONFIG_RUSTC_VERSION_TEXT="rustc 1.93.1 (01f6ddf75 2026-02-11)" +CONFIG_BINDGEN_VERSION_TEXT="bindgen 0.72.1" +CONFIG_TRACEPOINTS=y + +# +# Kexec and crash features +# +CONFIG_VMCORE_INFO=y +CONFIG_KEXEC_CORE=y +# CONFIG_KEXEC is not set +CONFIG_KEXEC_FILE=y +# CONFIG_KEXEC_SIG is not set +# CONFIG_CRASH_DUMP is not set +# end of Kexec and crash features + +# +# Live Update and Kexec HandOver +# +# CONFIG_KEXEC_HANDOVER is not set +# end of Live Update and Kexec HandOver +# end of General setup + +CONFIG_ARM64=y +CONFIG_RUSTC_SUPPORTS_ARM64=y +CONFIG_GCC_SUPPORTS_DYNAMIC_FTRACE_WITH_ARGS=y +CONFIG_64BIT=y +CONFIG_MMU=y +CONFIG_ARM64_CONT_PTE_SHIFT=7 +CONFIG_ARM64_CONT_PMD_SHIFT=5 +CONFIG_ARCH_MMAP_RND_BITS_MIN=16 +CONFIG_ARCH_MMAP_RND_BITS_MAX=31 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=9 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=16 +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CSUM=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_SMP=y +CONFIG_KERNEL_MODE_NEON=y +CONFIG_FIX_EARLYCON_MEM=y +CONFIG_PGTABLE_LEVELS=4 +CONFIG_ARCH_SUPPORTS_UPROBES=y +CONFIG_ARCH_PROC_KCORE_TEXT=y +CONFIG_BUILTIN_RETURN_ADDRESS_STRIPS_PAC=y +CONFIG_ARM64_ACTLR_STATE=y + +# +# Platform selection +# +# CONFIG_ARCH_ACTIONS is not set +# CONFIG_ARCH_AIROHA is not set +# CONFIG_ARCH_SUNXI is not set +# CONFIG_ARCH_ALPINE is not set +CONFIG_ARCH_APPLE=y +# CONFIG_ARCH_AXIADO is not set +# CONFIG_ARCH_BCM is not set +# CONFIG_ARCH_BERLIN is not set +# CONFIG_ARCH_BITMAIN is not set +# CONFIG_ARCH_BLAIZE is not set +# CONFIG_ARCH_BST is not set +# CONFIG_ARCH_CIX is not set +# CONFIG_ARCH_EXYNOS is not set +# CONFIG_ARCH_K3 is not set +# CONFIG_ARCH_LG1K is not set +# CONFIG_ARCH_HISI is not set +# CONFIG_ARCH_KEEMBAY is not set +# CONFIG_ARCH_MEDIATEK is not set +# CONFIG_ARCH_MESON is not set +# CONFIG_ARCH_MICROCHIP is not set +# CONFIG_ARCH_MMP is not set +# CONFIG_ARCH_MVEBU is not set +# CONFIG_ARCH_NXP is not set +# CONFIG_ARCH_MA35 is not set +# CONFIG_ARCH_NPCM is not set +# CONFIG_ARCH_PENSANDO is not set +# CONFIG_ARCH_QCOM is not set +# CONFIG_ARCH_REALTEK is not set +# CONFIG_ARCH_RENESAS is not set +# CONFIG_ARCH_ROCKCHIP is not set +# CONFIG_ARCH_SEATTLE is not set +# CONFIG_ARCH_INTEL_SOCFPGA is not set +# CONFIG_ARCH_SOPHGO is not set +# CONFIG_ARCH_STM32 is not set +# CONFIG_ARCH_SYNQUACER is not set +# CONFIG_ARCH_TEGRA is not set +# CONFIG_ARCH_SPRD is not set +# CONFIG_ARCH_THUNDER is not set +# CONFIG_ARCH_THUNDER2 is not set +# CONFIG_ARCH_UNIPHIER is not set +# CONFIG_ARCH_VEXPRESS is not set +# CONFIG_ARCH_VISCONTI is not set +# CONFIG_ARCH_XGENE is not set +# CONFIG_ARCH_ZYNQMP is not set +# end of Platform selection + +# +# Kernel Features +# + +# +# ARM errata workarounds via the alternatives framework +# +# CONFIG_AMPERE_ERRATUM_AC03_CPU_38 is not set +CONFIG_AMPERE_ERRATUM_AC04_CPU_23=y +# CONFIG_ARM64_ERRATUM_826319 is not set +# CONFIG_ARM64_ERRATUM_827319 is not set +# CONFIG_ARM64_ERRATUM_824069 is not set +# CONFIG_ARM64_ERRATUM_819472 is not set +# CONFIG_ARM64_ERRATUM_832075 is not set +# CONFIG_ARM64_ERRATUM_834220 is not set +# CONFIG_ARM64_ERRATUM_843419 is not set +# CONFIG_ARM64_ERRATUM_1024718 is not set +# CONFIG_ARM64_ERRATUM_1165522 is not set +# CONFIG_ARM64_ERRATUM_1319367 is not set +# CONFIG_ARM64_ERRATUM_1530923 is not set +CONFIG_ARM64_WORKAROUND_REPEAT_TLBI=y +# CONFIG_ARM64_ERRATUM_2441007 is not set +# CONFIG_ARM64_ERRATUM_1286807 is not set +# CONFIG_ARM64_ERRATUM_1463225 is not set +# CONFIG_ARM64_ERRATUM_1542419 is not set +# CONFIG_ARM64_ERRATUM_1508412 is not set +# CONFIG_ARM64_ERRATUM_2051678 is not set +# CONFIG_ARM64_ERRATUM_2077057 is not set +# CONFIG_ARM64_ERRATUM_2658417 is not set +# CONFIG_ARM64_ERRATUM_2054223 is not set +# CONFIG_ARM64_ERRATUM_2067961 is not set +# CONFIG_ARM64_ERRATUM_2441009 is not set +# CONFIG_ARM64_ERRATUM_2457168 is not set +# CONFIG_ARM64_ERRATUM_2645198 is not set +CONFIG_ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD=y +CONFIG_ARM64_ERRATUM_2966298=y +CONFIG_ARM64_ERRATUM_3117295=y +CONFIG_ARM64_ERRATUM_3194386=y +CONFIG_ARM64_ERRATUM_4311569=y +CONFIG_ARM64_ERRATUM_4193714=y +CONFIG_ARM64_ERRATUM_4118414=y +# CONFIG_CAVIUM_ERRATUM_22375 is not set +# CONFIG_CAVIUM_ERRATUM_23154 is not set +# CONFIG_CAVIUM_ERRATUM_27456 is not set +# CONFIG_CAVIUM_ERRATUM_30115 is not set +# CONFIG_CAVIUM_TX2_ERRATUM_219 is not set +# CONFIG_FUJITSU_ERRATUM_010001 is not set +# CONFIG_HISILICON_ERRATUM_161600802 is not set +CONFIG_HISILICON_ERRATUM_162100801=y +# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set +# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set +# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set +CONFIG_ROCKCHIP_ERRATUM_3568002=y +# CONFIG_ROCKCHIP_ERRATUM_3588001 is not set +# CONFIG_SOCIONEXT_SYNQUACER_PREITS is not set +# end of ARM errata workarounds via the alternatives framework + +# CONFIG_ARM64_4K_PAGES is not set +CONFIG_ARM64_16K_PAGES=y +# CONFIG_ARM64_64K_PAGES is not set +# CONFIG_ARM64_VA_BITS_36 is not set +# CONFIG_ARM64_VA_BITS_47 is not set +CONFIG_ARM64_VA_BITS_48=y +# CONFIG_ARM64_VA_BITS_52 is not set +CONFIG_ARM64_VA_BITS=48 +CONFIG_ARM64_PA_BITS_48=y +CONFIG_ARM64_PA_BITS=48 +CONFIG_CPU_LITTLE_ENDIAN=y +CONFIG_NR_CPUS=64 +CONFIG_HOTPLUG_CPU=y +# CONFIG_NUMA is not set +# CONFIG_HZ_100 is not set +# CONFIG_HZ_250 is not set +# CONFIG_HZ_300 is not set +CONFIG_HZ_1000=y +CONFIG_HZ=1000 +CONFIG_SCHED_HRTICK=y +CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_HW_PERF_EVENTS=y +CONFIG_CC_HAVE_SHADOW_CALL_STACK=y +CONFIG_PARAVIRT=y +CONFIG_PARAVIRT_TIME_ACCOUNTING=y +CONFIG_ARCH_SUPPORTS_KEXEC=y +CONFIG_ARCH_SUPPORTS_KEXEC_FILE=y +CONFIG_ARCH_SELECTS_KEXEC_FILE=y +CONFIG_ARCH_SUPPORTS_KEXEC_SIG=y +CONFIG_ARCH_SUPPORTS_KEXEC_IMAGE_VERIFY_SIG=y +CONFIG_ARCH_DEFAULT_KEXEC_IMAGE_VERIFY_SIG=y +CONFIG_ARCH_SUPPORTS_KEXEC_HANDOVER=y +CONFIG_ARCH_SUPPORTS_CRASH_DUMP=y +CONFIG_ARCH_DEFAULT_CRASH_DUMP=y +CONFIG_TRANS_TABLE=y +# CONFIG_XEN is not set +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_UNMAP_KERNEL_AT_EL0=y +CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY=y +# CONFIG_ARM64_SW_TTBR0_PAN is not set +CONFIG_ARM64_TAGGED_ADDR_ABI=y +# CONFIG_COMPAT is not set + +# +# ARMv8.1 architectural features +# +CONFIG_ARM64_HW_AFDBM=y +# end of ARMv8.1 architectural features + +# +# ARMv8.2 architectural features +# +# CONFIG_ARM64_PMEM is not set +CONFIG_ARM64_RAS_EXTN=y +CONFIG_ARM64_CNP=y +# end of ARMv8.2 architectural features + +# +# ARMv8.3 architectural features +# +CONFIG_ARM64_PTR_AUTH=y +CONFIG_ARM64_PTR_AUTH_KERNEL=y +CONFIG_CC_HAS_BRANCH_PROT_PAC_RET=y +CONFIG_AS_HAS_CFI_NEGATE_RA_STATE=y +# end of ARMv8.3 architectural features + +# +# ARMv8.4 architectural features +# +CONFIG_ARM64_AMU_EXTN=y +CONFIG_ARM64_TLB_RANGE=y +# CONFIG_ARM64_MPAM is not set +# end of ARMv8.4 architectural features + +# +# ARMv8.5 architectural features +# +CONFIG_AS_HAS_ARMV8_5=y +CONFIG_ARM64_BTI=y +CONFIG_CC_HAS_BRANCH_PROT_PAC_RET_BTI=y +CONFIG_ARM64_E0PD=y +CONFIG_ARM64_AS_HAS_MTE=y +CONFIG_ARM64_MTE=y +# end of ARMv8.5 architectural features + +# +# ARMv8.7 architectural features +# +CONFIG_ARM64_EPAN=y +# end of ARMv8.7 architectural features + +CONFIG_AS_HAS_MOPS=y + +# +# ARMv8.9 architectural features +# +CONFIG_ARM64_POE=y +CONFIG_ARCH_PKEY_BITS=3 +CONFIG_ARM64_HAFT=y +# end of ARMv8.9 architectural features + +# +# ARMv9.4 architectural features +# +CONFIG_ARM64_GCS=y +# end of ARMv9.4 architectural features + +CONFIG_AS_HAS_LSUI=y + +# +# ARMv9.6 architectural features +# +CONFIG_ARM64_LSUI=y +# end of ARMv9.6 architectural features + +CONFIG_ARM64_SVE=y +CONFIG_ARM64_SME=y +# CONFIG_ARM64_PSEUDO_NMI is not set +CONFIG_ARM64_MEMORY_MODEL_CONTROL=y +CONFIG_RELOCATABLE=y +# CONFIG_RANDOMIZE_BASE is not set +CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y +CONFIG_STACKPROTECTOR_PER_TASK=y +# end of Kernel Features + +# +# Boot options +# +CONFIG_CMDLINE="" +CONFIG_EFI_STUB=y +CONFIG_EFI=y +# CONFIG_COMPRESSED_INSTALL is not set +CONFIG_DMI=y +# end of Boot options + +# +# Power management options +# +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_SUSPEND_SKIP_SYNC is not set +# CONFIG_HIBERNATION is not set +CONFIG_PM_SLEEP=y +CONFIG_PM_SLEEP_SMP=y +# CONFIG_PM_AUTOSLEEP is not set +# CONFIG_PM_USERSPACE_AUTOSLEEP is not set +# CONFIG_PM_WAKELOCKS is not set +CONFIG_PM_QOS_CPU_SYSTEM_WAKEUP=y +CONFIG_PM=y +CONFIG_PM_DEBUG=y +# CONFIG_PM_ADVANCED_DEBUG is not set +# CONFIG_PM_TEST_SUSPEND is not set +CONFIG_PM_SLEEP_DEBUG=y +# CONFIG_DPM_WATCHDOG is not set +CONFIG_PM_CLK=y +CONFIG_PM_GENERIC_DOMAINS=y +# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set +CONFIG_PM_GENERIC_DOMAINS_SLEEP=y +CONFIG_PM_GENERIC_DOMAINS_OF=y +CONFIG_CPU_PM=y +CONFIG_ENERGY_MODEL=y +CONFIG_ARCH_HIBERNATION_POSSIBLE=y +CONFIG_ARCH_SUSPEND_POSSIBLE=y +# end of Power management options + +# +# CPU Power Management +# + +# +# CPU Idle +# +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_MULTIPLE_DRIVERS=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_CPU_IDLE_GOV_MENU=y +CONFIG_CPU_IDLE_GOV_TEO=y +CONFIG_DT_IDLE_STATES=y +CONFIG_DT_IDLE_GENPD=y + +# +# ARM CPU Idle Drivers +# +CONFIG_ARM_PSCI_CPUIDLE=y +CONFIG_ARM_PSCI_CPUIDLE_DOMAIN=y +CONFIG_ARM_APPLE_CPUIDLE=y +# end of ARM CPU Idle Drivers +# end of CPU Idle + +# +# CPU Frequency scaling +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_ATTR_SET=y +CONFIG_CPU_FREQ_GOV_COMMON=y +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y + +# +# CPU frequency scaling drivers +# +CONFIG_CPUFREQ_DT=y +# CONFIG_CPUFREQ_DT_RUST is not set +# CONFIG_CPUFREQ_VIRT is not set +CONFIG_CPUFREQ_DT_PLATDEV=y +CONFIG_ARM_APPLE_SOC_CPUFREQ=y +# end of CPU Frequency scaling +# end of CPU Power Management + +CONFIG_ARCH_SUPPORTS_ACPI=y +# CONFIG_ACPI is not set +CONFIG_KVM_COMMON=y +CONFIG_HAVE_KVM_IRQCHIP=y +CONFIG_HAVE_KVM_IRQ_ROUTING=y +CONFIG_HAVE_KVM_DIRTY_RING=y +CONFIG_HAVE_KVM_DIRTY_RING_ACQ_REL=y +CONFIG_NEED_KVM_DIRTY_RING_WITH_BITMAP=y +CONFIG_KVM_MMIO=y +CONFIG_HAVE_KVM_MSI=y +CONFIG_HAVE_KVM_READONLY_MEM=y +CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT=y +CONFIG_KVM_VFIO=y +CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT=y +CONFIG_HAVE_KVM_IRQ_BYPASS=y +CONFIG_HAVE_KVM_VCPU_RUN_PID_CHANGE=y +CONFIG_VIRT_XFER_TO_GUEST_WORK=y +CONFIG_KVM_GENERIC_HARDWARE_ENABLING=y +CONFIG_KVM_GUEST_MEMFD=y +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +# CONFIG_PTDUMP_STAGE2_DEBUGFS is not set +# CONFIG_NVHE_EL2_DEBUG is not set +CONFIG_HAVE_LIVEPATCH=y +CONFIG_CPU_MITIGATIONS=y + +# +# General architecture-dependent options +# +CONFIG_ARCH_HAS_SUBPAGE_FAULTS=y +CONFIG_HOTPLUG_SMT=y +CONFIG_ARCH_SUPPORTS_SCHED_SMT=y +CONFIG_ARCH_SUPPORTS_SCHED_CLUSTER=y +CONFIG_ARCH_SUPPORTS_SCHED_MC=y +CONFIG_SCHED_SMT=y +CONFIG_SCHED_CLUSTER=y +CONFIG_SCHED_MC=y +CONFIG_HOTPLUG_CORE_SYNC=y +CONFIG_HOTPLUG_CORE_SYNC_DEAD=y +CONFIG_GENERIC_IRQ_ENTRY=y +CONFIG_KPROBES=y +CONFIG_JUMP_LABEL=y +# CONFIG_STATIC_KEYS_SELFTEST is not set +CONFIG_UPROBES=y +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_KRETPROBES=y +CONFIG_HAVE_IOREMAP_PROT=y +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_FUNCTION_ERROR_INJECTION=y +CONFIG_HAVE_NMI=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_NMI_SUPPORT=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_DMA_CONTIGUOUS=y +CONFIG_GENERIC_SMP_IDLE_THREAD=y +CONFIG_GENERIC_IDLE_POLL_SETUP=y +CONFIG_ARCH_HAS_FORTIFY_SOURCE=y +CONFIG_ARCH_HAS_KEEPINITRD=y +CONFIG_ARCH_HAS_SET_MEMORY=y +CONFIG_ARCH_HAS_SET_DIRECT_MAP=y +CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y +CONFIG_ARCH_WANTS_NO_INSTR=y +CONFIG_HAVE_ASM_MODVERSIONS=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +CONFIG_HAVE_RSEQ=y +CONFIG_HAVE_RUST=y +CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_PERF_REGS=y +CONFIG_HAVE_PERF_USER_STACK_DUMP=y +CONFIG_HAVE_ARCH_JUMP_LABEL=y +CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE=y +CONFIG_MMU_GATHER_TABLE_FREE=y +CONFIG_MMU_GATHER_RCU_TABLE_FREE=y +CONFIG_MMU_LAZY_TLB_REFCOUNT=y +CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y +CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS=y +CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y +CONFIG_HAVE_CMPXCHG_LOCAL=y +CONFIG_HAVE_CMPXCHG_DOUBLE=y +CONFIG_HAVE_ARCH_SECCOMP=y +CONFIG_HAVE_ARCH_SECCOMP_FILTER=y +CONFIG_SECCOMP=y +CONFIG_SECCOMP_FILTER=y +# CONFIG_SECCOMP_CACHE_DEBUG is not set +CONFIG_HAVE_ARCH_KSTACK_ERASE=y +CONFIG_HAVE_STACKPROTECTOR=y +CONFIG_STACKPROTECTOR=y +CONFIG_STACKPROTECTOR_STRONG=y +CONFIG_ARCH_SUPPORTS_SHADOW_CALL_STACK=y +# CONFIG_SHADOW_CALL_STACK is not set +CONFIG_ARCH_SUPPORTS_LTO_CLANG=y +CONFIG_ARCH_SUPPORTS_LTO_CLANG_THIN=y +CONFIG_LTO_NONE=y +CONFIG_ARCH_SUPPORTS_CFI=y +CONFIG_HAVE_CONTEXT_TRACKING_USER=y +CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y +CONFIG_HAVE_PV_STEAL_CLOCK_GEN=y +CONFIG_HAVE_MOVE_PUD=y +CONFIG_HAVE_MOVE_PMD=y +CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y +CONFIG_HAVE_ARCH_HUGE_VMAP=y +CONFIG_HAVE_ARCH_HUGE_VMALLOC=y +CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y +CONFIG_HAVE_MOD_ARCH_SPECIFIC=y +CONFIG_MODULES_USE_ELF_RELA=y +CONFIG_ARCH_WANTS_EXECMEM_LATE=y +CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK=y +CONFIG_SOFTIRQ_ON_OWN_STACK=y +CONFIG_ARCH_HAS_ELF_RANDOMIZE=y +CONFIG_HAVE_ARCH_MMAP_RND_BITS=y +CONFIG_ARCH_MMAP_RND_BITS=18 +CONFIG_HAVE_PAGE_SIZE_16KB=y +CONFIG_PAGE_SIZE_16KB=y +CONFIG_PAGE_SIZE_LESS_THAN_64KB=y +CONFIG_PAGE_SIZE_LESS_THAN_256KB=y +CONFIG_PAGE_SHIFT=14 +CONFIG_ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT=y +CONFIG_HAVE_RELIABLE_STACKTRACE=y +CONFIG_CLONE_BACKWARDS=y +CONFIG_COMPAT_32BIT_TIME=y +CONFIG_ARCH_SUPPORTS_RT=y +CONFIG_HAVE_ARCH_VMAP_STACK=y +CONFIG_VMAP_STACK=y +CONFIG_HAVE_ARCH_RANDOMIZE_KSTACK_OFFSET=y +CONFIG_RANDOMIZE_KSTACK_OFFSET=y +# CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT is not set +CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y +CONFIG_STRICT_KERNEL_RWX=y +CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y +CONFIG_STRICT_MODULE_RWX=y +CONFIG_HAVE_ARCH_COMPILER_H=y +CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y +CONFIG_ARCH_USE_MEMREMAP_PROT=y +# CONFIG_LOCK_EVENT_COUNTS is not set +CONFIG_ARCH_HAS_RELR=y +CONFIG_RELR=y +CONFIG_ARCH_HAS_MEM_ENCRYPT=y +CONFIG_ARCH_HAS_CC_PLATFORM=y +CONFIG_HAVE_PREEMPT_DYNAMIC=y +CONFIG_HAVE_PREEMPT_DYNAMIC_KEY=y +CONFIG_ARCH_WANT_LD_ORPHAN_WARN=y +CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y +CONFIG_ARCH_SUPPORTS_PAGE_TABLE_CHECK=y +CONFIG_ARCH_HAVE_TRACE_MMIO_ACCESS=y +CONFIG_ARCH_HAS_HW_PTE_YOUNG=y +CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG=y +CONFIG_ARCH_HAS_KERNEL_FPU_SUPPORT=y + +# +# GCOV-based kernel profiling +# +# CONFIG_GCOV_KERNEL is not set +CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y +# end of GCOV-based kernel profiling + +CONFIG_HAVE_GCC_PLUGINS=y +# CONFIG_GCC_PLUGINS is not set +CONFIG_FUNCTION_ALIGNMENT_4B=y +CONFIG_FUNCTION_ALIGNMENT=4 +CONFIG_CC_HAS_MIN_FUNCTION_ALIGNMENT=y +CONFIG_CC_HAS_SANE_FUNCTION_ALIGNMENT=y +CONFIG_HAVE_ARCH_GET_SECUREBOOT=y +# end of General architecture-dependent options + +CONFIG_RT_MUTEXES=y +CONFIG_MODULES=y +# CONFIG_MODULE_DEBUG is not set +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODULE_FORCE_UNLOAD is not set +# CONFIG_MODULE_UNLOAD_TAINT_TRACKING is not set +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +# CONFIG_MODULE_SIG is not set +# CONFIG_MODULE_COMPRESS is not set +# CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS is not set +CONFIG_MODPROBE_PATH="/sbin/modprobe" +# CONFIG_TRIM_UNUSED_KSYMS is not set +CONFIG_MODULES_TREE_LOOKUP=y +CONFIG_BLOCK=y +CONFIG_BLOCK_LEGACY_AUTOLOAD=y +CONFIG_BLK_RQ_ALLOC_TIME=y +CONFIG_BLK_CGROUP_RWSTAT=y +CONFIG_BLK_CGROUP_PUNT_BIO=y +CONFIG_BLK_DEV_BSG_COMMON=y +CONFIG_BLK_ICQ=y +CONFIG_BLK_DEV_BSGLIB=y +CONFIG_BLK_DEV_INTEGRITY=y +CONFIG_BLK_DEV_WRITE_MOUNTED=y +CONFIG_BLK_DEV_ZONED=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_BLK_WBT=y +CONFIG_BLK_WBT_MQ=y +# CONFIG_BLK_CGROUP_IOLATENCY is not set +CONFIG_BLK_CGROUP_IOCOST=y +CONFIG_BLK_CGROUP_IOPRIO=y +CONFIG_BLK_DEBUG_FS=y +CONFIG_BLK_SED_OPAL=y +# CONFIG_BLK_INLINE_ENCRYPTION is not set + +# +# Partition Types +# +CONFIG_PARTITION_ADVANCED=y +# CONFIG_ACORN_PARTITION is not set +CONFIG_AIX_PARTITION=y +CONFIG_OSF_PARTITION=y +CONFIG_AMIGA_PARTITION=y +# CONFIG_ATARI_PARTITION is not set +CONFIG_MAC_PARTITION=y +CONFIG_MSDOS_PARTITION=y +CONFIG_BSD_DISKLABEL=y +CONFIG_MINIX_SUBPARTITION=y +CONFIG_SOLARIS_X86_PARTITION=y +CONFIG_UNIXWARE_DISKLABEL=y +CONFIG_LDM_PARTITION=y +# CONFIG_LDM_DEBUG is not set +CONFIG_SGI_PARTITION=y +# CONFIG_ULTRIX_PARTITION is not set +CONFIG_SUN_PARTITION=y +CONFIG_KARMA_PARTITION=y +CONFIG_EFI_PARTITION=y +# CONFIG_SYSV68_PARTITION is not set +# CONFIG_CMDLINE_PARTITION is not set +# CONFIG_OF_PARTITION is not set +# end of Partition Types + +CONFIG_BLK_PM=y +CONFIG_BLOCK_HOLDER_DEPRECATED=y +CONFIG_BLK_MQ_STACKING=y + +# +# IO Schedulers +# +CONFIG_MQ_IOSCHED_DEADLINE=y +CONFIG_MQ_IOSCHED_KYBER=y +CONFIG_IOSCHED_BFQ=y +CONFIG_BFQ_GROUP_IOSCHED=y +# CONFIG_BFQ_CGROUP_DEBUG is not set +# end of IO Schedulers + +CONFIG_PREEMPT_NOTIFIERS=y +CONFIG_PADATA=y +CONFIG_ASN1=y +CONFIG_UNINLINE_SPIN_UNLOCK=y +CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y +CONFIG_MUTEX_SPIN_ON_OWNER=y +CONFIG_RWSEM_SPIN_ON_OWNER=y +CONFIG_LOCK_SPIN_ON_OWNER=y +CONFIG_ARCH_USE_QUEUED_SPINLOCKS=y +CONFIG_QUEUED_SPINLOCKS=y +CONFIG_ARCH_USE_QUEUED_RWLOCKS=y +CONFIG_QUEUED_RWLOCKS=y +CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE=y +CONFIG_ARCH_HAS_SYSCALL_WRAPPER=y +CONFIG_FREEZER=y + +# +# Executable file formats +# +CONFIG_BINFMT_ELF=y +CONFIG_ARCH_BINFMT_ELF_STATE=y +CONFIG_ARCH_BINFMT_ELF_EXTRA_PHDRS=y +CONFIG_ARCH_HAVE_ELF_PROT=y +CONFIG_ARCH_USE_GNU_PROPERTY=y +CONFIG_ELFCORE=y +CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y +CONFIG_BINFMT_SCRIPT=y +CONFIG_BINFMT_MISC=y +CONFIG_COREDUMP=y +# end of Executable file formats + +# +# Memory Management options +# +CONFIG_SWAP=y +CONFIG_ZSWAP=y +# CONFIG_ZSWAP_DEFAULT_ON is not set +CONFIG_ZSWAP_SHRINKER_DEFAULT_ON=y +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_DEFLATE is not set +CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZO=y +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_842 is not set +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZ4 is not set +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZ4HC is not set +# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_ZSTD is not set +CONFIG_ZSWAP_COMPRESSOR_DEFAULT="lzo" +CONFIG_ZSMALLOC=y + +# +# Zsmalloc allocator options +# + +# +# Zsmalloc is a common backend allocator for zswap & zram +# +# CONFIG_ZSMALLOC_STAT is not set +CONFIG_ZSMALLOC_CHAIN_SIZE=8 +# end of Zsmalloc allocator options + +# +# Slab allocator options +# +CONFIG_SLUB=y +CONFIG_KVFREE_RCU_BATCHED=y +# CONFIG_SLUB_TINY is not set +CONFIG_SLAB_MERGE_DEFAULT=y +CONFIG_SLAB_FREELIST_RANDOM=y +# CONFIG_SLAB_FREELIST_HARDENED is not set +# CONFIG_SLAB_BUCKETS is not set +# CONFIG_SLUB_STATS is not set +# CONFIG_RANDOM_KMALLOC_CACHES is not set +# end of Slab allocator options + +# CONFIG_SHUFFLE_PAGE_ALLOCATOR is not set +# CONFIG_COMPAT_BRK is not set +CONFIG_SPARSEMEM=y +CONFIG_SPARSEMEM_EXTREME=y +CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y +CONFIG_SPARSEMEM_VMEMMAP=y +CONFIG_HAVE_GUP_FAST=y +CONFIG_ARCH_KEEP_MEMBLOCK=y +CONFIG_MEMORY_ISOLATION=y +CONFIG_EXCLUSIVE_SYSTEM_RAM=y +CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y +# CONFIG_MEMORY_HOTPLUG is not set +CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y +CONFIG_SPLIT_PTE_PTLOCKS=y +CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK=y +CONFIG_SPLIT_PMD_PTLOCKS=y +CONFIG_BALLOON=y +CONFIG_BALLOON_MIGRATION=y +CONFIG_COMPACTION=y +CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1 +CONFIG_PAGE_REPORTING=y +CONFIG_MIGRATION=y +CONFIG_ARCH_ENABLE_HUGEPAGE_MIGRATION=y +CONFIG_CONTIG_ALLOC=y +CONFIG_PCP_BATCH_SCALE_MAX=5 +CONFIG_PHYS_ADDR_T_64BIT=y +CONFIG_MMU_NOTIFIER=y +CONFIG_KSM=y +CONFIG_DEFAULT_MMAP_MIN_ADDR=32768 +CONFIG_ARCH_SUPPORTS_MEMORY_FAILURE=y +# CONFIG_MEMORY_FAILURE is not set +# CONFIG_TRANSPARENT_HUGEPAGE is not set +CONFIG_PAGE_MAPCOUNT=y +CONFIG_PGTABLE_HAS_HUGE_LEAVES=y +CONFIG_HAVE_GIGANTIC_FOLIOS=y +CONFIG_CMA=y +CONFIG_CMA_DEBUGFS=y +# CONFIG_CMA_SYSFS is not set +CONFIG_CMA_AREAS=7 +CONFIG_PAGE_BLOCK_MAX_ORDER=11 +CONFIG_GENERIC_EARLY_IOREMAP=y +# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set +# CONFIG_IDLE_PAGE_TRACKING is not set +CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y +CONFIG_ARCH_HAS_CURRENT_STACK_POINTER=y +CONFIG_ARCH_HAS_ZONE_DMA_SET=y +CONFIG_ZONE_DMA=y +CONFIG_ZONE_DMA32=y +CONFIG_ARCH_USES_HIGH_VMA_FLAGS=y +CONFIG_ARCH_HAS_PKEYS=y +CONFIG_ARCH_USES_PG_ARCH_2=y +CONFIG_ARCH_USES_PG_ARCH_3=y +CONFIG_VM_EVENT_COUNTERS=y +# CONFIG_PERCPU_STATS is not set +# CONFIG_GUP_TEST is not set +# CONFIG_DMAPOOL_TEST is not set +CONFIG_ARCH_HAS_PTE_SPECIAL=y +CONFIG_MEMFD_CREATE=y +CONFIG_SECRETMEM=y +# CONFIG_ANON_VMA_NAME is not set +# CONFIG_USERFAULTFD is not set +# CONFIG_LRU_GEN is not set +CONFIG_ARCH_SUPPORTS_PER_VMA_LOCK=y +CONFIG_PER_VMA_LOCK=y +CONFIG_LOCK_MM_AND_FIND_VMA=y +CONFIG_EXECMEM=y +CONFIG_ARCH_HAS_USER_SHADOW_STACK=y +CONFIG_PT_RECLAIM=y +CONFIG_ARCH_HAS_LAZY_MMU_MODE=y + +# +# Data Access Monitoring +# +# CONFIG_DAMON is not set +# end of Data Access Monitoring +# end of Memory Management options + +CONFIG_NET=y +CONFIG_NET_INGRESS=y +CONFIG_NET_EGRESS=y +CONFIG_NET_XGRESS=y +CONFIG_NET_REDIRECT=y +CONFIG_SKB_EXTENSIONS=y +CONFIG_NET_DEVMEM=y +CONFIG_NET_CRC32C=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=m +# CONFIG_INET_PSP is not set +CONFIG_UNIX=y +CONFIG_AF_UNIX_OOB=y +CONFIG_UNIX_DIAG=m +CONFIG_TLS=m +# CONFIG_TLS_DEVICE is not set +# CONFIG_TLS_TOE is not set +CONFIG_XFRM=y +CONFIG_XFRM_ALGO=y +CONFIG_XFRM_USER=y +# CONFIG_XFRM_INTERFACE is not set +CONFIG_XFRM_SUB_POLICY=y +CONFIG_XFRM_MIGRATE=y +CONFIG_XFRM_STATISTICS=y +CONFIG_XFRM_AH=m +CONFIG_XFRM_ESP=m +CONFIG_XFRM_IPCOMP=m +CONFIG_NET_KEY=m +CONFIG_NET_KEY_MIGRATE=y +CONFIG_XFRM_IPTFS=m +# CONFIG_DIBS is not set +# CONFIG_XDP_SOCKETS is not set +CONFIG_NET_HANDSHAKE=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_FIB_TRIE_STATS=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_ROUTE_CLASSID=y +# CONFIG_IP_PNP is not set +CONFIG_NET_IPIP=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NET_IP_TUNNEL=m +CONFIG_NET_IPGRE=m +CONFIG_NET_IPGRE_BROADCAST=y +CONFIG_IP_MROUTE_COMMON=y +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_SYN_COOKIES=y +CONFIG_NET_IPVTI=m +CONFIG_NET_UDP_TUNNEL=m +CONFIG_NET_FOU=m +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=m +CONFIG_INET_ESP=m +# CONFIG_INET_ESP_OFFLOAD is not set +# CONFIG_INET_ESPINTCP is not set +CONFIG_INET_IPCOMP=m +CONFIG_INET_TABLE_PERTURB_ORDER=16 +CONFIG_INET_XFRM_TUNNEL=m +CONFIG_INET_TUNNEL=m +CONFIG_INET_DIAG=m +CONFIG_INET_TCP_DIAG=m +CONFIG_INET_UDP_DIAG=m +CONFIG_INET_RAW_DIAG=m +# CONFIG_INET_DIAG_DESTROY is not set +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=m +CONFIG_TCP_CONG_CUBIC=y +CONFIG_TCP_CONG_WESTWOOD=m +CONFIG_TCP_CONG_HTCP=m +CONFIG_TCP_CONG_HSTCP=m +CONFIG_TCP_CONG_HYBLA=m +CONFIG_TCP_CONG_VEGAS=m +CONFIG_TCP_CONG_NV=m +CONFIG_TCP_CONG_SCALABLE=m +CONFIG_TCP_CONG_LP=m +CONFIG_TCP_CONG_VENO=m +CONFIG_TCP_CONG_YEAH=m +CONFIG_TCP_CONG_ILLINOIS=m +CONFIG_TCP_CONG_DCTCP=m +CONFIG_TCP_CONG_CDG=m +CONFIG_TCP_CONG_BBR=m +CONFIG_DEFAULT_CUBIC=y +# CONFIG_DEFAULT_RENO is not set +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_AO is not set +CONFIG_TCP_MD5SIG=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +# CONFIG_INET6_ESP_OFFLOAD is not set +# CONFIG_INET6_ESPINTCP is not set +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_ILA=m +CONFIG_INET6_XFRM_TUNNEL=m +CONFIG_INET6_TUNNEL=m +CONFIG_IPV6_VTI=m +CONFIG_IPV6_SIT=m +CONFIG_IPV6_SIT_6RD=y +CONFIG_IPV6_NDISC_NODETYPE=y +CONFIG_IPV6_TUNNEL=m +CONFIG_IPV6_GRE=m +CONFIG_IPV6_FOU=m +CONFIG_IPV6_FOU_TUNNEL=m +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_IPV6_SEG6_HMAC=y +CONFIG_IPV6_SEG6_BPF=y +# CONFIG_IPV6_RPL_LWTUNNEL is not set +# CONFIG_IPV6_IOAM6_LWTUNNEL is not set +CONFIG_NETLABEL=y +CONFIG_MPTCP=y +CONFIG_INET_MPTCP_DIAG=m +CONFIG_MPTCP_IPV6=y +# CONFIG_NETWORK_SECMARK is not set +CONFIG_NET_PTP_CLASSIFY=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_NETFILTER_ADVANCED=y +CONFIG_BRIDGE_NETFILTER=m + +# +# Core Netfilter Configuration +# +CONFIG_NETFILTER_INGRESS=y +CONFIG_NETFILTER_EGRESS=y +CONFIG_NETFILTER_SKIP_EGRESS=y +CONFIG_NETFILTER_NETLINK=m +CONFIG_NETFILTER_FAMILY_BRIDGE=y +CONFIG_NETFILTER_FAMILY_ARP=y +CONFIG_NETFILTER_BPF_LINK=y +CONFIG_NETFILTER_NETLINK_HOOK=m +CONFIG_NETFILTER_NETLINK_ACCT=m +CONFIG_NETFILTER_NETLINK_QUEUE=m +CONFIG_NETFILTER_NETLINK_LOG=m +CONFIG_NETFILTER_NETLINK_OSF=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_LOG_SYSLOG=m +CONFIG_NETFILTER_CONNCOUNT=m +CONFIG_NF_CONNTRACK_MARK=y +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMEOUT=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CONNTRACK_LABELS=y +CONFIG_NF_CONNTRACK_OVS=y +CONFIG_NF_CT_PROTO_GRE=y +CONFIG_NF_CT_PROTO_SCTP=y +CONFIG_NF_CONNTRACK_AMANDA=m +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_H323=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_BROADCAST=m +CONFIG_NF_CONNTRACK_NETBIOS_NS=m +CONFIG_NF_CONNTRACK_SNMP=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_SANE=m +CONFIG_NF_CONNTRACK_SIP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NF_CT_NETLINK_TIMEOUT=m +CONFIG_NF_CT_NETLINK_HELPER=m +CONFIG_NETFILTER_NETLINK_GLUE_CT=y +CONFIG_NF_NAT=m +CONFIG_NF_NAT_AMANDA=m +CONFIG_NF_NAT_FTP=m +CONFIG_NF_NAT_IRC=m +CONFIG_NF_NAT_SIP=m +CONFIG_NF_NAT_TFTP=m +CONFIG_NF_NAT_REDIRECT=y +CONFIG_NF_NAT_MASQUERADE=y +CONFIG_NF_NAT_OVS=y +CONFIG_NETFILTER_SYNPROXY=m +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +# CONFIG_NFT_EXTHDR_DCCP is not set +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_CONNLIMIT=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_TUNNEL=m +CONFIG_NFT_QUEUE=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_REJECT_INET=m +CONFIG_NFT_COMPAT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB=m +CONFIG_NFT_FIB_INET=m +CONFIG_NFT_XFRM=m +CONFIG_NFT_SOCKET=m +CONFIG_NFT_OSF=m +CONFIG_NFT_TPROXY=m +CONFIG_NFT_SYNPROXY=m +CONFIG_NF_DUP_NETDEV=m +CONFIG_NFT_DUP_NETDEV=m +CONFIG_NFT_FWD_NETDEV=m +CONFIG_NFT_FIB_NETDEV=m +CONFIG_NFT_REJECT_NETDEV=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NF_FLOW_TABLE_PROCFS=y +CONFIG_NETFILTER_XTABLES=y +CONFIG_NETFILTER_XTABLES_LEGACY=y + +# +# Xtables combined modules +# +CONFIG_NETFILTER_XT_MARK=m +CONFIG_NETFILTER_XT_CONNMARK=m +CONFIG_NETFILTER_XT_SET=m + +# +# Xtables targets +# +CONFIG_NETFILTER_XT_TARGET_AUDIT=m +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m +CONFIG_NETFILTER_XT_TARGET_CONNMARK=m +CONFIG_NETFILTER_XT_TARGET_CT=m +CONFIG_NETFILTER_XT_TARGET_DSCP=m +CONFIG_NETFILTER_XT_TARGET_HL=m +CONFIG_NETFILTER_XT_TARGET_HMARK=m +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m +CONFIG_NETFILTER_XT_TARGET_LED=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_NAT=m +CONFIG_NETFILTER_XT_TARGET_NETMAP=m +CONFIG_NETFILTER_XT_TARGET_NFLOG=m +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m +CONFIG_NETFILTER_XT_TARGET_NOTRACK=m +CONFIG_NETFILTER_XT_TARGET_RATEEST=m +CONFIG_NETFILTER_XT_TARGET_REDIRECT=m +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=m +CONFIG_NETFILTER_XT_TARGET_TEE=m +CONFIG_NETFILTER_XT_TARGET_TPROXY=m +CONFIG_NETFILTER_XT_TARGET_TRACE=m +CONFIG_NETFILTER_XT_TARGET_TCPMSS=m +CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m + +# +# Xtables matches +# +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_CGROUP=m +CONFIG_NETFILTER_XT_MATCH_CLUSTER=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m +CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m +CONFIG_NETFILTER_XT_MATCH_CONNMARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_CPU=m +CONFIG_NETFILTER_XT_MATCH_DCCP=m +CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m +CONFIG_NETFILTER_XT_MATCH_DSCP=m +CONFIG_NETFILTER_XT_MATCH_ECN=m +CONFIG_NETFILTER_XT_MATCH_ESP=m +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m +CONFIG_NETFILTER_XT_MATCH_HELPER=m +CONFIG_NETFILTER_XT_MATCH_HL=m +CONFIG_NETFILTER_XT_MATCH_IPCOMP=m +CONFIG_NETFILTER_XT_MATCH_IPRANGE=m +CONFIG_NETFILTER_XT_MATCH_IPVS=m +CONFIG_NETFILTER_XT_MATCH_L2TP=m +CONFIG_NETFILTER_XT_MATCH_LENGTH=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MAC=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_NFACCT=m +CONFIG_NETFILTER_XT_MATCH_OSF=m +CONFIG_NETFILTER_XT_MATCH_OWNER=m +CONFIG_NETFILTER_XT_MATCH_POLICY=m +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m +CONFIG_NETFILTER_XT_MATCH_QUOTA=m +CONFIG_NETFILTER_XT_MATCH_RATEEST=m +CONFIG_NETFILTER_XT_MATCH_REALM=m +CONFIG_NETFILTER_XT_MATCH_RECENT=m +CONFIG_NETFILTER_XT_MATCH_SCTP=m +CONFIG_NETFILTER_XT_MATCH_SOCKET=m +CONFIG_NETFILTER_XT_MATCH_STATE=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NETFILTER_XT_MATCH_STRING=m +CONFIG_NETFILTER_XT_MATCH_TCPMSS=m +CONFIG_NETFILTER_XT_MATCH_TIME=m +CONFIG_NETFILTER_XT_MATCH_U32=m +# end of Core Netfilter Configuration + +CONFIG_IP_SET=m +CONFIG_IP_SET_MAX=256 +CONFIG_IP_SET_BITMAP_IP=m +CONFIG_IP_SET_BITMAP_IPMAC=m +CONFIG_IP_SET_BITMAP_PORT=m +CONFIG_IP_SET_HASH_IP=m +CONFIG_IP_SET_HASH_IPMARK=m +CONFIG_IP_SET_HASH_IPPORT=m +CONFIG_IP_SET_HASH_IPPORTIP=m +CONFIG_IP_SET_HASH_IPPORTNET=m +CONFIG_IP_SET_HASH_IPMAC=m +CONFIG_IP_SET_HASH_MAC=m +CONFIG_IP_SET_HASH_NETPORTNET=m +CONFIG_IP_SET_HASH_NET=m +CONFIG_IP_SET_HASH_NETNET=m +CONFIG_IP_SET_HASH_NETPORT=m +CONFIG_IP_SET_HASH_NETIFACE=m +CONFIG_IP_SET_LIST_SET=m +CONFIG_IP_VS=m +CONFIG_IP_VS_IPV6=y +# CONFIG_IP_VS_DEBUG is not set +CONFIG_IP_VS_TAB_BITS=12 + +# +# IPVS transport protocol load balancing support +# +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_PROTO_AH_ESP=y +CONFIG_IP_VS_PROTO_ESP=y +CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_SCTP=y + +# +# IPVS scheduler +# +CONFIG_IP_VS_RR=m +CONFIG_IP_VS_WRR=m +CONFIG_IP_VS_LC=m +CONFIG_IP_VS_WLC=m +CONFIG_IP_VS_FO=m +CONFIG_IP_VS_OVF=m +CONFIG_IP_VS_LBLC=m +CONFIG_IP_VS_LBLCR=m +CONFIG_IP_VS_DH=m +CONFIG_IP_VS_SH=m +CONFIG_IP_VS_MH=m +CONFIG_IP_VS_SED=m +CONFIG_IP_VS_NQ=m +CONFIG_IP_VS_TWOS=m + +# +# IPVS SH scheduler +# +CONFIG_IP_VS_SH_TAB_BITS=8 + +# +# IPVS MH scheduler +# +CONFIG_IP_VS_MH_TAB_INDEX=12 + +# +# IPVS application helper +# +CONFIG_IP_VS_FTP=m +CONFIG_IP_VS_NFCT=y +CONFIG_IP_VS_PE_SIP=m + +# +# IP: Netfilter Configuration +# +CONFIG_NF_DEFRAG_IPV4=m +CONFIG_IP_NF_IPTABLES_LEGACY=m +CONFIG_NF_SOCKET_IPV4=m +CONFIG_NF_TPROXY_IPV4=m +CONFIG_NF_TABLES_IPV4=y +CONFIG_NFT_REJECT_IPV4=m +CONFIG_NFT_DUP_IPV4=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_DUP_IPV4=m +CONFIG_NF_LOG_ARP=m +CONFIG_NF_LOG_IPV4=m +CONFIG_NF_REJECT_IPV4=y +CONFIG_NF_NAT_SNMP_BASIC=m +CONFIG_NF_NAT_PPTP=m +CONFIG_NF_NAT_H323=m +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=m +CONFIG_IP_NF_MATCH_ECN=m +CONFIG_IP_NF_MATCH_RPFILTER=m +CONFIG_IP_NF_MATCH_TTL=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_TARGET_SYNPROXY=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_TARGET_NETMAP=m +CONFIG_IP_NF_TARGET_REDIRECT=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_TARGET_ECN=m +CONFIG_IP_NF_TARGET_TTL=m +CONFIG_IP_NF_RAW=m +CONFIG_IP_NF_SECURITY=m +CONFIG_IP_NF_ARPTABLES=m +CONFIG_NFT_COMPAT_ARP=m +CONFIG_IP_NF_ARPFILTER=m +CONFIG_IP_NF_ARP_MANGLE=m +# end of IP: Netfilter Configuration + +# +# IPv6: Netfilter Configuration +# +CONFIG_IP6_NF_IPTABLES_LEGACY=m +CONFIG_NF_SOCKET_IPV6=m +CONFIG_NF_TPROXY_IPV6=m +CONFIG_NF_TABLES_IPV6=y +CONFIG_NFT_REJECT_IPV6=m +CONFIG_NFT_DUP_IPV6=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_NF_DUP_IPV6=m +CONFIG_NF_REJECT_IPV6=m +CONFIG_NF_LOG_IPV6=m +CONFIG_IP6_NF_IPTABLES=m +CONFIG_IP6_NF_MATCH_AH=m +CONFIG_IP6_NF_MATCH_EUI64=m +CONFIG_IP6_NF_MATCH_FRAG=m +CONFIG_IP6_NF_MATCH_OPTS=m +CONFIG_IP6_NF_MATCH_HL=m +CONFIG_IP6_NF_MATCH_IPV6HEADER=m +CONFIG_IP6_NF_MATCH_MH=m +CONFIG_IP6_NF_MATCH_RPFILTER=m +CONFIG_IP6_NF_MATCH_RT=m +CONFIG_IP6_NF_MATCH_SRH=m +CONFIG_IP6_NF_TARGET_HL=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_TARGET_SYNPROXY=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_SECURITY=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_IP6_NF_TARGET_NPT=m +# end of IPv6: Netfilter Configuration + +CONFIG_NF_DEFRAG_IPV6=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_NFT_BRIDGE_META=m +CONFIG_NFT_BRIDGE_REJECT=m +CONFIG_NF_CONNTRACK_BRIDGE=m +CONFIG_BRIDGE_NF_EBTABLES_LEGACY=m +CONFIG_BRIDGE_NF_EBTABLES=m +CONFIG_BRIDGE_EBT_BROUTE=m +CONFIG_BRIDGE_EBT_T_FILTER=m +CONFIG_BRIDGE_EBT_T_NAT=m +CONFIG_BRIDGE_EBT_802_3=m +CONFIG_BRIDGE_EBT_AMONG=m +CONFIG_BRIDGE_EBT_ARP=m +CONFIG_BRIDGE_EBT_IP=m +CONFIG_BRIDGE_EBT_IP6=m +CONFIG_BRIDGE_EBT_LIMIT=m +CONFIG_BRIDGE_EBT_MARK=m +CONFIG_BRIDGE_EBT_PKTTYPE=m +CONFIG_BRIDGE_EBT_STP=m +CONFIG_BRIDGE_EBT_VLAN=m +CONFIG_BRIDGE_EBT_ARPREPLY=m +CONFIG_BRIDGE_EBT_DNAT=m +CONFIG_BRIDGE_EBT_MARK_T=m +CONFIG_BRIDGE_EBT_REDIRECT=m +CONFIG_BRIDGE_EBT_SNAT=m +CONFIG_BRIDGE_EBT_LOG=m +CONFIG_BRIDGE_EBT_NFLOG=m +CONFIG_IP_SCTP=m +# CONFIG_SCTP_DBG_OBJCNT is not set +CONFIG_SCTP_DEFAULT_COOKIE_HMAC_SHA256=y +# CONFIG_SCTP_DEFAULT_COOKIE_HMAC_NONE is not set +CONFIG_INET_SCTP_DIAG=m +CONFIG_RDS=m +CONFIG_RDS_TCP=m +# CONFIG_RDS_DEBUG is not set +CONFIG_TIPC=m +CONFIG_TIPC_MEDIA_UDP=y +CONFIG_TIPC_CRYPTO=y +CONFIG_TIPC_DIAG=m +CONFIG_ATM=m +CONFIG_ATM_BR2684=m +# CONFIG_ATM_BR2684_IPFILTER is not set +CONFIG_L2TP=m +CONFIG_L2TP_DEBUGFS=m +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=m +CONFIG_L2TP_ETH=m +CONFIG_STP=m +CONFIG_GARP=m +CONFIG_MRP=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_IGMP_SNOOPING=y +CONFIG_BRIDGE_VLAN_FILTERING=y +# CONFIG_BRIDGE_MRP is not set +# CONFIG_BRIDGE_CFM is not set +CONFIG_NET_DSA=m +CONFIG_NET_DSA_TAG_NONE=m +# CONFIG_NET_DSA_TAG_AR9331 is not set +CONFIG_NET_DSA_TAG_BRCM_COMMON=m +CONFIG_NET_DSA_TAG_BRCM=m +CONFIG_NET_DSA_TAG_BRCM_LEGACY=m +CONFIG_NET_DSA_TAG_BRCM_LEGACY_FCS=m +CONFIG_NET_DSA_TAG_BRCM_PREPEND=m +# CONFIG_NET_DSA_TAG_HELLCREEK is not set +# CONFIG_NET_DSA_TAG_GSWIP is not set +CONFIG_NET_DSA_TAG_DSA_COMMON=m +CONFIG_NET_DSA_TAG_DSA=m +CONFIG_NET_DSA_TAG_EDSA=m +# CONFIG_NET_DSA_TAG_MTK is not set +# CONFIG_NET_DSA_TAG_MXL_862XX is not set +# CONFIG_NET_DSA_TAG_MXL_GSW1XX is not set +# CONFIG_NET_DSA_TAG_KSZ is not set +# CONFIG_NET_DSA_TAG_OCELOT is not set +# CONFIG_NET_DSA_TAG_OCELOT_8021Q is not set +CONFIG_NET_DSA_TAG_QCA=m +# CONFIG_NET_DSA_TAG_RTL4_A is not set +# CONFIG_NET_DSA_TAG_RTL8_4 is not set +# CONFIG_NET_DSA_TAG_RZN1_A5PSW is not set +# CONFIG_NET_DSA_TAG_LAN9303 is not set +# CONFIG_NET_DSA_TAG_SJA1105 is not set +CONFIG_NET_DSA_TAG_TRAILER=m +# CONFIG_NET_DSA_TAG_VSC73XX_8021Q is not set +# CONFIG_NET_DSA_TAG_XRS700X is not set +# CONFIG_NET_DSA_TAG_YT921X is not set +CONFIG_VLAN_8021Q=m +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_VLAN_8021Q_MVRP=y +CONFIG_LLC=m +# CONFIG_LLC2 is not set +CONFIG_ATALK=m +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_PHONET is not set +CONFIG_6LOWPAN=m +# CONFIG_6LOWPAN_DEBUGFS is not set +CONFIG_6LOWPAN_NHC=m +CONFIG_6LOWPAN_NHC_DEST=m +CONFIG_6LOWPAN_NHC_FRAGMENT=m +CONFIG_6LOWPAN_NHC_HOP=m +CONFIG_6LOWPAN_NHC_IPV6=m +CONFIG_6LOWPAN_NHC_MOBILITY=m +CONFIG_6LOWPAN_NHC_ROUTING=m +CONFIG_6LOWPAN_NHC_UDP=m +CONFIG_6LOWPAN_GHC_EXT_HDR_HOP=m +CONFIG_6LOWPAN_GHC_UDP=m +CONFIG_6LOWPAN_GHC_ICMPV6=m +CONFIG_6LOWPAN_GHC_EXT_HDR_DEST=m +CONFIG_6LOWPAN_GHC_EXT_HDR_FRAG=m +CONFIG_6LOWPAN_GHC_EXT_HDR_ROUTE=m +CONFIG_IEEE802154=m +# CONFIG_IEEE802154_NL802154_EXPERIMENTAL is not set +CONFIG_IEEE802154_SOCKET=m +CONFIG_IEEE802154_6LOWPAN=m +CONFIG_MAC802154=m +CONFIG_NET_SCHED=y + +# +# Queueing/Scheduling +# +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_MULTIQ=m +CONFIG_NET_SCH_RED=m +CONFIG_NET_SCH_SFB=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TEQL=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_CBS=m +CONFIG_NET_SCH_ETF=m +CONFIG_NET_SCH_MQPRIO_LIB=m +CONFIG_NET_SCH_TAPRIO=m +CONFIG_NET_SCH_GRED=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_DRR=m +CONFIG_NET_SCH_MQPRIO=m +CONFIG_NET_SCH_SKBPRIO=m +CONFIG_NET_SCH_CHOKE=m +CONFIG_NET_SCH_QFQ=m +CONFIG_NET_SCH_CODEL=m +CONFIG_NET_SCH_FQ_CODEL=y +CONFIG_NET_SCH_CAKE=m +CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_HHF=m +CONFIG_NET_SCH_PIE=m +CONFIG_NET_SCH_FQ_PIE=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_SCH_PLUG=m +CONFIG_NET_SCH_ETS=m +# CONFIG_NET_SCH_BPF is not set +# CONFIG_NET_SCH_DUALPI2 is not set +# CONFIG_NET_SCH_DEFAULT is not set + +# +# Classification +# +CONFIG_NET_CLS=y +CONFIG_NET_CLS_BASIC=m +CONFIG_NET_CLS_ROUTE4=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_U32=m +CONFIG_CLS_U32_PERF=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_CLS_CGROUP=y +CONFIG_NET_CLS_BPF=m +CONFIG_NET_CLS_FLOWER=m +CONFIG_NET_CLS_MATCHALL=m +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_STACK=32 +CONFIG_NET_EMATCH_CMP=m +CONFIG_NET_EMATCH_NBYTE=m +CONFIG_NET_EMATCH_U32=m +CONFIG_NET_EMATCH_META=m +CONFIG_NET_EMATCH_TEXT=m +CONFIG_NET_EMATCH_CANID=m +CONFIG_NET_EMATCH_IPSET=m +CONFIG_NET_EMATCH_IPT=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=m +CONFIG_NET_ACT_GACT=m +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=m +CONFIG_NET_ACT_SAMPLE=m +CONFIG_NET_ACT_NAT=m +CONFIG_NET_ACT_PEDIT=m +CONFIG_NET_ACT_SIMP=m +CONFIG_NET_ACT_SKBEDIT=m +CONFIG_NET_ACT_CSUM=m +# CONFIG_NET_ACT_MPLS is not set +CONFIG_NET_ACT_VLAN=m +CONFIG_NET_ACT_BPF=m +CONFIG_NET_ACT_CONNMARK=m +CONFIG_NET_ACT_CTINFO=m +CONFIG_NET_ACT_SKBMOD=m +CONFIG_NET_ACT_IFE=m +CONFIG_NET_ACT_TUNNEL_KEY=m +CONFIG_NET_ACT_CT=m +CONFIG_NET_ACT_GATE=m +CONFIG_NET_IFE_SKBMARK=m +CONFIG_NET_IFE_SKBPRIO=m +CONFIG_NET_IFE_SKBTCINDEX=m +# CONFIG_NET_TC_SKB_EXT is not set +CONFIG_NET_SCH_FIFO=y +CONFIG_DCB=y +CONFIG_DNS_RESOLVER=y +CONFIG_BATMAN_ADV=m +# CONFIG_BATMAN_ADV_BATMAN_V is not set +CONFIG_BATMAN_ADV_BLA=y +CONFIG_BATMAN_ADV_DAT=y +CONFIG_BATMAN_ADV_MCAST=y +# CONFIG_BATMAN_ADV_DEBUG is not set +# CONFIG_BATMAN_ADV_TRACING is not set +CONFIG_OPENVSWITCH=m +CONFIG_OPENVSWITCH_GRE=m +CONFIG_OPENVSWITCH_VXLAN=m +CONFIG_OPENVSWITCH_GENEVE=m +CONFIG_VSOCKETS=m +CONFIG_VSOCKETS_DIAG=m +CONFIG_VSOCKETS_LOOPBACK=m +CONFIG_VIRTIO_VSOCKETS=m +CONFIG_VIRTIO_VSOCKETS_COMMON=m +CONFIG_NETLINK_DIAG=m +CONFIG_MPLS=y +CONFIG_NET_MPLS_GSO=m +CONFIG_MPLS_ROUTING=m +CONFIG_MPLS_IPTUNNEL=m +CONFIG_NET_NSH=m +# CONFIG_HSR is not set +CONFIG_NET_SWITCHDEV=y +CONFIG_NET_L3_MASTER_DEV=y +CONFIG_QRTR=m +CONFIG_QRTR_TUN=m +# CONFIG_NET_NCSI is not set +CONFIG_PCPU_DEV_REFCNT=y +CONFIG_MAX_SKB_FRAGS=17 +CONFIG_RPS=y +CONFIG_RFS_ACCEL=y +CONFIG_SOCK_RX_QUEUE_MAPPING=y +CONFIG_XPS=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_CGROUP_NET_CLASSID=y +CONFIG_NET_RX_BUSY_POLL=y +CONFIG_BQL=y +CONFIG_BPF_STREAM_PARSER=y +CONFIG_NET_FLOW_LIMIT=y + +# +# Network testing +# +CONFIG_NET_PKTGEN=m +# CONFIG_NET_DROP_MONITOR is not set +# end of Network testing +# end of Networking options + +CONFIG_CAN=m +CONFIG_CAN_RAW=m +CONFIG_CAN_BCM=m +CONFIG_CAN_GW=m +# CONFIG_CAN_J1939 is not set +# CONFIG_CAN_ISOTP is not set +CONFIG_BT=m +CONFIG_BT_BREDR=y +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m +CONFIG_BT_LE=y +CONFIG_BT_LE_L2CAP_ECRED=y +CONFIG_BT_6LOWPAN=m +# CONFIG_BT_LEDS is not set +CONFIG_BT_MSFTEXT=y +# CONFIG_BT_AOSPEXT is not set +CONFIG_BT_BRCMEXT=y +# CONFIG_BT_DEBUGFS is not set +# CONFIG_BT_SELFTEST is not set + +# +# Bluetooth device drivers +# +CONFIG_BT_INTEL=m +CONFIG_BT_BCM=m +CONFIG_BT_RTL=m +CONFIG_BT_QCA=m +CONFIG_BT_HCIBTUSB=m +# CONFIG_BT_HCIBTUSB_AUTOSUSPEND is not set +CONFIG_BT_HCIBTUSB_POLL_SYNC=y +CONFIG_BT_HCIBTUSB_BCM=y +# CONFIG_BT_HCIBTUSB_MTK is not set +CONFIG_BT_HCIBTUSB_RTL=y +CONFIG_BT_HCIBTSDIO=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_SERDEV=y +CONFIG_BT_HCIUART_H4=y +# CONFIG_BT_HCIUART_NOKIA is not set +CONFIG_BT_HCIUART_BCSP=y +CONFIG_BT_HCIUART_ATH3K=y +CONFIG_BT_HCIUART_LL=y +CONFIG_BT_HCIUART_3WIRE=y +CONFIG_BT_HCIUART_INTEL=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIUART_RTL=y +CONFIG_BT_HCIUART_QCA=y +# CONFIG_BT_HCIUART_AG6XX is not set +CONFIG_BT_HCIUART_MRVL=y +# CONFIG_BT_HCIUART_AML is not set +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBCM4377=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIVHCI=m +CONFIG_BT_MRVL=m +CONFIG_BT_MRVL_SDIO=m +CONFIG_BT_ATH3K=m +# CONFIG_BT_MTKSDIO is not set +# CONFIG_BT_MTKUART is not set +CONFIG_BT_HCIRSI=m +# CONFIG_BT_VIRTIO is not set +# CONFIG_BT_NXPUART is not set +# end of Bluetooth device drivers + +# CONFIG_AF_RXRPC is not set +# CONFIG_AF_KCM is not set +CONFIG_STREAM_PARSER=y +# CONFIG_MCTP is not set +CONFIG_FIB_RULES=y +CONFIG_WIRELESS=y +CONFIG_WEXT_CORE=y +CONFIG_WEXT_PROC=y +CONFIG_CFG80211=m +# CONFIG_NL80211_TESTMODE is not set +# CONFIG_CFG80211_DEVELOPER_WARNINGS is not set +# CONFIG_CFG80211_CERTIFICATION_ONUS is not set +CONFIG_CFG80211_REQUIRE_SIGNED_REGDB=y +CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS=y +CONFIG_CFG80211_DEFAULT_PS=y +CONFIG_CFG80211_DEBUGFS=y +CONFIG_CFG80211_CRDA_SUPPORT=y +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_HAS_RC=y +CONFIG_MAC80211_RC_MINSTREL=y +CONFIG_MAC80211_RC_DEFAULT_MINSTREL=y +CONFIG_MAC80211_RC_DEFAULT="minstrel_ht" +CONFIG_MAC80211_MESH=y +CONFIG_MAC80211_LEDS=y +CONFIG_MAC80211_DEBUGFS=y +# CONFIG_MAC80211_MESSAGE_TRACING is not set +# CONFIG_MAC80211_DEBUG_MENU is not set +CONFIG_MAC80211_STA_HASH_MAX_SIZE=0 +CONFIG_RFKILL=m +CONFIG_RFKILL_LEDS=y +CONFIG_RFKILL_INPUT=y +CONFIG_RFKILL_GPIO=m +CONFIG_NET_9P=m +CONFIG_NET_9P_FD=m +CONFIG_NET_9P_VIRTIO=m +# CONFIG_NET_9P_USBG is not set +# CONFIG_NET_9P_DEBUG is not set +CONFIG_CEPH_LIB=m +# CONFIG_CEPH_LIB_PRETTYDEBUG is not set +# CONFIG_CEPH_LIB_USE_DNS_RESOLVER is not set +CONFIG_NFC=m +CONFIG_NFC_DIGITAL=m +CONFIG_NFC_NCI=m +# CONFIG_NFC_NCI_SPI is not set +# CONFIG_NFC_NCI_UART is not set +CONFIG_NFC_HCI=m +CONFIG_NFC_SHDLC=y + +# +# Near Field Communication (NFC) devices +# +# CONFIG_NFC_TRF7970A is not set +CONFIG_NFC_SIM=m +CONFIG_NFC_PORT100=m +# CONFIG_NFC_VIRTUAL_NCI is not set +# CONFIG_NFC_FDP is not set +CONFIG_NFC_PN544=m +CONFIG_NFC_PN544_I2C=m +# CONFIG_NFC_PN533_USB is not set +# CONFIG_NFC_PN533_I2C is not set +# CONFIG_NFC_PN532_UART is not set +CONFIG_NFC_MICROREAD=m +CONFIG_NFC_MICROREAD_I2C=m +CONFIG_NFC_MRVL=m +CONFIG_NFC_MRVL_USB=m +# CONFIG_NFC_MRVL_I2C is not set +CONFIG_NFC_ST21NFCA=m +CONFIG_NFC_ST21NFCA_I2C=m +# CONFIG_NFC_ST_NCI_I2C is not set +# CONFIG_NFC_ST_NCI_SPI is not set +# CONFIG_NFC_NXP_NCI is not set +CONFIG_NFC_S3FWRN5=m +CONFIG_NFC_S3FWRN5_I2C=m +# CONFIG_NFC_S3FWRN82_UART is not set +# CONFIG_NFC_ST95HF is not set +# end of Near Field Communication (NFC) devices + +CONFIG_PSAMPLE=m +CONFIG_NET_IFE=m +CONFIG_LWTUNNEL=y +CONFIG_LWTUNNEL_BPF=y +CONFIG_DST_CACHE=y +CONFIG_GRO_CELLS=y +CONFIG_NET_SELFTESTS=y +CONFIG_NET_SOCK_MSG=y +CONFIG_NET_DEVLINK=y +CONFIG_PAGE_POOL=y +CONFIG_PAGE_POOL_STATS=y +CONFIG_FAILOVER=m +CONFIG_ETHTOOL_NETLINK=y + +# +# Device Drivers +# +CONFIG_ARM_AMBA=y +CONFIG_HAVE_PCI=y +CONFIG_GENERIC_PCI_IOMAP=y +CONFIG_PCI=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_DOMAINS_GENERIC=y +CONFIG_PCI_SYSCALL=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI_PCIE=y +CONFIG_PCIEAER=y +CONFIG_PCIEAER_INJECT=m +CONFIG_PCIE_ECRC=y +CONFIG_PCIEASPM=y +CONFIG_PCIEASPM_DEFAULT=y +# CONFIG_PCIEASPM_POWERSAVE is not set +# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set +# CONFIG_PCIEASPM_PERFORMANCE is not set +CONFIG_PCIE_PME=y +# CONFIG_PCIE_DPC is not set +# CONFIG_PCIE_PTM is not set +CONFIG_PCI_MSI=y +CONFIG_PCI_QUIRKS=y +# CONFIG_PCI_DEBUG is not set +# CONFIG_PCI_REALLOC_ENABLE_AUTO is not set +CONFIG_PCI_STUB=m +# CONFIG_PCI_PF_STUB is not set +CONFIG_PCI_ATS=y +# CONFIG_PCI_TSM is not set +# CONFIG_PCI_DOE is not set +CONFIG_PCI_ECAM=y +CONFIG_PCI_IOV=y +# CONFIG_PCI_NPEM is not set +CONFIG_PCI_PRI=y +CONFIG_PCI_PASID=y +# CONFIG_PCIE_TPH is not set +CONFIG_PCI_LABEL=y +CONFIG_PCI_DYNAMIC_OF_NODES=y +# CONFIG_PCIE_BUS_TUNE_OFF is not set +CONFIG_PCIE_BUS_DEFAULT=y +# CONFIG_PCIE_BUS_SAFE is not set +# CONFIG_PCIE_BUS_PERFORMANCE is not set +# CONFIG_PCIE_BUS_PEER2PEER is not set +CONFIG_VGA_ARB=y +CONFIG_VGA_ARB_MAX_GPUS=16 +CONFIG_HOTPLUG_PCI=y +# CONFIG_HOTPLUG_PCI_CPCI is not set +# CONFIG_HOTPLUG_PCI_OCTEONEP is not set +# CONFIG_HOTPLUG_PCI_SHPC is not set + +# +# PCI controller drivers +# +CONFIG_PCI_HOST_COMMON=y +# CONFIG_PCIE_ALTERA is not set +CONFIG_PCIE_APPLE_MSI_DOORBELL_ADDR=0xfffff000 +CONFIG_PCIE_APPLE=y +# CONFIG_PCI_HOST_THUNDER_PEM is not set +# CONFIG_PCI_HOST_THUNDER_ECAM is not set +# CONFIG_PCI_FTPCI100 is not set +# CONFIG_PCI_HOST_GENERIC is not set +# CONFIG_PCI_XGENE is not set +# CONFIG_PCIE_XILINX is not set + +# +# Cadence-based PCIe controllers +# +# CONFIG_PCIE_CADENCE_PLAT_HOST is not set +# end of Cadence-based PCIe controllers + +# +# DesignWare-based PCIe controllers +# +# CONFIG_PCIE_AL is not set +# CONFIG_PCIE_AMD_MDB is not set +# CONFIG_PCI_MESON is not set +# CONFIG_PCI_HISI is not set +# CONFIG_PCIE_KIRIN is not set +# CONFIG_PCIE_DW_PLAT_HOST is not set +# end of DesignWare-based PCIe controllers + +# +# Mobiveil-based PCIe controllers +# +# end of Mobiveil-based PCIe controllers + +# +# PLDA-based PCIe controllers +# +# CONFIG_PCIE_MICROCHIP_HOST is not set +# end of PLDA-based PCIe controllers +# end of PCI controller drivers + +# +# PCI Endpoint +# +# CONFIG_PCI_ENDPOINT is not set +# end of PCI Endpoint + +# +# PCI switch controller drivers +# +# CONFIG_PCI_SW_SWITCHTEC is not set +# end of PCI switch controller drivers + +CONFIG_PCI_PWRCTRL=y +CONFIG_PCI_PWRCTRL_GENERIC=y +# CONFIG_PCI_PWRCTRL_TC9563 is not set +# CONFIG_CXL_BUS is not set +# CONFIG_PCCARD is not set +# CONFIG_RAPIDIO is not set +# CONFIG_PC104 is not set + +# +# Generic Driver Options +# +# CONFIG_UEVENT_HELPER is not set +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_DEVTMPFS_SAFE is not set +CONFIG_DRIVER_DEFERRED_PROBE_TIMEOUT=10 +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y + +# +# Firmware loader +# +CONFIG_FW_LOADER=y +CONFIG_FW_LOADER_DEBUG=y +CONFIG_RUST_FW_LOADER_ABSTRACTIONS=y +CONFIG_FW_LOADER_PAGED_BUF=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_FW_LOADER_USER_HELPER is not set +CONFIG_FW_LOADER_COMPRESS=y +CONFIG_FW_LOADER_COMPRESS_XZ=y +CONFIG_FW_LOADER_COMPRESS_ZSTD=y +CONFIG_FW_CACHE=y +# CONFIG_FW_UPLOAD is not set +# end of Firmware loader + +CONFIG_WANT_DEV_COREDUMP=y +CONFIG_ALLOW_DEV_COREDUMP=y +CONFIG_DEV_COREDUMP=y +# CONFIG_DEBUG_DRIVER is not set +CONFIG_DEBUG_DEVRES=y +# CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set +# CONFIG_TEST_ASYNC_DRIVER_PROBE is not set +CONFIG_GENERIC_CPU_DEVICES=y +CONFIG_GENERIC_CPU_AUTOPROBE=y +CONFIG_GENERIC_CPU_VULNERABILITIES=y +CONFIG_REGMAP=y +CONFIG_REGMAP_I2C=y +CONFIG_REGMAP_SPI=y +CONFIG_REGMAP_SPMI=y +CONFIG_REGMAP_MMIO=y +CONFIG_REGMAP_IRQ=y +CONFIG_DMA_SHARED_BUFFER=y +# CONFIG_DMA_FENCE_TRACE is not set +CONFIG_GENERIC_ARCH_TOPOLOGY=y +# CONFIG_FW_DEVLINK_SYNC_STATE_TIMEOUT is not set +# end of Generic Driver Options + +# +# Bus devices +# +# CONFIG_MOXTET is not set +# CONFIG_VEXPRESS_CONFIG is not set +# CONFIG_MHI_BUS is not set +# CONFIG_MHI_BUS_EP is not set +# end of Bus devices + +# CONFIG_CACHEMAINT_FOR_HOTPLUG is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y + +# +# Firmware Drivers +# + +# +# ARM System Control and Management Interface Protocol +# +# CONFIG_ARM_SCMI_PROTOCOL is not set +# end of ARM System Control and Management Interface Protocol + +# CONFIG_ARM_SDE_INTERFACE is not set +# CONFIG_FIRMWARE_MEMMAP is not set +CONFIG_DMIID=y +CONFIG_DMI_SYSFS=y +# CONFIG_FW_CFG_SYSFS is not set +# CONFIG_SYSFB_SIMPLEFB is not set +# CONFIG_ARM_FFA_TRANSPORT is not set +CONFIG_FW_CS_DSP=m +# CONFIG_GOOGLE_FIRMWARE is not set + +# +# EFI (Extensible Firmware Interface) Support +# +CONFIG_EFI_ESRT=y +CONFIG_EFI_VARS_PSTORE=y +CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE=y +CONFIG_EFI_PARAMS_FROM_FDT=y +CONFIG_EFI_RUNTIME_WRAPPERS=y +CONFIG_EFI_GENERIC_STUB=y +# CONFIG_EFI_ZBOOT is not set +# CONFIG_EFI_ARMSTUB_DTB_LOADER is not set +CONFIG_EFI_BOOTLOADER_CONTROL=y +# CONFIG_EFI_CAPSULE_LOADER is not set +# CONFIG_EFI_TEST is not set +# CONFIG_RESET_ATTACK_MITIGATION is not set +# CONFIG_EFI_DISABLE_PCI_DMA is not set +CONFIG_EFI_EARLYCON=y +# CONFIG_EFI_DISABLE_RUNTIME is not set +# CONFIG_EFI_COCO_SECRET is not set +# CONFIG_OVMF_DEBUG_LOG is not set +# end of EFI (Extensible Firmware Interface) Support + +CONFIG_ARM_PSCI_FW=y +# CONFIG_ARM_PSCI_CHECKER is not set + +# +# Qualcomm firmware drivers +# +# end of Qualcomm firmware drivers + +CONFIG_HAVE_ARM_SMCCC=y +CONFIG_HAVE_ARM_SMCCC_DISCOVERY=y +# CONFIG_ARM_SMCCC_SOC_ID is not set + +# +# Tegra firmware driver +# +# end of Tegra firmware driver +# end of Firmware Drivers + +# CONFIG_FWCTL is not set +CONFIG_GNSS=m +CONFIG_GNSS_SERIAL=m +CONFIG_GNSS_MTK_SERIAL=m +CONFIG_GNSS_SIRF_SERIAL=m +CONFIG_GNSS_UBX_SERIAL=m +CONFIG_GNSS_USB=m +CONFIG_MTD=y +# CONFIG_MTD_TESTS is not set + +# +# Partition parsers +# +# CONFIG_MTD_CMDLINE_PARTS is not set +CONFIG_MTD_OF_PARTS=m +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_REDBOOT_PARTS is not set +# end of Partition parsers + +# +# User Modules And Translation Layers +# +CONFIG_MTD_BLKDEVS=m +CONFIG_MTD_BLOCK=m +# CONFIG_MTD_BLOCK_RO is not set + +# +# Note that in some cases UBI block is preferred. See MTD_UBI_BLOCK. +# +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_SM_FTL is not set +# CONFIG_MTD_OOPS is not set +# CONFIG_MTD_SWAP is not set +# CONFIG_MTD_PARTITIONED_MASTER is not set + +# +# RAM/ROM/Flash chip drivers +# +CONFIG_MTD_CFI=m +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_GEN_PROBE=m +# CONFIG_MTD_CFI_ADV_OPTIONS is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +CONFIG_MTD_CFI_INTELEXT=m +CONFIG_MTD_CFI_AMDSTD=m +CONFIG_MTD_CFI_STAA=m +CONFIG_MTD_CFI_UTIL=m +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set +# end of RAM/ROM/Flash chip drivers + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PHYSMAP is not set +# CONFIG_MTD_PLATRAM is not set +# end of Mapping drivers for chip access + +# +# Self-contained MTD device drivers +# +# CONFIG_MTD_PMC551 is not set +# CONFIG_MTD_DATAFLASH is not set +# CONFIG_MTD_MCHP23K256 is not set +# CONFIG_MTD_MCHP48L640 is not set +# CONFIG_MTD_SST25L is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOCG3 is not set +# end of Self-contained MTD device drivers + +# +# NAND +# +# CONFIG_MTD_ONENAND is not set +# CONFIG_MTD_RAW_NAND is not set +# CONFIG_MTD_SPI_NAND is not set + +# +# ECC engine support +# +# CONFIG_MTD_NAND_ECC_SW_HAMMING is not set +# CONFIG_MTD_NAND_ECC_SW_BCH is not set +# CONFIG_MTD_NAND_ECC_MXIC is not set +# end of ECC engine support +# end of NAND + +# +# LPDDR & LPDDR2 PCM memory drivers +# +# CONFIG_MTD_LPDDR is not set +# end of LPDDR & LPDDR2 PCM memory drivers + +CONFIG_MTD_SPI_NOR=m +CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=y +# CONFIG_MTD_SPI_NOR_SWP_DISABLE is not set +# CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE is not set +CONFIG_MTD_SPI_NOR_SWP_KEEP=y +# CONFIG_MTD_UBI is not set +# CONFIG_MTD_HYPERBUS is not set +CONFIG_DTC=y +CONFIG_OF=y +# CONFIG_OF_UNITTEST is not set +CONFIG_OF_FLATTREE=y +CONFIG_OF_EARLY_FLATTREE=y +CONFIG_OF_KOBJ=y +CONFIG_OF_DYNAMIC=y +CONFIG_OF_ADDRESS=y +CONFIG_OF_IRQ=y +CONFIG_OF_RESERVED_MEM=y +CONFIG_OF_RESOLVE=y +CONFIG_OF_OVERLAY=y +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +CONFIG_BLK_DEV_NULL_BLK=m +# CONFIG_BLK_DEV_RUST_NULL is not set +CONFIG_CDROM=y +# CONFIG_BLK_DEV_PCIESSD_MTIP32XX is not set +CONFIG_ZRAM=m +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_LZ4HC=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_DEFLATE=y +CONFIG_ZRAM_BACKEND_842=y +CONFIG_ZRAM_BACKEND_LZO=y +CONFIG_ZRAM_DEF_COMP_LZORLE=y +# CONFIG_ZRAM_DEF_COMP_LZO is not set +# CONFIG_ZRAM_DEF_COMP_LZ4 is not set +# CONFIG_ZRAM_DEF_COMP_LZ4HC is not set +# CONFIG_ZRAM_DEF_COMP_ZSTD is not set +# CONFIG_ZRAM_DEF_COMP_DEFLATE is not set +# CONFIG_ZRAM_DEF_COMP_842 is not set +CONFIG_ZRAM_DEF_COMP="lzo-rle" +CONFIG_ZRAM_WRITEBACK=y +# CONFIG_ZRAM_TRACK_ENTRY_ACTIME is not set +# CONFIG_ZRAM_MEMORY_TRACKING is not set +CONFIG_ZRAM_MULTI_COMP=y +CONFIG_BLK_DEV_LOOP=m +CONFIG_BLK_DEV_LOOP_MIN_COUNT=8 +CONFIG_BLK_DEV_DRBD=m +# CONFIG_DRBD_FAULT_INJECTION is not set +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_RAM=m +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_ATA_OVER_ETH=m +CONFIG_VIRTIO_BLK=y +CONFIG_BLK_DEV_RBD=m +# CONFIG_BLK_DEV_UBLK is not set +# CONFIG_BLK_DEV_ZONED_LOOP is not set + +# +# NVME Support +# +CONFIG_NVME_CORE=y +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_VERBOSE_ERRORS=y +# CONFIG_NVME_HWMON is not set +# CONFIG_NVME_FC is not set +# CONFIG_NVME_TCP is not set +# CONFIG_NVME_HOST_AUTH is not set +CONFIG_NVME_APPLE=y +# CONFIG_NVME_TARGET is not set +# end of NVME Support + +# +# Misc devices +# +# CONFIG_AD525X_DPOT is not set +# CONFIG_DUMMY_IRQ is not set +# CONFIG_PHANTOM is not set +# CONFIG_RPMB is not set +# CONFIG_TI_FPC202 is not set +CONFIG_TIFM_CORE=y +# CONFIG_TIFM_7XX1 is not set +# CONFIG_ICS932S401 is not set +CONFIG_ENCLOSURE_SERVICES=m +# CONFIG_HI6421V600_IRQ is not set +# CONFIG_HP_ILO is not set +# CONFIG_APDS9802ALS is not set +# CONFIG_ISL29003 is not set +# CONFIG_ISL29020 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_SENSORS_BH1770 is not set +# CONFIG_SENSORS_APDS990X is not set +# CONFIG_HMC6352 is not set +# CONFIG_DS1682 is not set +# CONFIG_LATTICE_ECP3_CONFIG is not set +# CONFIG_SRAM is not set +# CONFIG_DW_XDATA_PCIE is not set +# CONFIG_PCI_ENDPOINT_TEST is not set +# CONFIG_XILINX_SDFEC is not set +# CONFIG_HISI_HIKEY_USB is not set +# CONFIG_OPEN_DICE is not set +CONFIG_NTSYNC=y +# CONFIG_VCPU_STALL_DETECTOR is not set +# CONFIG_NSM is not set +# CONFIG_MCHP_LAN966X_PCI is not set +# CONFIG_C2PORT is not set + +# +# EEPROM support +# +CONFIG_EEPROM_AT24=m +# CONFIG_EEPROM_AT25 is not set +# CONFIG_EEPROM_MAX6875 is not set +CONFIG_EEPROM_93CX6=m +# CONFIG_EEPROM_93XX46 is not set +# CONFIG_EEPROM_IDT_89HPESX is not set +# CONFIG_EEPROM_EE1004 is not set +# CONFIG_EEPROM_M24LR is not set +# end of EEPROM support + +CONFIG_CB710_CORE=y +# CONFIG_CB710_DEBUG is not set +CONFIG_CB710_DEBUG_ASSUMPTIONS=y +# CONFIG_SENSORS_LIS3_SPI is not set +# CONFIG_SENSORS_LIS3_I2C is not set +# CONFIG_ALTERA_STAPL is not set +# CONFIG_VMWARE_VMCI is not set +# CONFIG_GENWQE is not set +# CONFIG_BCM_VK is not set +# CONFIG_MISC_ALCOR_PCI is not set +# CONFIG_MISC_RTSX_PCI is not set +# CONFIG_MISC_RTSX_USB is not set +CONFIG_UACCE=m +# CONFIG_PVPANIC is not set +# CONFIG_GP_PCI1XXXX is not set +# CONFIG_KEBA_CP500 is not set +# CONFIG_AMD_SBRMI_I2C is not set +# CONFIG_MISC_RP1 is not set +# end of Misc devices + +# +# SCSI device support +# +CONFIG_SCSI_MOD=y +CONFIG_RAID_ATTRS=m +CONFIG_SCSI_COMMON=y +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +CONFIG_SCSI_NETLINK=y +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_ST=m +CONFIG_BLK_DEV_SR=y +CONFIG_CHR_DEV_SG=y +CONFIG_BLK_DEV_BSG=y +CONFIG_CHR_DEV_SCH=m +CONFIG_SCSI_ENCLOSURE=m +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y + +# +# SCSI Transports +# +CONFIG_SCSI_SPI_ATTRS=m +CONFIG_SCSI_FC_ATTRS=m +CONFIG_SCSI_ISCSI_ATTRS=m +CONFIG_SCSI_SAS_ATTRS=m +CONFIG_SCSI_SAS_LIBSAS=m +CONFIG_SCSI_SAS_HOST_SMP=y +CONFIG_SCSI_SRP_ATTRS=m +# end of SCSI Transports + +CONFIG_SCSI_LOWLEVEL=y +CONFIG_ISCSI_TCP=m +CONFIG_ISCSI_BOOT_SYSFS=m +# CONFIG_SCSI_CXGB3_ISCSI is not set +# CONFIG_SCSI_CXGB4_ISCSI is not set +# CONFIG_SCSI_BNX2_ISCSI is not set +# CONFIG_BE2ISCSI is not set +# CONFIG_BLK_DEV_3W_XXXX_RAID is not set +# CONFIG_SCSI_HPSA is not set +# CONFIG_SCSI_3W_9XXX is not set +# CONFIG_SCSI_3W_SAS is not set +# CONFIG_SCSI_ACARD is not set +# CONFIG_SCSI_AACRAID is not set +# CONFIG_SCSI_AIC7XXX is not set +# CONFIG_SCSI_AIC79XX is not set +# CONFIG_SCSI_AIC94XX is not set +# CONFIG_SCSI_MVSAS is not set +# CONFIG_SCSI_MVUMI is not set +# CONFIG_SCSI_ADVANSYS is not set +# CONFIG_SCSI_ARCMSR is not set +# CONFIG_SCSI_ESAS2R is not set +# CONFIG_MEGARAID_NEWGEN is not set +# CONFIG_MEGARAID_LEGACY is not set +# CONFIG_MEGARAID_SAS is not set +# CONFIG_SCSI_MPT3SAS is not set +# CONFIG_SCSI_MPT2SAS is not set +# CONFIG_SCSI_MPI3MR is not set +# CONFIG_SCSI_SMARTPQI is not set +# CONFIG_SCSI_HPTIOP is not set +# CONFIG_SCSI_BUSLOGIC is not set +# CONFIG_SCSI_MYRB is not set +# CONFIG_SCSI_MYRS is not set +# CONFIG_LIBFC is not set +# CONFIG_SCSI_SNIC is not set +# CONFIG_SCSI_DMX3191D is not set +# CONFIG_SCSI_FDOMAIN_PCI is not set +# CONFIG_SCSI_IPS is not set +# CONFIG_SCSI_INITIO is not set +# CONFIG_SCSI_INIA100 is not set +# CONFIG_SCSI_STEX is not set +# CONFIG_SCSI_SYM53C8XX_2 is not set +# CONFIG_SCSI_IPR is not set +# CONFIG_SCSI_QLOGIC_1280 is not set +# CONFIG_SCSI_QLA_FC is not set +# CONFIG_SCSI_QLA_ISCSI is not set +# CONFIG_SCSI_LPFC is not set +# CONFIG_SCSI_EFCT is not set +# CONFIG_SCSI_DC395x is not set +# CONFIG_SCSI_AM53C974 is not set +# CONFIG_SCSI_WD719X is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_PMCRAID is not set +# CONFIG_SCSI_PM8001 is not set +# CONFIG_SCSI_BFA_FC is not set +CONFIG_SCSI_VIRTIO=y +# CONFIG_SCSI_CHELSIO_FCOE is not set +CONFIG_SCSI_DH=y +CONFIG_SCSI_DH_RDAC=m +CONFIG_SCSI_DH_HP_SW=m +CONFIG_SCSI_DH_EMC=m +CONFIG_SCSI_DH_ALUA=m +# end of SCSI device support + +# CONFIG_ATA is not set +CONFIG_MD=y +CONFIG_BLK_DEV_MD=y +CONFIG_MD_BITMAP=y +# CONFIG_MD_LLBITMAP is not set +CONFIG_MD_AUTODETECT=y +CONFIG_MD_BITMAP_FILE=y +# CONFIG_MD_LINEAR is not set +CONFIG_MD_RAID0=m +CONFIG_MD_RAID1=m +CONFIG_MD_RAID10=m +CONFIG_MD_RAID456=m +CONFIG_MD_CLUSTER=m +CONFIG_BCACHE=m +# CONFIG_BCACHE_DEBUG is not set +# CONFIG_BCACHE_ASYNC_REGISTRATION is not set +CONFIG_BLK_DEV_DM_BUILTIN=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_DEBUG=y +CONFIG_DM_BUFIO=y +# CONFIG_DM_DEBUG_BLOCK_MANAGER_LOCKING is not set +CONFIG_DM_BIO_PRISON=m +CONFIG_DM_PERSISTENT_DATA=m +# CONFIG_DM_UNSTRIPED is not set +CONFIG_DM_CRYPT=m +CONFIG_DM_SNAPSHOT=y +CONFIG_DM_THIN_PROVISIONING=m +CONFIG_DM_CACHE=m +CONFIG_DM_CACHE_SMQ=m +CONFIG_DM_WRITECACHE=m +# CONFIG_DM_EBS is not set +# CONFIG_DM_ERA is not set +# CONFIG_DM_CLONE is not set +CONFIG_DM_MIRROR=y +CONFIG_DM_LOG_USERSPACE=m +CONFIG_DM_RAID=m +CONFIG_DM_ZERO=y +CONFIG_DM_MULTIPATH=m +CONFIG_DM_MULTIPATH_QL=m +CONFIG_DM_MULTIPATH_ST=m +# CONFIG_DM_MULTIPATH_HST is not set +# CONFIG_DM_MULTIPATH_IOA is not set +CONFIG_DM_DELAY=m +CONFIG_DM_DUST=m +CONFIG_DM_INIT=y +CONFIG_DM_UEVENT=y +CONFIG_DM_FLAKEY=m +CONFIG_DM_VERITY=m +# CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG is not set +CONFIG_DM_VERITY_FEC=y +CONFIG_DM_SWITCH=m +CONFIG_DM_LOG_WRITES=m +CONFIG_DM_INTEGRITY=m +CONFIG_DM_ZONED=m +CONFIG_DM_AUDIT=y +# CONFIG_DM_VDO is not set +CONFIG_TARGET_CORE=m +CONFIG_TCM_IBLOCK=m +CONFIG_TCM_FILEIO=m +CONFIG_TCM_PSCSI=m +CONFIG_LOOPBACK_TARGET=m +CONFIG_ISCSI_TARGET=m +# CONFIG_REMOTE_TARGET is not set +# CONFIG_FUSION is not set + +# +# IEEE 1394 (FireWire) support +# +# CONFIG_FIREWIRE is not set +# CONFIG_FIREWIRE_NOSY is not set +# end of IEEE 1394 (FireWire) support + +CONFIG_NETDEVICES=y +CONFIG_MII=m +CONFIG_NET_CORE=y +CONFIG_BONDING=m +CONFIG_DUMMY=m +CONFIG_WIREGUARD=m +# CONFIG_WIREGUARD_DEBUG is not set +# CONFIG_OVPN is not set +CONFIG_EQUALIZER=m +CONFIG_NET_FC=y +CONFIG_IFB=m +CONFIG_NET_TEAM=m +CONFIG_NET_TEAM_MODE_BROADCAST=m +CONFIG_NET_TEAM_MODE_ROUNDROBIN=m +CONFIG_NET_TEAM_MODE_RANDOM=m +CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m +CONFIG_NET_TEAM_MODE_LOADBALANCE=m +CONFIG_MACVLAN=m +CONFIG_MACVTAP=m +CONFIG_IPVLAN_L3S=y +CONFIG_IPVLAN=m +CONFIG_IPVTAP=m +CONFIG_VXLAN=m +CONFIG_GENEVE=m +# CONFIG_BAREUDP is not set +# CONFIG_GTP is not set +# CONFIG_PFCP is not set +# CONFIG_AMT is not set +# CONFIG_MACSEC is not set +CONFIG_NETCONSOLE=m +CONFIG_NETCONSOLE_DYNAMIC=y +# CONFIG_NETCONSOLE_EXTENDED_LOG is not set +CONFIG_NETPOLL=y +CONFIG_NET_POLL_CONTROLLER=y +CONFIG_TUN=m +CONFIG_TAP=m +# CONFIG_TUN_VNET_CROSS_LE is not set +CONFIG_VETH=m +CONFIG_VIRTIO_NET=m +CONFIG_NLMON=m +# CONFIG_NETKIT is not set +CONFIG_NET_VRF=m +# CONFIG_VSOCKMON is not set +# CONFIG_ARCNET is not set +# CONFIG_ATM_DRIVERS is not set + +# +# Distributed Switch Architecture drivers +# +CONFIG_B53=m +# CONFIG_B53_SPI_DRIVER is not set +# CONFIG_B53_MDIO_DRIVER is not set +# CONFIG_B53_MMAP_DRIVER is not set +# CONFIG_B53_SRAB_DRIVER is not set +# CONFIG_B53_SERDES is not set +CONFIG_NET_DSA_BCM_SF2=m +# CONFIG_NET_DSA_LOOP is not set +# CONFIG_NET_DSA_HIRSCHMANN_HELLCREEK is not set +# CONFIG_NET_DSA_LANTIQ_GSWIP is not set +# CONFIG_NET_DSA_MXL_GSW1XX is not set +# CONFIG_NET_DSA_MT7530 is not set +CONFIG_NET_DSA_MV88E6060=m +# CONFIG_NET_DSA_MICROCHIP_KSZ_COMMON is not set +CONFIG_NET_DSA_MV88E6XXX=m +CONFIG_NET_DSA_MV88E6XXX_PTP=y +CONFIG_NET_DSA_MV88E6XXX_LEDS=y +# CONFIG_NET_DSA_MXL862 is not set +# CONFIG_NET_DSA_AR9331 is not set +CONFIG_NET_DSA_QCA8K=m +# CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT is not set +# CONFIG_NET_DSA_SJA1105 is not set +# CONFIG_NET_DSA_XRS700X_I2C is not set +# CONFIG_NET_DSA_XRS700X_MDIO is not set +# CONFIG_NET_DSA_REALTEK is not set +# CONFIG_NET_DSA_KS8995 is not set +# CONFIG_NET_DSA_SMSC_LAN9303_I2C is not set +# CONFIG_NET_DSA_SMSC_LAN9303_MDIO is not set +# CONFIG_NET_DSA_VITESSE_VSC73XX_SPI is not set +# CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM is not set +# CONFIG_NET_DSA_YT921X is not set +# end of Distributed Switch Architecture drivers + +CONFIG_ETHERNET=y +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_AGERE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_ALTERA_TSE is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AMD is not set +CONFIG_NET_VENDOR_AQUANTIA=y +CONFIG_AQTION=m +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +CONFIG_NET_VENDOR_BROADCOM=y +# CONFIG_B44 is not set +# CONFIG_BCMGENET is not set +# CONFIG_BNX2 is not set +# CONFIG_CNIC is not set +CONFIG_TIGON3=m +CONFIG_TIGON3_HWMON=y +# CONFIG_BNX2X is not set +# CONFIG_SYSTEMPORT is not set +# CONFIG_BNXT is not set +# CONFIG_BNGE is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CISCO is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_JME is not set +CONFIG_NET_VENDOR_ADI=y +# CONFIG_ADIN1110 is not set +# CONFIG_NET_VENDOR_LITEX is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +CONFIG_NET_VENDOR_META=y +# CONFIG_FBNIC is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +CONFIG_NET_VENDOR_MUCSE=y +# CONFIG_MGBE is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_FEALNX is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_ETHOC is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RDC is not set +# CONFIG_NET_VENDOR_REALTEK is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_SUN is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_NET_VENDOR_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +# CONFIG_FDDI is not set +CONFIG_PHYLINK=m +CONFIG_PHYLIB=y +CONFIG_SWPHY=y +CONFIG_PHY_PACKAGE=y +CONFIG_LED_TRIGGER_PHY=y +CONFIG_PHYLIB_LEDS=y +CONFIG_FIXED_PHY=y +CONFIG_RUST_PHYLIB_ABSTRACTIONS=y +CONFIG_SFP=m + +# +# MII PHY device drivers +# +# CONFIG_AS21XXX_PHY is not set +# CONFIG_AIR_EN8811H_PHY is not set +CONFIG_AMD_PHY=m +# CONFIG_ADIN_PHY is not set +# CONFIG_ADIN1100_PHY is not set +# CONFIG_AMCC_QT2025_PHY is not set +# CONFIG_AQUANTIA_PHY is not set +CONFIG_AX88796B_PHY=m +# CONFIG_AX88796B_RUST_PHY is not set +CONFIG_BROADCOM_PHY=m +CONFIG_BCM54140_PHY=m +CONFIG_BCM7XXX_PHY=m +# CONFIG_BCM84881_PHY is not set +CONFIG_BCM87XX_PHY=m +CONFIG_BCM_NET_PHYLIB=m +CONFIG_BCM_NET_PHYPTP=m +CONFIG_CICADA_PHY=m +# CONFIG_CORTINA_PHY is not set +CONFIG_DAVICOM_PHY=m +CONFIG_ICPLUS_PHY=m +CONFIG_LXT_PHY=m +# CONFIG_INTEL_XWAY_PHY is not set +CONFIG_LSI_ET1011C_PHY=m +CONFIG_MARVELL_PHY=m +CONFIG_MARVELL_10G_PHY=m +# CONFIG_MARVELL_88Q2XXX_PHY is not set +# CONFIG_MARVELL_88X2222_PHY is not set +# CONFIG_MAXLINEAR_GPHY is not set +# CONFIG_MAXLINEAR_86110_PHY is not set +# CONFIG_MEDIATEK_GE_PHY is not set +CONFIG_MICREL_PHY=m +# CONFIG_MICROCHIP_T1S_PHY is not set +CONFIG_MICROCHIP_PHY=m +# CONFIG_MICROCHIP_T1_PHY is not set +# CONFIG_MICROSEMI_PHY is not set +# CONFIG_MOTORCOMM_PHY is not set +CONFIG_NATIONAL_PHY=m +# CONFIG_NXP_CBTX_PHY is not set +# CONFIG_NXP_C45_TJA11XX_PHY is not set +# CONFIG_NXP_TJA11XX_PHY is not set +# CONFIG_NCN26000_PHY is not set +CONFIG_QCOM_NET_PHYLIB=y +CONFIG_AT803X_PHY=y +# CONFIG_QCA83XX_PHY is not set +# CONFIG_QCA808X_PHY is not set +# CONFIG_QCA807X_PHY is not set +CONFIG_QSEMI_PHY=m +CONFIG_REALTEK_PHY=y +CONFIG_REALTEK_PHY_HWMON=y +# CONFIG_RENESAS_PHY is not set +CONFIG_ROCKCHIP_PHY=y +CONFIG_SMSC_PHY=m +CONFIG_STE10XP=m +# CONFIG_TERANETICS_PHY is not set +# CONFIG_DP83822_PHY is not set +# CONFIG_DP83TC811_PHY is not set +CONFIG_DP83848_PHY=m +CONFIG_DP83867_PHY=m +# CONFIG_DP83869_PHY is not set +# CONFIG_DP83TD510_PHY is not set +# CONFIG_DP83TG720_PHY is not set +CONFIG_VITESSE_PHY=m +# CONFIG_XILINX_GMII2RGMII is not set +# CONFIG_PSE_CONTROLLER is not set +CONFIG_CAN_DEV=m +CONFIG_CAN_VCAN=m +CONFIG_CAN_VXCAN=m +# CONFIG_CAN_NETLINK is not set +# CONFIG_CAN_DEBUG_DEVICES is not set +CONFIG_FWNODE_MDIO=y +CONFIG_OF_MDIO=y +CONFIG_MDIO_BITBANG=m +CONFIG_MDIO_BCM_UNIMAC=y +# CONFIG_MDIO_GPIO is not set +# CONFIG_MDIO_HISI_FEMAC is not set +CONFIG_MDIO_I2C=m +# CONFIG_MDIO_MVUSB is not set +# CONFIG_MDIO_MSCC_MIIM is not set +# CONFIG_MDIO_OCTEON is not set +# CONFIG_MDIO_IPQ4019 is not set +# CONFIG_MDIO_IPQ8064 is not set +# CONFIG_MDIO_THUNDER is not set + +# +# MDIO Multiplexers +# +CONFIG_MDIO_BUS_MUX=m +CONFIG_MDIO_BUS_MUX_GPIO=m +# CONFIG_MDIO_BUS_MUX_MULTIPLEXER is not set +CONFIG_MDIO_BUS_MUX_MMIOREG=m + +# +# PCS device drivers +# +# CONFIG_PCS_XPCS is not set +# end of PCS device drivers + +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOATM=m +CONFIG_PPPOE=m +# CONFIG_PPPOE_HASH_BITS_1 is not set +# CONFIG_PPPOE_HASH_BITS_2 is not set +CONFIG_PPPOE_HASH_BITS_4=y +# CONFIG_PPPOE_HASH_BITS_8 is not set +CONFIG_PPPOE_HASH_BITS=4 +CONFIG_PPTP=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_SLIP=m +CONFIG_SLHC=m +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_SMART=y +# CONFIG_SLIP_MODE_SLIP6 is not set +CONFIG_USB_NET_DRIVERS=y +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=m +CONFIG_USB_LAN78XX=m +CONFIG_USB_USBNET=m +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +CONFIG_USB_NET_HUAWEI_CDC_NCM=m +CONFIG_USB_NET_CDC_MBIM=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_SR9700=m +CONFIG_USB_NET_SR9800=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=m +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_CDC_SUBSET_ENABLE=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_BELKIN=y +CONFIG_USB_ARMLINUX=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +CONFIG_USB_NET_CX82310_ETH=m +CONFIG_USB_NET_KALMIA=m +CONFIG_USB_NET_QMI_WWAN=m +CONFIG_USB_HSO=m +CONFIG_USB_NET_INT51X1=m +CONFIG_USB_IPHETH=m +CONFIG_USB_SIERRA_NET=m +CONFIG_USB_VL600=m +CONFIG_USB_NET_CH9200=m +CONFIG_USB_NET_AQC111=m +CONFIG_USB_RTL8153_ECM=m +CONFIG_WLAN=y +# CONFIG_WLAN_VENDOR_ADMTEK is not set +CONFIG_ATH_COMMON=m +CONFIG_WLAN_VENDOR_ATH=y +# CONFIG_ATH_DEBUG is not set +# CONFIG_ATH5K is not set +# CONFIG_ATH5K_PCI is not set +CONFIG_ATH9K_HW=m +CONFIG_ATH9K_COMMON=m +CONFIG_ATH9K_BTCOEX_SUPPORT=y +# CONFIG_ATH9K is not set +CONFIG_ATH9K_HTC=m +# CONFIG_ATH9K_HTC_DEBUGFS is not set +CONFIG_CARL9170=m +CONFIG_CARL9170_LEDS=y +# CONFIG_CARL9170_DEBUGFS is not set +CONFIG_CARL9170_WPC=y +# CONFIG_CARL9170_HWRNG is not set +CONFIG_ATH6KL=m +# CONFIG_ATH6KL_SDIO is not set +CONFIG_ATH6KL_USB=m +# CONFIG_ATH6KL_DEBUG is not set +# CONFIG_ATH6KL_TRACING is not set +CONFIG_AR5523=m +# CONFIG_WIL6210 is not set +CONFIG_ATH10K=m +CONFIG_ATH10K_CE=y +# CONFIG_ATH10K_PCI is not set +# CONFIG_ATH10K_SDIO is not set +CONFIG_ATH10K_USB=m +# CONFIG_ATH10K_DEBUG is not set +CONFIG_ATH10K_DEBUGFS=y +CONFIG_ATH10K_LEDS=y +# CONFIG_ATH10K_SPECTRAL is not set +# CONFIG_ATH10K_TRACING is not set +# CONFIG_WCN36XX is not set +# CONFIG_ATH11K is not set +# CONFIG_ATH12K is not set +CONFIG_WLAN_VENDOR_ATMEL=y +CONFIG_AT76C50X_USB=m +CONFIG_WLAN_VENDOR_BROADCOM=y +# CONFIG_B43 is not set +# CONFIG_B43LEGACY is not set +CONFIG_BRCMUTIL=m +# CONFIG_BRCMSMAC is not set +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_PROTO_BCDC=y +CONFIG_BRCMFMAC_PROTO_MSGBUF=y +# CONFIG_BRCMFMAC_SDIO is not set +CONFIG_BRCMFMAC_USB=y +CONFIG_BRCMFMAC_PCIE=y +# CONFIG_BRCM_TRACING is not set +# CONFIG_BRCMDBG is not set +CONFIG_WLAN_VENDOR_INTEL=y +# CONFIG_IPW2100 is not set +# CONFIG_IPW2200 is not set +# CONFIG_IWL4965 is not set +# CONFIG_IWL3945 is not set +# CONFIG_IWLWIFI is not set +CONFIG_WLAN_VENDOR_INTERSIL=y +CONFIG_P54_COMMON=m +CONFIG_P54_USB=m +# CONFIG_P54_PCI is not set +# CONFIG_P54_SPI is not set +CONFIG_P54_LEDS=y +CONFIG_WLAN_VENDOR_MARVELL=y +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_USB=m +# CONFIG_LIBERTAS_SDIO is not set +# CONFIG_LIBERTAS_SPI is not set +# CONFIG_LIBERTAS_DEBUG is not set +CONFIG_LIBERTAS_MESH=y +CONFIG_LIBERTAS_THINFIRM=m +# CONFIG_LIBERTAS_THINFIRM_DEBUG is not set +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_MWIFIEX=m +# CONFIG_MWIFIEX_SDIO is not set +# CONFIG_MWIFIEX_PCIE is not set +CONFIG_MWIFIEX_USB=m +# CONFIG_MWL8K is not set +CONFIG_WLAN_VENDOR_MEDIATEK=y +CONFIG_MT7601U=m +CONFIG_MT76_CORE=m +CONFIG_MT76_LEDS=y +CONFIG_MT76_USB=m +CONFIG_MT76x02_LIB=m +CONFIG_MT76x02_USB=m +CONFIG_MT76_CONNAC_LIB=m +CONFIG_MT76x0_COMMON=m +CONFIG_MT76x0U=m +# CONFIG_MT76x0E is not set +CONFIG_MT76x2_COMMON=m +# CONFIG_MT76x2E is not set +CONFIG_MT76x2U=m +# CONFIG_MT7603E is not set +CONFIG_MT7615_COMMON=m +# CONFIG_MT7615E is not set +CONFIG_MT7663_USB_SDIO_COMMON=m +CONFIG_MT7663U=m +# CONFIG_MT7663S is not set +# CONFIG_MT7915E is not set +# CONFIG_MT7921E is not set +# CONFIG_MT7921S is not set +# CONFIG_MT7921U is not set +# CONFIG_MT7996E is not set +# CONFIG_MT7925E is not set +# CONFIG_MT7925U is not set +# CONFIG_WLAN_VENDOR_MICROCHIP is not set +# CONFIG_WLAN_VENDOR_PURELIFI is not set +CONFIG_WLAN_VENDOR_RALINK=y +CONFIG_RT2X00=m +# CONFIG_RT2400PCI is not set +# CONFIG_RT2500PCI is not set +# CONFIG_RT61PCI is not set +# CONFIG_RT2800PCI is not set +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT33XX=y +CONFIG_RT2800USB_RT35XX=y +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_RT2800USB_UNKNOWN=y +CONFIG_RT2800_LIB=m +CONFIG_RT2X00_LIB_USB=m +CONFIG_RT2X00_LIB=m +CONFIG_RT2X00_LIB_FIRMWARE=y +CONFIG_RT2X00_LIB_CRYPTO=y +CONFIG_RT2X00_LIB_LEDS=y +CONFIG_RT2X00_LIB_DEBUGFS=y +# CONFIG_RT2X00_DEBUG is not set +CONFIG_WLAN_VENDOR_REALTEK=y +# CONFIG_RTL8180 is not set +CONFIG_RTL8187=m +CONFIG_RTL8187_LEDS=y +CONFIG_RTL_CARDS=m +# CONFIG_RTL8192CE is not set +# CONFIG_RTL8192SE is not set +# CONFIG_RTL8192DE is not set +# CONFIG_RTL8723AE is not set +# CONFIG_RTL8723BE is not set +# CONFIG_RTL8188EE is not set +# CONFIG_RTL8192EE is not set +# CONFIG_RTL8821AE is not set +CONFIG_RTL8192CU=m +# CONFIG_RTL8192DU is not set +CONFIG_RTLWIFI=m +CONFIG_RTLWIFI_USB=m +CONFIG_RTLWIFI_DEBUG=y +CONFIG_RTL8192C_COMMON=m +CONFIG_RTL8XXXU=m +# CONFIG_RTL8XXXU_UNTESTED is not set +# CONFIG_RTW88 is not set +# CONFIG_RTW89 is not set +CONFIG_WLAN_VENDOR_RSI=y +CONFIG_RSI_91X=m +CONFIG_RSI_DEBUGFS=y +# CONFIG_RSI_SDIO is not set +CONFIG_RSI_USB=m +CONFIG_RSI_COEX=y +CONFIG_WLAN_VENDOR_SILABS=y +# CONFIG_WFX is not set +# CONFIG_WLAN_VENDOR_ST is not set +# CONFIG_WLAN_VENDOR_TI is not set +CONFIG_WLAN_VENDOR_ZYDAS=y +CONFIG_ZD1211RW=m +# CONFIG_ZD1211RW_DEBUG is not set +# CONFIG_WLAN_VENDOR_QUANTENNA is not set +# CONFIG_MAC80211_HWSIM is not set +# CONFIG_VIRT_WIFI is not set +# CONFIG_WAN is not set +CONFIG_IEEE802154_DRIVERS=m +CONFIG_IEEE802154_FAKELB=m +# CONFIG_IEEE802154_AT86RF230 is not set +# CONFIG_IEEE802154_MRF24J40 is not set +# CONFIG_IEEE802154_CC2520 is not set +CONFIG_IEEE802154_ATUSB=m +# CONFIG_IEEE802154_ADF7242 is not set +# CONFIG_IEEE802154_CA8210 is not set +# CONFIG_IEEE802154_MCR20A is not set +# CONFIG_IEEE802154_HWSIM is not set + +# +# Wireless WAN +# +# CONFIG_WWAN is not set +# end of Wireless WAN + +# CONFIG_VMXNET3 is not set +# CONFIG_NETDEVSIM is not set +CONFIG_NET_FAILOVER=m + +# +# Input device support +# +CONFIG_INPUT=y +CONFIG_INPUT_LEDS=y +CONFIG_INPUT_FF_MEMLESS=m +# CONFIG_INPUT_SPARSEKMAP is not set +# CONFIG_INPUT_MATRIXKMAP is not set + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=y +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 +CONFIG_INPUT_JOYDEV=m +CONFIG_INPUT_EVDEV=y + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ADC is not set +# CONFIG_KEYBOARD_ADP5588 is not set +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_QT1050 is not set +# CONFIG_KEYBOARD_QT1070 is not set +# CONFIG_KEYBOARD_QT2160 is not set +# CONFIG_KEYBOARD_DLINK_DIR685 is not set +# CONFIG_KEYBOARD_LKKBD is not set +CONFIG_KEYBOARD_GPIO=m +CONFIG_KEYBOARD_GPIO_POLLED=m +# CONFIG_KEYBOARD_TCA8418 is not set +# CONFIG_KEYBOARD_MATRIX is not set +# CONFIG_KEYBOARD_CHARLIEPLEX is not set +# CONFIG_KEYBOARD_LM8323 is not set +# CONFIG_KEYBOARD_LM8333 is not set +# CONFIG_KEYBOARD_MAX7359 is not set +# CONFIG_KEYBOARD_MPR121 is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_OPENCORES is not set +# CONFIG_KEYBOARD_PINEPHONE is not set +# CONFIG_KEYBOARD_SAMSUNG is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_OMAP4 is not set +# CONFIG_KEYBOARD_TM2_TOUCHKEY is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_CAP11XX is not set +# CONFIG_KEYBOARD_BCM is not set +# CONFIG_KEYBOARD_CYPRESS_SF is not set +CONFIG_INPUT_MOUSE=y +# CONFIG_MOUSE_PS2 is not set +# CONFIG_MOUSE_SERIAL is not set +CONFIG_MOUSE_APPLETOUCH=m +CONFIG_MOUSE_BCM5974=m +# CONFIG_MOUSE_CYAPA is not set +# CONFIG_MOUSE_ELAN_I2C is not set +# CONFIG_MOUSE_VSXXXAA is not set +# CONFIG_MOUSE_GPIO is not set +# CONFIG_MOUSE_SYNAPTICS_I2C is not set +CONFIG_MOUSE_SYNAPTICS_USB=m +CONFIG_INPUT_JOYSTICK=y +# CONFIG_JOYSTICK_ANALOG is not set +# CONFIG_JOYSTICK_A3D is not set +# CONFIG_JOYSTICK_ADC is not set +# CONFIG_JOYSTICK_ADI is not set +# CONFIG_JOYSTICK_COBRA is not set +# CONFIG_JOYSTICK_GF2K is not set +# CONFIG_JOYSTICK_GRIP is not set +# CONFIG_JOYSTICK_GRIP_MP is not set +# CONFIG_JOYSTICK_GUILLEMOT is not set +# CONFIG_JOYSTICK_INTERACT is not set +# CONFIG_JOYSTICK_SIDEWINDER is not set +# CONFIG_JOYSTICK_TMDC is not set +CONFIG_JOYSTICK_IFORCE=m +CONFIG_JOYSTICK_IFORCE_USB=m +# CONFIG_JOYSTICK_WARRIOR is not set +# CONFIG_JOYSTICK_MAGELLAN is not set +# CONFIG_JOYSTICK_SPACEORB is not set +# CONFIG_JOYSTICK_SPACEBALL is not set +# CONFIG_JOYSTICK_STINGER is not set +# CONFIG_JOYSTICK_TWIDJOY is not set +# CONFIG_JOYSTICK_ZHENHUA is not set +# CONFIG_JOYSTICK_AS5011 is not set +# CONFIG_JOYSTICK_JOYDUMP is not set +CONFIG_JOYSTICK_XPAD=m +CONFIG_JOYSTICK_XPAD_FF=y +CONFIG_JOYSTICK_XPAD_LEDS=y +# CONFIG_JOYSTICK_PSXPAD_SPI is not set +# CONFIG_JOYSTICK_PXRC is not set +# CONFIG_JOYSTICK_QWIIC is not set +# CONFIG_JOYSTICK_FSIA6B is not set +# CONFIG_JOYSTICK_SENSEHAT is not set +# CONFIG_JOYSTICK_SEESAW is not set +CONFIG_INPUT_TABLET=y +CONFIG_TABLET_USB_ACECAD=m +CONFIG_TABLET_USB_AIPTEK=m +CONFIG_TABLET_USB_HANWANG=m +CONFIG_TABLET_USB_KBTAB=m +CONFIG_TABLET_USB_PEGASUS=m +# CONFIG_TABLET_SERIAL_WACOM4 is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_TOUCHSCREEN_ADS7846 is not set +# CONFIG_TOUCHSCREEN_AD7877 is not set +# CONFIG_TOUCHSCREEN_AD7879 is not set +# CONFIG_TOUCHSCREEN_ADC is not set +CONFIG_TOUCHSCREEN_APPLE_Z2=m +# CONFIG_TOUCHSCREEN_AR1021_I2C is not set +# CONFIG_TOUCHSCREEN_ATMEL_MXT is not set +# CONFIG_TOUCHSCREEN_AUO_PIXCIR is not set +# CONFIG_TOUCHSCREEN_BU21013 is not set +# CONFIG_TOUCHSCREEN_BU21029 is not set +# CONFIG_TOUCHSCREEN_CHIPONE_ICN8318 is not set +# CONFIG_TOUCHSCREEN_CY8CTMA140 is not set +# CONFIG_TOUCHSCREEN_CY8CTMG110 is not set +# CONFIG_TOUCHSCREEN_CYTTSP_CORE is not set +# CONFIG_TOUCHSCREEN_CYTTSP5 is not set +# CONFIG_TOUCHSCREEN_DYNAPRO is not set +# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set +# CONFIG_TOUCHSCREEN_EETI is not set +# CONFIG_TOUCHSCREEN_EGALAX is not set +# CONFIG_TOUCHSCREEN_EGALAX_SERIAL is not set +# CONFIG_TOUCHSCREEN_EXC3000 is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GOODIX is not set +# CONFIG_TOUCHSCREEN_GOODIX_BERLIN_I2C is not set +# CONFIG_TOUCHSCREEN_GOODIX_BERLIN_SPI is not set +# CONFIG_TOUCHSCREEN_HIDEEP is not set +# CONFIG_TOUCHSCREEN_HIMAX_HX852X is not set +# CONFIG_TOUCHSCREEN_HYCON_HY46XX is not set +# CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX is not set +# CONFIG_TOUCHSCREEN_HYNITRON_CST816X is not set +# CONFIG_TOUCHSCREEN_ILI210X is not set +# CONFIG_TOUCHSCREEN_ILITEK is not set +# CONFIG_TOUCHSCREEN_S6SY761 is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_EKTF2127 is not set +# CONFIG_TOUCHSCREEN_ELAN is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_WACOM_W8001 is not set +# CONFIG_TOUCHSCREEN_WACOM_I2C is not set +# CONFIG_TOUCHSCREEN_MAX11801 is not set +# CONFIG_TOUCHSCREEN_MMS114 is not set +# CONFIG_TOUCHSCREEN_MELFAS_MIP4 is not set +# CONFIG_TOUCHSCREEN_MSG2638 is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_NOVATEK_NVT_TS is not set +# CONFIG_TOUCHSCREEN_IMAGIS is not set +# CONFIG_TOUCHSCREEN_IMX6UL_TSC is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_EDT_FT5X06 is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_PIXCIR is not set +# CONFIG_TOUCHSCREEN_WDT87XX_I2C is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_TOUCHSCREEN_TSC_SERIO is not set +# CONFIG_TOUCHSCREEN_TSC2004 is not set +# CONFIG_TOUCHSCREEN_TSC2005 is not set +# CONFIG_TOUCHSCREEN_TSC2007 is not set +# CONFIG_TOUCHSCREEN_RM_TS is not set +# CONFIG_TOUCHSCREEN_SILEAD is not set +# CONFIG_TOUCHSCREEN_SIS_I2C is not set +# CONFIG_TOUCHSCREEN_ST1232 is not set +# CONFIG_TOUCHSCREEN_STMFTS is not set +# CONFIG_TOUCHSCREEN_SUR40 is not set +# CONFIG_TOUCHSCREEN_SURFACE3_SPI is not set +# CONFIG_TOUCHSCREEN_SX8654 is not set +# CONFIG_TOUCHSCREEN_TPS6507X is not set +# CONFIG_TOUCHSCREEN_ZET6223 is not set +# CONFIG_TOUCHSCREEN_ZFORCE is not set +# CONFIG_TOUCHSCREEN_COLIBRI_VF50 is not set +# CONFIG_TOUCHSCREEN_ROHM_BU21023 is not set +# CONFIG_TOUCHSCREEN_IQS5XX is not set +# CONFIG_TOUCHSCREEN_IQS7211 is not set +# CONFIG_TOUCHSCREEN_ZINITIX is not set +# CONFIG_TOUCHSCREEN_HIMAX_HX83112B is not set +CONFIG_INPUT_MISC=y +# CONFIG_INPUT_AD714X is not set +# CONFIG_INPUT_ATMEL_CAPTOUCH is not set +# CONFIG_INPUT_AW86927 is not set +# CONFIG_INPUT_BMA150 is not set +CONFIG_INPUT_E3X0_BUTTON=m +# CONFIG_INPUT_MMA8450 is not set +# CONFIG_INPUT_GPIO_BEEPER is not set +# CONFIG_INPUT_GPIO_DECODER is not set +# CONFIG_INPUT_GPIO_VIBRA is not set +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +# CONFIG_INPUT_KXTJ9 is not set +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_YEALINK=m +CONFIG_INPUT_CM109=m +# CONFIG_INPUT_REGULATOR_HAPTIC is not set +CONFIG_INPUT_UINPUT=m +# CONFIG_INPUT_PCF8574 is not set +CONFIG_INPUT_PWM_BEEPER=m +CONFIG_INPUT_PWM_VIBRA=m +# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set +# CONFIG_INPUT_DA7280_HAPTICS is not set +# CONFIG_INPUT_ADXL34X is not set +# CONFIG_INPUT_IMS_PCU is not set +# CONFIG_INPUT_IQS269A is not set +# CONFIG_INPUT_IQS626A is not set +# CONFIG_INPUT_IQS7222 is not set +# CONFIG_INPUT_CMA3000 is not set +# CONFIG_INPUT_DRV260X_HAPTICS is not set +# CONFIG_INPUT_DRV2665_HAPTICS is not set +# CONFIG_INPUT_DRV2667_HAPTICS is not set +CONFIG_INPUT_MACSMC_INPUT=y +# CONFIG_RMI4_CORE is not set + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set +# end of Hardware I/O ports +# end of Input device support + +# +# Character devices +# +CONFIG_TTY=y +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_VT_CONSOLE_SLEEP=y +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_UNIX98_PTYS=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_LEGACY_TIOCSTI=y +CONFIG_LDISC_AUTOLOAD=y + +# +# Serial drivers +# +CONFIG_SERIAL_EARLYCON=y +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_AMBA_PL010 is not set +# CONFIG_SERIAL_AMBA_PL011 is not set +# CONFIG_SERIAL_EARLYCON_SEMIHOST is not set +CONFIG_SERIAL_SAMSUNG=y +CONFIG_SERIAL_SAMSUNG_UARTS=4 +CONFIG_SERIAL_SAMSUNG_CONSOLE=y +# CONFIG_SERIAL_MAX3100 is not set +# CONFIG_SERIAL_MAX310X is not set +# CONFIG_SERIAL_UARTLITE is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +# CONFIG_SERIAL_JSM is not set +# CONFIG_SERIAL_SIFIVE is not set +# CONFIG_SERIAL_SCCNXP is not set +# CONFIG_SERIAL_SC16IS7XX is not set +# CONFIG_SERIAL_ALTERA_JTAGUART is not set +# CONFIG_SERIAL_ALTERA_UART is not set +# CONFIG_SERIAL_XILINX_PS_UART is not set +# CONFIG_SERIAL_ARC is not set +# CONFIG_SERIAL_RP2 is not set +# CONFIG_SERIAL_FSL_LPUART is not set +# CONFIG_SERIAL_FSL_LINFLEXUART is not set +# CONFIG_SERIAL_CONEXANT_DIGICOLOR is not set +# CONFIG_SERIAL_SPRD is not set +# end of Serial drivers + +CONFIG_SERIAL_NONSTANDARD=y +# CONFIG_MOXA_INTELLIO is not set +# CONFIG_MOXA_SMARTIO is not set +# CONFIG_N_HDLC is not set +# CONFIG_N_GSM is not set +CONFIG_NOZOMI=m +# CONFIG_NULL_TTY is not set +CONFIG_HVC_DRIVER=y +# CONFIG_HVC_DCC is not set +CONFIG_SERIAL_DEV_BUS=y +CONFIG_SERIAL_DEV_CTRL_TTYPORT=y +# CONFIG_TTY_PRINTK is not set +CONFIG_VIRTIO_CONSOLE=y +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_HW_RANDOM_TIMERIOMEM is not set +# CONFIG_HW_RANDOM_BA431 is not set +CONFIG_HW_RANDOM_VIRTIO=m +# CONFIG_HW_RANDOM_CCTRNG is not set +# CONFIG_HW_RANDOM_XIPHERA is not set +# CONFIG_HW_RANDOM_ARM_SMCCC_TRNG is not set +# CONFIG_HW_RANDOM_CN10K is not set +# CONFIG_APPLICOM is not set +CONFIG_DEVMEM=y +CONFIG_DEVPORT=y +# CONFIG_TCG_TPM is not set +# CONFIG_XILLYBUS is not set +# CONFIG_XILLYUSB is not set +# end of Character devices + +# +# I2C support +# +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_MUX=y + +# +# Multiplexer I2C Chip support +# +CONFIG_I2C_ARB_GPIO_CHALLENGE=m +CONFIG_I2C_MUX_GPIO=m +CONFIG_I2C_MUX_GPMUX=m +# CONFIG_I2C_MUX_LTC4306 is not set +CONFIG_I2C_MUX_PCA9541=m +CONFIG_I2C_MUX_PCA954x=y +CONFIG_I2C_MUX_PINCTRL=m +CONFIG_I2C_MUX_REG=m +CONFIG_I2C_DEMUX_PINCTRL=m +# CONFIG_I2C_MUX_MLXCPLD is not set +# end of Multiplexer I2C Chip support + +CONFIG_I2C_HELPER_AUTO=y +CONFIG_I2C_ALGOBIT=m + +# +# I2C Hardware Bus support +# + +# +# PC SMBus host controller drivers +# +# CONFIG_I2C_ALI1535 is not set +# CONFIG_I2C_ALI1563 is not set +# CONFIG_I2C_ALI15X3 is not set +# CONFIG_I2C_AMD756 is not set +# CONFIG_I2C_AMD8111 is not set +# CONFIG_I2C_I801 is not set +# CONFIG_I2C_ISCH is not set +# CONFIG_I2C_PIIX4 is not set +# CONFIG_I2C_NFORCE2 is not set +# CONFIG_I2C_NVIDIA_GPU is not set +# CONFIG_I2C_SIS5595 is not set +# CONFIG_I2C_SIS630 is not set +# CONFIG_I2C_SIS96X is not set +# CONFIG_I2C_VIA is not set +# CONFIG_I2C_VIAPRO is not set + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +# CONFIG_I2C_CADENCE is not set +# CONFIG_I2C_CBUS_GPIO is not set +# CONFIG_I2C_DESIGNWARE_CORE is not set +# CONFIG_I2C_EMEV2 is not set +# CONFIG_I2C_GPIO is not set +# CONFIG_I2C_HISI is not set +# CONFIG_I2C_NOMADIK is not set +# CONFIG_I2C_OCORES is not set +CONFIG_I2C_APPLE=y +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_RK3X is not set +# CONFIG_I2C_SIMTEC is not set +# CONFIG_I2C_THUNDERX is not set +# CONFIG_I2C_XILINX is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_DIOLAN_U2C is not set +# CONFIG_I2C_CP2615 is not set +# CONFIG_I2C_PCI1XXXX is not set +# CONFIG_I2C_ROBOTFUZZ_OSIF is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_VIRTIO is not set +# end of I2C Hardware Bus support + +# CONFIG_I2C_STUB is not set +# CONFIG_I2C_SLAVE is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# end of I2C support + +# CONFIG_I3C is not set +CONFIG_I3C_OR_I2C=y +CONFIG_SPI=y +# CONFIG_SPI_DEBUG is not set +CONFIG_SPI_MASTER=y +CONFIG_SPI_MEM=y + +# +# SPI Master Controller Drivers +# +# CONFIG_SPI_ALTERA is not set +CONFIG_SPI_APPLE=y +# CONFIG_SPI_AXI_SPI_ENGINE is not set +# CONFIG_SPI_BITBANG is not set +# CONFIG_SPI_CADENCE is not set +# CONFIG_SPI_CADENCE_QUADSPI is not set +# CONFIG_SPI_CADENCE_XSPI is not set +# CONFIG_SPI_CH341 is not set +# CONFIG_SPI_DESIGNWARE is not set +# CONFIG_SPI_GPIO is not set +# CONFIG_SPI_FSL_SPI is not set +# CONFIG_SPI_MICROCHIP_CORE_QSPI is not set +# CONFIG_SPI_MICROCHIP_CORE_SPI is not set +# CONFIG_SPI_OC_TINY is not set +# CONFIG_SPI_PCI1XXXX is not set +# CONFIG_SPI_PL022 is not set +# CONFIG_SPI_SC18IS602 is not set +# CONFIG_SPI_SIFIVE is not set +# CONFIG_SPI_SN_F_OSPI is not set +# CONFIG_SPI_MXIC is not set +# CONFIG_SPI_THUNDERX is not set +CONFIG_SPI_VIRTIO=m +# CONFIG_SPI_XCOMM is not set +# CONFIG_SPI_XILINX is not set +# CONFIG_SPI_ZYNQMP_GQSPI is not set +# CONFIG_SPI_AMD is not set + +# +# SPI Multiplexer support +# +# CONFIG_SPI_MUX is not set + +# +# SPI Protocol Masters +# +CONFIG_SPI_SPIDEV=m +# CONFIG_SPI_LOOPBACK_TEST is not set +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_SPI_SLAVE is not set +CONFIG_SPI_DYNAMIC=y +CONFIG_SPMI=y +CONFIG_SPMI_APPLE=y +# CONFIG_SPMI_HISI3670 is not set +# CONFIG_HSI is not set +CONFIG_PPS=y +# CONFIG_PPS_DEBUG is not set + +# +# PPS clients support +# +# CONFIG_PPS_CLIENT_KTIMER is not set +CONFIG_PPS_CLIENT_LDISC=m +CONFIG_PPS_CLIENT_GPIO=m +# CONFIG_PPS_GENERATOR is not set + +# +# PTP clock support +# +CONFIG_PTP_1588_CLOCK=m +CONFIG_PTP_1588_CLOCK_OPTIONAL=m +# CONFIG_DP83640_PHY is not set +# CONFIG_PTP_1588_CLOCK_INES is not set +CONFIG_PTP_1588_CLOCK_KVM=m +CONFIG_PTP_1588_CLOCK_VMCLOCK=m +# CONFIG_PTP_1588_CLOCK_IDT82P33 is not set +# CONFIG_PTP_1588_CLOCK_IDTCM is not set +# CONFIG_PTP_1588_CLOCK_FC3W is not set +# CONFIG_PTP_1588_CLOCK_MOCK is not set +# CONFIG_PTP_NETC_V4_TIMER is not set +# end of PTP clock support + +# +# DPLL device support +# +# CONFIG_ZL3073X_I2C is not set +# CONFIG_ZL3073X_SPI is not set +# end of DPLL device support + +CONFIG_PINCTRL=y +CONFIG_GENERIC_PINCTRL_GROUPS=y +CONFIG_PINMUX=y +CONFIG_GENERIC_PINMUX_FUNCTIONS=y +# CONFIG_DEBUG_PINCTRL is not set +CONFIG_PINCTRL_APPLE_GPIO=y +# CONFIG_PINCTRL_AW9523 is not set +# CONFIG_PINCTRL_CY8C95X0 is not set +# CONFIG_PINCTRL_MCP23S08 is not set +# CONFIG_PINCTRL_MICROCHIP_SGPIO is not set +# CONFIG_PINCTRL_OCELOT is not set +# CONFIG_PINCTRL_SINGLE is not set +# CONFIG_PINCTRL_STMFX is not set +# CONFIG_PINCTRL_SX150X is not set + +# +# Renesas pinctrl drivers +# +# end of Renesas pinctrl drivers + +CONFIG_GPIOLIB_LEGACY=y +CONFIG_GPIOLIB=y +CONFIG_GPIOLIB_FASTPATH_LIMIT=512 +CONFIG_OF_GPIO=y +CONFIG_GPIOLIB_IRQCHIP=y +# CONFIG_DEBUG_GPIO is not set +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_SYSFS_LEGACY=y +CONFIG_GPIO_CDEV=y +CONFIG_GPIO_CDEV_V1=y + +# +# Memory mapped GPIO drivers +# +# CONFIG_GPIO_74XX_MMIO is not set +# CONFIG_GPIO_ALTERA is not set +# CONFIG_GPIO_BY_PINCTRL is not set +# CONFIG_GPIO_CADENCE is not set +# CONFIG_GPIO_DWAPB is not set +# CONFIG_GPIO_FTGPIO010 is not set +# CONFIG_GPIO_GENERIC_PLATFORM is not set +# CONFIG_GPIO_GRGPIO is not set +# CONFIG_GPIO_HISI is not set +# CONFIG_GPIO_HLWD is not set +# CONFIG_GPIO_LOGICVC is not set +# CONFIG_GPIO_MB86S7X is not set +# CONFIG_GPIO_PL061 is not set +# CONFIG_GPIO_POLARFIRE_SOC is not set +# CONFIG_GPIO_SIFIVE is not set +# CONFIG_GPIO_SYSCON is not set +# CONFIG_GPIO_XGENE is not set +# CONFIG_GPIO_XILINX is not set +# CONFIG_GPIO_AMD_FCH is not set +# end of Memory mapped GPIO drivers + +# +# I2C GPIO expanders +# +# CONFIG_GPIO_ADNP is not set +# CONFIG_GPIO_FXL6408 is not set +# CONFIG_GPIO_DS4520 is not set +# CONFIG_GPIO_GW_PLD is not set +# CONFIG_GPIO_MAX7300 is not set +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCA9570 is not set +# CONFIG_GPIO_PCF857X is not set +# CONFIG_GPIO_TPIC2810 is not set +# end of I2C GPIO expanders + +# +# MFD GPIO expanders +# +CONFIG_GPIO_MACSMC=y +# end of MFD GPIO expanders + +# +# PCI GPIO expanders +# +# CONFIG_GPIO_BT8XX is not set +# CONFIG_GPIO_PCI_IDIO_16 is not set +# CONFIG_GPIO_PCIE_IDIO_24 is not set +# CONFIG_GPIO_RDC321X is not set +# end of PCI GPIO expanders + +# +# SPI GPIO expanders +# +# CONFIG_GPIO_74X164 is not set +# CONFIG_GPIO_MAX3191X is not set +# CONFIG_GPIO_MAX7301 is not set +# CONFIG_GPIO_MC33880 is not set +# CONFIG_GPIO_PISOSR is not set +# CONFIG_GPIO_XRA1403 is not set +# end of SPI GPIO expanders + +# +# USB GPIO expanders +# +# CONFIG_GPIO_MPSSE is not set +# end of USB GPIO expanders + +# +# Virtual GPIO drivers +# +# CONFIG_GPIO_AGGREGATOR is not set +# CONFIG_GPIO_LATCH is not set +# CONFIG_GPIO_LINE_MUX is not set +# CONFIG_GPIO_MOCKUP is not set +# CONFIG_GPIO_VIRTIO is not set +# CONFIG_GPIO_SIM is not set +# end of Virtual GPIO drivers + +# +# GPIO Debugging utilities +# +# CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER is not set +# CONFIG_GPIO_VIRTUSER is not set +# end of GPIO Debugging utilities + +# CONFIG_W1 is not set +CONFIG_POWER_RESET=y +# CONFIG_POWER_RESET_GPIO is not set +# CONFIG_POWER_RESET_GPIO_RESTART is not set +CONFIG_POWER_RESET_MACSMC=y +# CONFIG_POWER_RESET_LTC2952 is not set +# CONFIG_POWER_RESET_REGULATOR is not set +# CONFIG_POWER_RESET_RESTART is not set +# CONFIG_POWER_RESET_XGENE is not set +# CONFIG_POWER_RESET_SYSCON is not set +# CONFIG_POWER_RESET_SYSCON_POWEROFF is not set +# CONFIG_SYSCON_REBOOT_MODE is not set +# CONFIG_NVMEM_REBOOT_MODE is not set +# CONFIG_POWER_RESET_QEMU_VIRT_CTRL is not set +CONFIG_POWER_SEQUENCING=y +# CONFIG_POWER_SEQUENCING_QCOM_WCN is not set +# CONFIG_POWER_SEQUENCING_PCIE_M2 is not set +CONFIG_POWER_SUPPLY=y +# CONFIG_POWER_SUPPLY_DEBUG is not set +CONFIG_POWER_SUPPLY_HWMON=y +# CONFIG_GENERIC_ADC_BATTERY is not set +# CONFIG_IP5XXX_POWER is not set +# CONFIG_TEST_POWER is not set +# CONFIG_CHARGER_ADP5061 is not set +# CONFIG_BATTERY_CHAGALL is not set +# CONFIG_BATTERY_CW2015 is not set +# CONFIG_BATTERY_DS2780 is not set +# CONFIG_BATTERY_DS2781 is not set +# CONFIG_BATTERY_DS2782 is not set +# CONFIG_BATTERY_SAMSUNG_SDI is not set +# CONFIG_BATTERY_S2MU005 is not set +# CONFIG_BATTERY_SBS is not set +# CONFIG_CHARGER_SBS is not set +# CONFIG_MANAGER_SBS is not set +# CONFIG_BATTERY_BQ27XXX is not set +# CONFIG_BATTERY_MAX17040 is not set +# CONFIG_BATTERY_MAX17042 is not set +# CONFIG_BATTERY_MAX1720X is not set +# CONFIG_CHARGER_ISP1704 is not set +# CONFIG_CHARGER_MAX8903 is not set +# CONFIG_CHARGER_LP8727 is not set +# CONFIG_CHARGER_GPIO is not set +# CONFIG_CHARGER_MANAGER is not set +# CONFIG_CHARGER_LT3651 is not set +# CONFIG_CHARGER_LTC4162L is not set +# CONFIG_CHARGER_DETECTOR_MAX14656 is not set +# CONFIG_CHARGER_MAX77976 is not set +# CONFIG_CHARGER_MAX8971 is not set +# CONFIG_CHARGER_BQ2415X is not set +# CONFIG_CHARGER_BQ24190 is not set +# CONFIG_CHARGER_BQ24257 is not set +# CONFIG_CHARGER_BQ24735 is not set +# CONFIG_CHARGER_BQ2515X is not set +# CONFIG_CHARGER_BQ25890 is not set +# CONFIG_CHARGER_BQ25980 is not set +# CONFIG_CHARGER_BQ256XX is not set +# CONFIG_CHARGER_SMB347 is not set +# CONFIG_BATTERY_GAUGE_LTC2941 is not set +# CONFIG_BATTERY_GOLDFISH is not set +# CONFIG_BATTERY_RT5033 is not set +# CONFIG_CHARGER_RT9455 is not set +# CONFIG_CHARGER_RT9467 is not set +# CONFIG_CHARGER_RT9471 is not set +# CONFIG_CHARGER_RT9756 is not set +# CONFIG_CHARGER_UCS1002 is not set +# CONFIG_CHARGER_BD99954 is not set +# CONFIG_BATTERY_UG3105 is not set +# CONFIG_FUEL_GAUGE_MM8013 is not set +CONFIG_MACSMC_POWER=y +CONFIG_HWMON=y +# CONFIG_HWMON_DEBUG_CHIP is not set + +# +# Native drivers +# +# CONFIG_SENSORS_AD7314 is not set +# CONFIG_SENSORS_AD7414 is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM1177 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7310 is not set +# CONFIG_SENSORS_ADT7410 is not set +# CONFIG_SENSORS_ADT7411 is not set +# CONFIG_SENSORS_ADT7462 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ADT7475 is not set +# CONFIG_SENSORS_AHT10 is not set +# CONFIG_SENSORS_AQUACOMPUTER_D5NEXT is not set +# CONFIG_SENSORS_AS370 is not set +# CONFIG_SENSORS_ASC7621 is not set +# CONFIG_SENSORS_ASUS_ROG_RYUJIN is not set +# CONFIG_SENSORS_AXI_FAN_CONTROL is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_CHIPCAP2 is not set +# CONFIG_SENSORS_CORSAIR_CPRO is not set +# CONFIG_SENSORS_CORSAIR_PSU is not set +# CONFIG_SENSORS_DS620 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_I5K_AMB is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_FTSTEUTATES is not set +# CONFIG_SENSORS_GIGABYTE_WATERFORCE is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_G760A is not set +# CONFIG_SENSORS_G762 is not set +# CONFIG_SENSORS_GPIO_FAN is not set +# CONFIG_SENSORS_HIH6130 is not set +# CONFIG_SENSORS_HS3001 is not set +# CONFIG_SENSORS_HTU31 is not set +# CONFIG_SENSORS_IIO_HWMON is not set +# CONFIG_SENSORS_ISL28022 is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_JC42 is not set +# CONFIG_SENSORS_POWERZ is not set +# CONFIG_SENSORS_POWR1220 is not set +# CONFIG_SENSORS_LINEAGE is not set +# CONFIG_SENSORS_LTC2945 is not set +# CONFIG_SENSORS_LTC2947_I2C is not set +# CONFIG_SENSORS_LTC2947_SPI is not set +# CONFIG_SENSORS_LTC2990 is not set +# CONFIG_SENSORS_LTC2991 is not set +# CONFIG_SENSORS_LTC2992 is not set +# CONFIG_SENSORS_LTC4151 is not set +# CONFIG_SENSORS_LTC4215 is not set +# CONFIG_SENSORS_LTC4222 is not set +# CONFIG_SENSORS_LTC4245 is not set +# CONFIG_SENSORS_LTC4260 is not set +# CONFIG_SENSORS_LTC4261 is not set +# CONFIG_SENSORS_LTC4282 is not set +CONFIG_SENSORS_MACSMC_HWMON=y +# CONFIG_SENSORS_MAX1111 is not set +# CONFIG_SENSORS_MAX127 is not set +# CONFIG_SENSORS_MAX16065 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX1668 is not set +# CONFIG_SENSORS_MAX197 is not set +# CONFIG_SENSORS_MAX31722 is not set +# CONFIG_SENSORS_MAX31730 is not set +# CONFIG_SENSORS_MAX31760 is not set +# CONFIG_MAX31827 is not set +# CONFIG_SENSORS_MAX6620 is not set +# CONFIG_SENSORS_MAX6621 is not set +# CONFIG_SENSORS_MAX6639 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_MAX6697 is not set +# CONFIG_SENSORS_MAX31790 is not set +# CONFIG_SENSORS_MC34VR500 is not set +# CONFIG_SENSORS_MCP3021 is not set +# CONFIG_SENSORS_MCP9982 is not set +# CONFIG_SENSORS_TC654 is not set +# CONFIG_SENSORS_TPS23861 is not set +# CONFIG_SENSORS_MR75203 is not set +# CONFIG_SENSORS_ADCXX is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM70 is not set +# CONFIG_SENSORS_LM73 is not set +# CONFIG_SENSORS_LM75 is not set +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +# CONFIG_SENSORS_LM95234 is not set +# CONFIG_SENSORS_LM95241 is not set +# CONFIG_SENSORS_LM95245 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_NTC_THERMISTOR is not set +# CONFIG_SENSORS_NCT6683 is not set +# CONFIG_SENSORS_NCT6775 is not set +# CONFIG_SENSORS_NCT6775_I2C is not set +# CONFIG_SENSORS_NCT7363 is not set +# CONFIG_SENSORS_NCT7802 is not set +# CONFIG_SENSORS_NCT7904 is not set +# CONFIG_SENSORS_NPCM7XX is not set +# CONFIG_SENSORS_NZXT_KRAKEN2 is not set +# CONFIG_SENSORS_NZXT_KRAKEN3 is not set +# CONFIG_SENSORS_NZXT_SMART2 is not set +# CONFIG_SENSORS_OCC_P8_I2C is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_PMBUS is not set +# CONFIG_SENSORS_PT5161L is not set +# CONFIG_SENSORS_PWM_FAN is not set +# CONFIG_SENSORS_SBTSI is not set +# CONFIG_SENSORS_SHT15 is not set +# CONFIG_SENSORS_SHT21 is not set +# CONFIG_SENSORS_SHT3x is not set +# CONFIG_SENSORS_SHT4x is not set +# CONFIG_SENSORS_SHTC1 is not set +# CONFIG_SENSORS_SIS5595 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_EMC1403 is not set +# CONFIG_SENSORS_EMC2103 is not set +# CONFIG_SENSORS_EMC2305 is not set +# CONFIG_SENSORS_EMC6W201 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +# CONFIG_SENSORS_SCH5627 is not set +# CONFIG_SENSORS_SCH5636 is not set +# CONFIG_SENSORS_STTS751 is not set +# CONFIG_SENSORS_ADC128D818 is not set +# CONFIG_SENSORS_ADS7828 is not set +# CONFIG_SENSORS_ADS7871 is not set +# CONFIG_SENSORS_AMC6821 is not set +# CONFIG_SENSORS_INA209 is not set +# CONFIG_SENSORS_INA2XX is not set +# CONFIG_SENSORS_INA238 is not set +# CONFIG_SENSORS_INA3221 is not set +# CONFIG_SENSORS_SPD5118 is not set +# CONFIG_SENSORS_TC74 is not set +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_TMP102 is not set +# CONFIG_SENSORS_TMP103 is not set +# CONFIG_SENSORS_TMP108 is not set +# CONFIG_SENSORS_TMP401 is not set +# CONFIG_SENSORS_TMP421 is not set +# CONFIG_SENSORS_TMP464 is not set +# CONFIG_SENSORS_TMP513 is not set +# CONFIG_SENSORS_TSC1641 is not set +# CONFIG_SENSORS_VIA686A is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_VT8231 is not set +# CONFIG_SENSORS_W83773G is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83795 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83L786NG is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +CONFIG_THERMAL=y +# CONFIG_THERMAL_NETLINK is not set +# CONFIG_THERMAL_STATISTICS is not set +# CONFIG_THERMAL_DEBUGFS is not set +# CONFIG_THERMAL_CORE_TESTING is not set +CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=0 +CONFIG_THERMAL_HWMON=y +CONFIG_THERMAL_OF=y +CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y +# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set +# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set +# CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR is not set +# CONFIG_THERMAL_DEFAULT_GOV_BANG_BANG is not set +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_THERMAL_GOV_STEP_WISE=y +CONFIG_THERMAL_GOV_BANG_BANG=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y +CONFIG_CPU_THERMAL=y +CONFIG_CPU_FREQ_THERMAL=y +CONFIG_DEVFREQ_THERMAL=y +# CONFIG_PCIE_THERMAL is not set +CONFIG_THERMAL_EMULATION=y +# CONFIG_THERMAL_MMIO is not set +# CONFIG_GENERIC_ADC_THERMAL is not set +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_CORE=y +# CONFIG_WATCHDOG_NOWAYOUT is not set +CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y +CONFIG_WATCHDOG_OPEN_TIMEOUT=0 +# CONFIG_WATCHDOG_SYSFS is not set +# CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT is not set + +# +# Watchdog Pretimeout Governors +# +# CONFIG_WATCHDOG_PRETIMEOUT_GOV is not set + +# +# Watchdog Device Drivers +# +CONFIG_SOFT_WATCHDOG=m +# CONFIG_GPIO_WATCHDOG is not set +# CONFIG_XILINX_WATCHDOG is not set +# CONFIG_XILINX_WINDOW_WATCHDOG is not set +# CONFIG_ZIIRAVE_WATCHDOG is not set +# CONFIG_ARM_SP805_WATCHDOG is not set +# CONFIG_ARM_SBSA_WATCHDOG is not set +# CONFIG_CADENCE_WATCHDOG is not set +# CONFIG_DW_WATCHDOG is not set +# CONFIG_MAX63XX_WATCHDOG is not set +# CONFIG_ARM_SMC_WATCHDOG is not set +CONFIG_APPLE_WATCHDOG=y +# CONFIG_ALIM7101_WDT is not set +# CONFIG_I6300ESB_WDT is not set +# CONFIG_HP_WATCHDOG is not set +# CONFIG_MEN_A21_WDT is not set + +# +# PCI-based Watchdog Cards +# +# CONFIG_PCIPCWATCHDOG is not set +# CONFIG_WDTPCI is not set + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y +# CONFIG_SSB is not set +CONFIG_BCMA_POSSIBLE=y +# CONFIG_BCMA is not set + +# +# Multifunction device drivers +# +CONFIG_MFD_CORE=y +# CONFIG_MFD_ADP5585 is not set +# CONFIG_MFD_ACT8945A is not set +# CONFIG_MFD_AS3711 is not set +# CONFIG_MFD_SMPRO is not set +# CONFIG_MFD_AS3722 is not set +# CONFIG_PMIC_ADP5520 is not set +# CONFIG_MFD_AAT2870_CORE is not set +# CONFIG_MFD_ATMEL_FLEXCOM is not set +# CONFIG_MFD_ATMEL_HLCDC is not set +# CONFIG_MFD_BCM590XX is not set +# CONFIG_MFD_BD9571MWV is not set +# CONFIG_MFD_AXP20X_I2C is not set +# CONFIG_MFD_CS40L50_I2C is not set +# CONFIG_MFD_CS40L50_SPI is not set +# CONFIG_MFD_CS42L43_I2C is not set +# CONFIG_MFD_LOCHNAGAR is not set +CONFIG_MFD_MACSMC=y +# CONFIG_MFD_MADERA is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_DA9052_SPI is not set +# CONFIG_MFD_DA9052_I2C is not set +# CONFIG_MFD_DA9055 is not set +# CONFIG_MFD_DA9062 is not set +# CONFIG_MFD_DA9063 is not set +# CONFIG_MFD_DA9150 is not set +# CONFIG_MFD_DLN2 is not set +# CONFIG_MFD_GATEWORKS_GSC is not set +# CONFIG_MFD_MC13XXX_SPI is not set +# CONFIG_MFD_MC13XXX_I2C is not set +# CONFIG_MFD_MP2629 is not set +# CONFIG_MFD_PF1550 is not set +# CONFIG_MFD_HI6421_PMIC is not set +# CONFIG_MFD_HI6421_SPMI is not set +# CONFIG_LPC_ICH is not set +# CONFIG_LPC_SCH is not set +# CONFIG_MFD_IQS62X is not set +# CONFIG_MFD_JANZ_CMODIO is not set +# CONFIG_MFD_KEMPLD is not set +# CONFIG_MFD_88PM800 is not set +# CONFIG_MFD_88PM805 is not set +# CONFIG_MFD_88PM860X is not set +# CONFIG_MFD_88PM886_PMIC is not set +# CONFIG_MFD_MAX5970 is not set +# CONFIG_MFD_MAX14577 is not set +# CONFIG_MFD_MAX77541 is not set +# CONFIG_MFD_MAX77620 is not set +# CONFIG_MFD_MAX77650 is not set +# CONFIG_MFD_MAX77686 is not set +# CONFIG_MFD_MAX77693 is not set +# CONFIG_MFD_MAX77705 is not set +# CONFIG_MFD_MAX77714 is not set +# CONFIG_MFD_MAX77759 is not set +# CONFIG_MFD_MAX77843 is not set +# CONFIG_MFD_MAX8907 is not set +# CONFIG_MFD_MAX8925 is not set +# CONFIG_MFD_MAX8997 is not set +# CONFIG_MFD_MAX8998 is not set +# CONFIG_MFD_MT6360 is not set +# CONFIG_MFD_MT6370 is not set +# CONFIG_MFD_MT6397 is not set +# CONFIG_MFD_MENF21BMC is not set +# CONFIG_MFD_NCT6694 is not set +# CONFIG_MFD_OCELOT is not set +# CONFIG_EZX_PCAP is not set +# CONFIG_MFD_CPCAP is not set +# CONFIG_MFD_VIPERBOARD is not set +# CONFIG_MFD_NTXEC is not set +# CONFIG_MFD_RETU is not set +# CONFIG_MFD_SY7636A is not set +# CONFIG_MFD_RDC321X is not set +# CONFIG_MFD_RT4831 is not set +# CONFIG_MFD_RT5033 is not set +# CONFIG_MFD_RT5120 is not set +# CONFIG_MFD_RC5T583 is not set +# CONFIG_MFD_RK8XX_I2C is not set +# CONFIG_MFD_RK8XX_SPI is not set +# CONFIG_MFD_RN5T618 is not set +# CONFIG_MFD_SEC_I2C is not set +# CONFIG_MFD_SI476X_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_MFD_SKY81452 is not set +# CONFIG_MFD_STMPE is not set +CONFIG_MFD_SYSCON=y +# CONFIG_MFD_LP3943 is not set +# CONFIG_MFD_LP8788 is not set +# CONFIG_MFD_TI_LMU is not set +# CONFIG_MFD_BQ257XX is not set +# CONFIG_MFD_PALMAS is not set +# CONFIG_TPS6105X is not set +# CONFIG_TPS65010 is not set +# CONFIG_TPS6507X is not set +# CONFIG_MFD_TPS65086 is not set +# CONFIG_MFD_TPS65090 is not set +# CONFIG_MFD_TPS65217 is not set +# CONFIG_MFD_TI_LP873X is not set +# CONFIG_MFD_TI_LP87565 is not set +# CONFIG_MFD_TPS65218 is not set +# CONFIG_MFD_TPS65219 is not set +# CONFIG_MFD_TPS6586X is not set +# CONFIG_MFD_TPS65910 is not set +# CONFIG_MFD_TPS65912_I2C is not set +# CONFIG_MFD_TPS65912_SPI is not set +# CONFIG_MFD_TPS6594_I2C is not set +# CONFIG_MFD_TPS6594_SPI is not set +# CONFIG_TWL4030_CORE is not set +# CONFIG_TWL6040_CORE is not set +# CONFIG_MFD_LM3533 is not set +# CONFIG_MFD_TC3589X is not set +# CONFIG_MFD_TQMX86 is not set +# CONFIG_MFD_VX855 is not set +# CONFIG_MFD_ARIZONA_I2C is not set +# CONFIG_MFD_ARIZONA_SPI is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM831X_I2C is not set +# CONFIG_MFD_WM831X_SPI is not set +# CONFIG_MFD_WM8350_I2C is not set +# CONFIG_MFD_WM8994 is not set +# CONFIG_MFD_ROHM_BD718XX is not set +# CONFIG_MFD_ROHM_BD71828 is not set +# CONFIG_MFD_ROHM_BD957XMUF is not set +# CONFIG_MFD_ROHM_BD96801 is not set +# CONFIG_MFD_STPMIC1 is not set +# CONFIG_MFD_STMFX is not set +# CONFIG_MFD_ATC260X_I2C is not set +# CONFIG_MFD_QCOM_PM8008 is not set +# CONFIG_RAVE_SP_CORE is not set +# CONFIG_MFD_INTEL_M10_BMC_SPI is not set +# CONFIG_MFD_QNAP_MCU is not set +# CONFIG_MFD_RSMU_I2C is not set +# CONFIG_MFD_RSMU_SPI is not set +# CONFIG_MFD_MAX7360 is not set +# end of Multifunction device drivers + +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +CONFIG_REGULATOR_FIXED_VOLTAGE=y +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_REGULATOR_USERSPACE_CONSUMER is not set +# CONFIG_REGULATOR_NETLINK_EVENTS is not set +# CONFIG_REGULATOR_88PG86X is not set +# CONFIG_REGULATOR_ACT8865 is not set +# CONFIG_REGULATOR_AD5398 is not set +# CONFIG_REGULATOR_ADP5055 is not set +# CONFIG_REGULATOR_AW37503 is not set +# CONFIG_REGULATOR_DA9121 is not set +# CONFIG_REGULATOR_DA9210 is not set +# CONFIG_REGULATOR_DA9211 is not set +# CONFIG_REGULATOR_FAN53555 is not set +# CONFIG_REGULATOR_FAN53880 is not set +# CONFIG_REGULATOR_GPIO is not set +# CONFIG_REGULATOR_ISL9305 is not set +# CONFIG_REGULATOR_ISL6271A is not set +# CONFIG_REGULATOR_FP9931 is not set +# CONFIG_REGULATOR_LP3971 is not set +# CONFIG_REGULATOR_LP3972 is not set +# CONFIG_REGULATOR_LP872X is not set +# CONFIG_REGULATOR_LP8755 is not set +# CONFIG_REGULATOR_LTC3589 is not set +# CONFIG_REGULATOR_LTC3676 is not set +# CONFIG_REGULATOR_MAX1586 is not set +# CONFIG_REGULATOR_MAX77503 is not set +# CONFIG_REGULATOR_MAX77675 is not set +# CONFIG_REGULATOR_MAX77857 is not set +# CONFIG_REGULATOR_MAX8649 is not set +# CONFIG_REGULATOR_MAX8660 is not set +# CONFIG_REGULATOR_MAX8893 is not set +# CONFIG_REGULATOR_MAX8952 is not set +# CONFIG_REGULATOR_MAX8973 is not set +# CONFIG_REGULATOR_MAX20086 is not set +# CONFIG_REGULATOR_MAX20411 is not set +# CONFIG_REGULATOR_MAX77826 is not set +# CONFIG_REGULATOR_MAX77838 is not set +# CONFIG_REGULATOR_MCP16502 is not set +# CONFIG_REGULATOR_MP5416 is not set +# CONFIG_REGULATOR_MP8859 is not set +# CONFIG_REGULATOR_MP886X is not set +# CONFIG_REGULATOR_MPQ7920 is not set +# CONFIG_REGULATOR_MT6311 is not set +# CONFIG_REGULATOR_MT6315 is not set +# CONFIG_REGULATOR_MT6316 is not set +# CONFIG_REGULATOR_MT6363 is not set +# CONFIG_REGULATOR_PCA9450 is not set +# CONFIG_REGULATOR_PF9453 is not set +# CONFIG_REGULATOR_PF0900 is not set +# CONFIG_REGULATOR_PF530X is not set +# CONFIG_REGULATOR_PF8X00 is not set +# CONFIG_REGULATOR_PFUZE100 is not set +# CONFIG_REGULATOR_PV88060 is not set +# CONFIG_REGULATOR_PV88080 is not set +# CONFIG_REGULATOR_PV88090 is not set +# CONFIG_REGULATOR_PWM is not set +# CONFIG_REGULATOR_QCOM_SPMI is not set +# CONFIG_REGULATOR_QCOM_USB_VBUS is not set +# CONFIG_REGULATOR_RAA215300 is not set +# CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY is not set +# CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2 is not set +# CONFIG_REGULATOR_RT4801 is not set +# CONFIG_REGULATOR_RT4803 is not set +# CONFIG_REGULATOR_RT5133 is not set +# CONFIG_REGULATOR_RT5190A is not set +# CONFIG_REGULATOR_RT5739 is not set +# CONFIG_REGULATOR_RT5759 is not set +# CONFIG_REGULATOR_RT6160 is not set +# CONFIG_REGULATOR_RT6190 is not set +# CONFIG_REGULATOR_RT6245 is not set +# CONFIG_REGULATOR_RT8092 is not set +# CONFIG_REGULATOR_RTQ2134 is not set +# CONFIG_REGULATOR_RTMV20 is not set +# CONFIG_REGULATOR_RTQ6752 is not set +# CONFIG_REGULATOR_RTQ2208 is not set +# CONFIG_REGULATOR_SLG51000 is not set +# CONFIG_REGULATOR_SY8106A is not set +# CONFIG_REGULATOR_SY8824X is not set +# CONFIG_REGULATOR_SY8827N is not set +# CONFIG_REGULATOR_TPS51632 is not set +# CONFIG_REGULATOR_TPS62360 is not set +# CONFIG_REGULATOR_TPS6286X is not set +# CONFIG_REGULATOR_TPS6287X is not set +# CONFIG_REGULATOR_TPS65023 is not set +# CONFIG_REGULATOR_TPS6507X is not set +# CONFIG_REGULATOR_TPS65132 is not set +# CONFIG_REGULATOR_TPS65185 is not set +# CONFIG_REGULATOR_TPS6524X is not set +# CONFIG_REGULATOR_VCTRL is not set +# CONFIG_REGULATOR_QCOM_LABIBB is not set +CONFIG_RC_CORE=m +CONFIG_LIRC=y +CONFIG_RC_MAP=m +CONFIG_RC_DECODERS=y +CONFIG_IR_IMON_DECODER=m +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_MCE_KBD_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_RCMM_DECODER=m +CONFIG_IR_SANYO_DECODER=m +CONFIG_IR_SHARP_DECODER=m +CONFIG_IR_SONY_DECODER=m +CONFIG_IR_XMP_DECODER=m +CONFIG_RC_DEVICES=y +# CONFIG_IR_GPIO_CIR is not set +# CONFIG_IR_GPIO_TX is not set +CONFIG_IR_HIX5HD2=m +CONFIG_IR_IGORPLUGUSB=m +CONFIG_IR_IGUANA=m +CONFIG_IR_IMON=m +CONFIG_IR_IMON_RAW=m +CONFIG_IR_MCEUSB=m +# CONFIG_IR_PWM_TX is not set +CONFIG_IR_REDRAT3=m +# CONFIG_IR_SERIAL is not set +# CONFIG_IR_SPI is not set +CONFIG_IR_STREAMZAP=m +CONFIG_IR_TOY=m +CONFIG_IR_TTUSBIR=m +CONFIG_RC_ATI_REMOTE=m +CONFIG_RC_LOOPBACK=m +# CONFIG_RC_XBOX_DVD is not set +CONFIG_CEC_CORE=m + +# +# CEC support +# +# CONFIG_MEDIA_CEC_RC is not set +CONFIG_MEDIA_CEC_SUPPORT=y +# CONFIG_CEC_CH7322 is not set +# CONFIG_CEC_NXP_TDA9950 is not set +# CONFIG_CEC_GPIO is not set +# CONFIG_USB_EXTRON_DA_HD_4K_PLUS_CEC is not set +# CONFIG_USB_PULSE8_CEC is not set +# CONFIG_USB_RAINSHADOW_CEC is not set +# end of CEC support + +CONFIG_MEDIA_SUPPORT=m +# CONFIG_MEDIA_SUPPORT_FILTER is not set +CONFIG_MEDIA_SUBDRV_AUTOSELECT=y + +# +# Media device types +# +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_ANALOG_TV_SUPPORT=y +CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y +CONFIG_MEDIA_RADIO_SUPPORT=y +CONFIG_MEDIA_SDR_SUPPORT=y +CONFIG_MEDIA_PLATFORM_SUPPORT=y +CONFIG_MEDIA_TEST_SUPPORT=y +# end of Media device types + +# +# Media core support +# +CONFIG_VIDEO_DEV=m +CONFIG_MEDIA_CONTROLLER=y +CONFIG_DVB_CORE=m +# end of Media core support + +# +# Video4Linux options +# +CONFIG_VIDEO_V4L2_I2C=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +CONFIG_VIDEO_TUNER=m +CONFIG_V4L2_H264=m +CONFIG_V4L2_VP9=m +CONFIG_V4L2_MEM2MEM_DEV=m +# CONFIG_V4L2_FLASH_LED_CLASS is not set +CONFIG_V4L2_FWNODE=m +CONFIG_V4L2_ASYNC=m +# end of Video4Linux options + +# +# Media controller options +# +CONFIG_MEDIA_CONTROLLER_DVB=y +# end of Media controller options + +# +# Digital TV options +# +# CONFIG_DVB_MMAP is not set +CONFIG_DVB_NET=y +CONFIG_DVB_MAX_ADAPTERS=8 +CONFIG_DVB_DYNAMIC_MINORS=y +# CONFIG_DVB_DEMUX_SECTION_LOSS_LOG is not set +# CONFIG_DVB_ULE_DEBUG is not set +# end of Digital TV options + +# +# Media drivers +# + +# +# Media drivers +# +CONFIG_MEDIA_USB_SUPPORT=y + +# +# Webcam devices +# +CONFIG_USB_GSPCA=m +CONFIG_USB_GSPCA_BENQ=m +CONFIG_USB_GSPCA_CONEX=m +CONFIG_USB_GSPCA_CPIA1=m +CONFIG_USB_GSPCA_DTCS033=m +CONFIG_USB_GSPCA_ETOMS=m +CONFIG_USB_GSPCA_FINEPIX=m +CONFIG_USB_GSPCA_JEILINJ=m +CONFIG_USB_GSPCA_JL2005BCD=m +CONFIG_USB_GSPCA_KINECT=m +CONFIG_USB_GSPCA_KONICA=m +CONFIG_USB_GSPCA_MARS=m +CONFIG_USB_GSPCA_MR97310A=m +CONFIG_USB_GSPCA_NW80X=m +CONFIG_USB_GSPCA_OV519=m +CONFIG_USB_GSPCA_OV534=m +CONFIG_USB_GSPCA_OV534_9=m +CONFIG_USB_GSPCA_PAC207=m +CONFIG_USB_GSPCA_PAC7302=m +CONFIG_USB_GSPCA_PAC7311=m +CONFIG_USB_GSPCA_SE401=m +CONFIG_USB_GSPCA_SN9C2028=m +CONFIG_USB_GSPCA_SN9C20X=m +CONFIG_USB_GSPCA_SONIXB=m +CONFIG_USB_GSPCA_SONIXJ=m +CONFIG_USB_GSPCA_SPCA1528=m +CONFIG_USB_GSPCA_SPCA500=m +CONFIG_USB_GSPCA_SPCA501=m +CONFIG_USB_GSPCA_SPCA505=m +CONFIG_USB_GSPCA_SPCA506=m +CONFIG_USB_GSPCA_SPCA508=m +CONFIG_USB_GSPCA_SPCA561=m +CONFIG_USB_GSPCA_SQ905=m +CONFIG_USB_GSPCA_SQ905C=m +CONFIG_USB_GSPCA_SQ930X=m +CONFIG_USB_GSPCA_STK014=m +CONFIG_USB_GSPCA_STK1135=m +CONFIG_USB_GSPCA_STV0680=m +CONFIG_USB_GSPCA_SUNPLUS=m +CONFIG_USB_GSPCA_T613=m +CONFIG_USB_GSPCA_TOPRO=m +CONFIG_USB_GSPCA_TOUPTEK=m +CONFIG_USB_GSPCA_TV8532=m +CONFIG_USB_GSPCA_VC032X=m +CONFIG_USB_GSPCA_VICAM=m +CONFIG_USB_GSPCA_XIRLINK_CIT=m +CONFIG_USB_GSPCA_ZC3XX=m +CONFIG_USB_GL860=m +CONFIG_USB_M5602=m +CONFIG_USB_STV06XX=m +CONFIG_USB_PWC=m +# CONFIG_USB_PWC_DEBUG is not set +CONFIG_USB_PWC_INPUT_EVDEV=y +CONFIG_USB_S2255=m +CONFIG_VIDEO_USBTV=m +CONFIG_USB_VIDEO_CLASS=m +CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV=y + +# +# Analog TV USB devices +# +CONFIG_VIDEO_GO7007=m +CONFIG_VIDEO_GO7007_USB=m +CONFIG_VIDEO_GO7007_LOADER=m +CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m +CONFIG_VIDEO_HDPVR=m +CONFIG_VIDEO_PVRUSB2=m +CONFIG_VIDEO_PVRUSB2_SYSFS=y +CONFIG_VIDEO_PVRUSB2_DVB=y +# CONFIG_VIDEO_PVRUSB2_DEBUGIFC is not set +CONFIG_VIDEO_STK1160=m + +# +# Analog/digital TV USB devices +# +CONFIG_VIDEO_AU0828=m +CONFIG_VIDEO_AU0828_V4L2=y +CONFIG_VIDEO_AU0828_RC=y +CONFIG_VIDEO_CX231XX=m +CONFIG_VIDEO_CX231XX_RC=y +CONFIG_VIDEO_CX231XX_ALSA=m +CONFIG_VIDEO_CX231XX_DVB=m + +# +# Digital TV USB devices +# +CONFIG_DVB_AS102=m +CONFIG_DVB_B2C2_FLEXCOP_USB=m +# CONFIG_DVB_B2C2_FLEXCOP_USB_DEBUG is not set +CONFIG_DVB_USB_V2=m +CONFIG_DVB_USB_AF9015=m +CONFIG_DVB_USB_AF9035=m +CONFIG_DVB_USB_ANYSEE=m +CONFIG_DVB_USB_AU6610=m +CONFIG_DVB_USB_AZ6007=m +CONFIG_DVB_USB_CE6230=m +CONFIG_DVB_USB_DVBSKY=m +CONFIG_DVB_USB_EC168=m +CONFIG_DVB_USB_GL861=m +CONFIG_DVB_USB_LME2510=m +CONFIG_DVB_USB_MXL111SF=m +CONFIG_DVB_USB_RTL28XXU=m +CONFIG_DVB_USB_ZD1301=m +CONFIG_DVB_USB=m +# CONFIG_DVB_USB_DEBUG is not set +CONFIG_DVB_USB_A800=m +CONFIG_DVB_USB_AF9005=m +CONFIG_DVB_USB_AF9005_REMOTE=m +CONFIG_DVB_USB_AZ6027=m +CONFIG_DVB_USB_CINERGY_T2=m +CONFIG_DVB_USB_CXUSB=m +# CONFIG_DVB_USB_CXUSB_ANALOG is not set +CONFIG_DVB_USB_DIB0700=m +CONFIG_DVB_USB_DIB3000MC=m +CONFIG_DVB_USB_DIBUSB_MB=m +# CONFIG_DVB_USB_DIBUSB_MB_FAULTY is not set +CONFIG_DVB_USB_DIBUSB_MC=m +CONFIG_DVB_USB_DIGITV=m +CONFIG_DVB_USB_DTT200U=m +CONFIG_DVB_USB_DTV5100=m +CONFIG_DVB_USB_DW2102=m +CONFIG_DVB_USB_GP8PSK=m +CONFIG_DVB_USB_M920X=m +CONFIG_DVB_USB_NOVA_T_USB2=m +CONFIG_DVB_USB_OPERA1=m +CONFIG_DVB_USB_PCTV452E=m +CONFIG_DVB_USB_TECHNISAT_USB2=m +CONFIG_DVB_USB_TTUSB2=m +CONFIG_DVB_USB_UMT_010=m +CONFIG_DVB_USB_VP702X=m +CONFIG_DVB_USB_VP7045=m +CONFIG_SMS_USB_DRV=m +CONFIG_DVB_TTUSB_BUDGET=m +CONFIG_DVB_TTUSB_DEC=m + +# +# Webcam, TV (analog/digital) USB devices +# +CONFIG_VIDEO_EM28XX=m +CONFIG_VIDEO_EM28XX_V4L2=m +CONFIG_VIDEO_EM28XX_ALSA=m +CONFIG_VIDEO_EM28XX_DVB=m +CONFIG_VIDEO_EM28XX_RC=m + +# +# Software defined radio USB devices +# +CONFIG_USB_AIRSPY=m +CONFIG_USB_HACKRF=m +# CONFIG_USB_MSI2500 is not set +# CONFIG_MEDIA_PCI_SUPPORT is not set +CONFIG_RADIO_ADAPTERS=m +CONFIG_RADIO_MAXIRADIO=m +# CONFIG_RADIO_SAA7706H is not set +CONFIG_RADIO_SHARK=m +CONFIG_RADIO_SHARK2=m +CONFIG_RADIO_SI4713=m +CONFIG_RADIO_TEA575X=m +# CONFIG_RADIO_TEA5764 is not set +# CONFIG_RADIO_TEF6862 is not set +CONFIG_USB_DSBR=m +CONFIG_USB_KEENE=m +CONFIG_USB_MA901=m +CONFIG_USB_MR800=m +CONFIG_USB_RAREMONO=m +CONFIG_RADIO_SI470X=m +CONFIG_USB_SI470X=m +# CONFIG_I2C_SI470X is not set +CONFIG_USB_SI4713=m +# CONFIG_PLATFORM_SI4713 is not set +CONFIG_I2C_SI4713=m +CONFIG_MEDIA_PLATFORM_DRIVERS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +# CONFIG_SDR_PLATFORM_DRIVERS is not set +# CONFIG_DVB_PLATFORM_DRIVERS is not set +CONFIG_V4L_MEM2MEM_DRIVERS=y +# CONFIG_VIDEO_MEM2MEM_DEINTERLACE is not set +# CONFIG_VIDEO_MUX is not set + +# +# Allegro DVT media platform drivers +# + +# +# Amlogic media platform drivers +# + +# +# Amphion drivers +# + +# +# Apple media platform drivers +# +CONFIG_VIDEO_APPLE_ISP=m +CONFIG_VIDEO_APPLE_AVD=m + +# +# ARM media platform drivers +# + +# +# Aspeed media platform drivers +# + +# +# Atmel media platform drivers +# + +# +# Cadence media platform drivers +# +# CONFIG_VIDEO_CADENCE_CSI2RX is not set +# CONFIG_VIDEO_CADENCE_CSI2TX is not set + +# +# Chips&Media media platform drivers +# + +# +# Intel media platform drivers +# + +# +# Marvell media platform drivers +# +# CONFIG_VIDEO_CAFE_CCIC is not set + +# +# Mediatek media platform drivers +# + +# +# Microchip Technology, Inc. media platform drivers +# + +# +# Nuvoton media platform drivers +# + +# +# NVidia media platform drivers +# + +# +# NXP media platform drivers +# + +# +# Qualcomm media platform drivers +# + +# +# Raspberry Pi media platform drivers +# +# CONFIG_VIDEO_RP1_CFE is not set + +# +# Renesas media platform drivers +# + +# +# Rockchip media platform drivers +# + +# +# Samsung media platform drivers +# + +# +# STMicroelectronics media platform drivers +# + +# +# Sunxi media platform drivers +# + +# +# Texas Instruments drivers +# + +# +# Verisilicon media platform drivers +# + +# +# VIA media platform drivers +# + +# +# Xilinx media platform drivers +# +# CONFIG_VIDEO_XILINX is not set + +# +# MMC/SDIO DVB adapters +# +# CONFIG_SMS_SDIO_DRV is not set +# CONFIG_V4L_TEST_DRIVERS is not set +# CONFIG_DVB_TEST_DRIVERS is not set +CONFIG_MEDIA_COMMON_OPTIONS=y + +# +# common driver options +# +CONFIG_CYPRESS_FIRMWARE=m +CONFIG_TTPCI_EEPROM=m +CONFIG_UVC_COMMON=m +CONFIG_VIDEO_CX2341X=m +CONFIG_VIDEO_TVEEPROM=m +CONFIG_DVB_B2C2_FLEXCOP=m +CONFIG_SMS_SIANO_MDTV=m +CONFIG_SMS_SIANO_RC=y +CONFIG_VIDEOBUF2_CORE=m +CONFIG_VIDEOBUF2_V4L2=m +CONFIG_VIDEOBUF2_MEMOPS=m +CONFIG_VIDEOBUF2_DMA_CONTIG=m +CONFIG_VIDEOBUF2_VMALLOC=m +CONFIG_VIDEOBUF2_DMA_SG=m +# end of Media drivers + +# +# Media ancillary drivers +# +CONFIG_MEDIA_ATTACH=y + +# +# IR I2C driver auto-selected by 'Autoselect ancillary drivers' +# +CONFIG_VIDEO_IR_I2C=m +CONFIG_VIDEO_CAMERA_SENSOR=y +# CONFIG_VIDEO_ALVIUM_CSI2 is not set +# CONFIG_VIDEO_AR0521 is not set +# CONFIG_VIDEO_GC0308 is not set +# CONFIG_VIDEO_GC0310 is not set +# CONFIG_VIDEO_GC05A2 is not set +# CONFIG_VIDEO_GC08A3 is not set +# CONFIG_VIDEO_GC2145 is not set +# CONFIG_VIDEO_HI556 is not set +# CONFIG_VIDEO_HI846 is not set +# CONFIG_VIDEO_HI847 is not set +# CONFIG_VIDEO_IMX111 is not set +# CONFIG_VIDEO_IMX208 is not set +# CONFIG_VIDEO_IMX214 is not set +# CONFIG_VIDEO_IMX219 is not set +# CONFIG_VIDEO_IMX258 is not set +# CONFIG_VIDEO_IMX274 is not set +# CONFIG_VIDEO_IMX283 is not set +# CONFIG_VIDEO_IMX290 is not set +# CONFIG_VIDEO_IMX296 is not set +# CONFIG_VIDEO_IMX319 is not set +# CONFIG_VIDEO_IMX334 is not set +# CONFIG_VIDEO_IMX335 is not set +# CONFIG_VIDEO_IMX355 is not set +# CONFIG_VIDEO_IMX412 is not set +# CONFIG_VIDEO_IMX415 is not set +# CONFIG_VIDEO_MT9M001 is not set +# CONFIG_VIDEO_MT9M111 is not set +# CONFIG_VIDEO_MT9M114 is not set +# CONFIG_VIDEO_MT9P031 is not set +# CONFIG_VIDEO_MT9T112 is not set +CONFIG_VIDEO_MT9V011=m +# CONFIG_VIDEO_MT9V032 is not set +# CONFIG_VIDEO_MT9V111 is not set +# CONFIG_VIDEO_OG01A1B is not set +# CONFIG_VIDEO_OG0VE1B is not set +# CONFIG_VIDEO_OS05B10 is not set +# CONFIG_VIDEO_OV01A10 is not set +# CONFIG_VIDEO_OV02A10 is not set +# CONFIG_VIDEO_OV02E10 is not set +# CONFIG_VIDEO_OV02C10 is not set +# CONFIG_VIDEO_OV08D10 is not set +# CONFIG_VIDEO_OV08X40 is not set +# CONFIG_VIDEO_OV13858 is not set +# CONFIG_VIDEO_OV13B10 is not set +CONFIG_VIDEO_OV2640=m +# CONFIG_VIDEO_OV2659 is not set +# CONFIG_VIDEO_OV2680 is not set +# CONFIG_VIDEO_OV2685 is not set +# CONFIG_VIDEO_OV2732 is not set +# CONFIG_VIDEO_OV2735 is not set +# CONFIG_VIDEO_OV4689 is not set +# CONFIG_VIDEO_OV5640 is not set +# CONFIG_VIDEO_OV5645 is not set +# CONFIG_VIDEO_OV5647 is not set +# CONFIG_VIDEO_OV5648 is not set +# CONFIG_VIDEO_OV5670 is not set +# CONFIG_VIDEO_OV5675 is not set +# CONFIG_VIDEO_OV5693 is not set +# CONFIG_VIDEO_OV5695 is not set +# CONFIG_VIDEO_OV6211 is not set +# CONFIG_VIDEO_OV64A40 is not set +# CONFIG_VIDEO_OV7251 is not set +CONFIG_VIDEO_OV7640=m +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_OV772X is not set +# CONFIG_VIDEO_OV7740 is not set +# CONFIG_VIDEO_OV8856 is not set +# CONFIG_VIDEO_OV8858 is not set +# CONFIG_VIDEO_OV8865 is not set +# CONFIG_VIDEO_OV9282 is not set +# CONFIG_VIDEO_OV9640 is not set +# CONFIG_VIDEO_OV9650 is not set +# CONFIG_VIDEO_RDACM20 is not set +# CONFIG_VIDEO_RDACM21 is not set +# CONFIG_VIDEO_RJ54N1 is not set +# CONFIG_VIDEO_S5C73M3 is not set +# CONFIG_VIDEO_S5K3M5 is not set +# CONFIG_VIDEO_S5K5BAF is not set +# CONFIG_VIDEO_S5K6A3 is not set +# CONFIG_VIDEO_S5KJN1 is not set +# CONFIG_VIDEO_VD55G1 is not set +# CONFIG_VIDEO_VD56G3 is not set +# CONFIG_VIDEO_VGXY61 is not set +# CONFIG_VIDEO_CCS is not set +# CONFIG_VIDEO_ET8EK8 is not set + +# +# Camera ISPs +# +# CONFIG_VIDEO_THP7312 is not set +# end of Camera ISPs + +CONFIG_VIDEO_CAMERA_LENS=y +# CONFIG_VIDEO_AD5820 is not set +# CONFIG_VIDEO_AK7375 is not set +# CONFIG_VIDEO_DW9714 is not set +# CONFIG_VIDEO_DW9719 is not set +# CONFIG_VIDEO_DW9768 is not set +# CONFIG_VIDEO_DW9807_VCM is not set + +# +# Flash devices +# +# CONFIG_VIDEO_ADP1653 is not set +# CONFIG_VIDEO_LM3560 is not set +# CONFIG_VIDEO_LM3646 is not set +# end of Flash devices + +# +# Audio decoders, processors and mixers +# +CONFIG_VIDEO_CS3308=m +CONFIG_VIDEO_CS5345=m +CONFIG_VIDEO_CS53L32A=m +CONFIG_VIDEO_MSP3400=m +CONFIG_VIDEO_SONY_BTF_MPX=m +# CONFIG_VIDEO_TDA1997X is not set +CONFIG_VIDEO_TDA7432=m +CONFIG_VIDEO_TDA9840=m +CONFIG_VIDEO_TEA6415C=m +CONFIG_VIDEO_TEA6420=m +# CONFIG_VIDEO_TLV320AIC23B is not set +CONFIG_VIDEO_TVAUDIO=m +CONFIG_VIDEO_UDA1342=m +CONFIG_VIDEO_VP27SMPX=m +CONFIG_VIDEO_WM8739=m +CONFIG_VIDEO_WM8775=m +# end of Audio decoders, processors and mixers + +# +# RDS decoders +# +CONFIG_VIDEO_SAA6588=m +# end of RDS decoders + +# +# Video decoders +# +# CONFIG_VIDEO_ADV7180 is not set +# CONFIG_VIDEO_ADV7183 is not set +# CONFIG_VIDEO_ADV748X is not set +# CONFIG_VIDEO_ADV7604 is not set +# CONFIG_VIDEO_ADV7842 is not set +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_ISL7998X is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_MAX9286 is not set +# CONFIG_VIDEO_ML86V7667 is not set +# CONFIG_VIDEO_SAA7110 is not set +CONFIG_VIDEO_SAA711X=m +# CONFIG_VIDEO_TC358743 is not set +# CONFIG_VIDEO_TC358746 is not set +# CONFIG_VIDEO_TVP514X is not set +CONFIG_VIDEO_TVP5150=m +# CONFIG_VIDEO_TVP7002 is not set +CONFIG_VIDEO_TW2804=m +# CONFIG_VIDEO_TW9900 is not set +CONFIG_VIDEO_TW9903=m +CONFIG_VIDEO_TW9906=m +# CONFIG_VIDEO_TW9910 is not set +# CONFIG_VIDEO_VPX3220 is not set + +# +# Video and audio decoders +# +CONFIG_VIDEO_SAA717X=m +CONFIG_VIDEO_CX25840=m +# end of Video decoders + +# +# Video encoders +# +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set +# CONFIG_VIDEO_ADV7343 is not set +# CONFIG_VIDEO_ADV7393 is not set +# CONFIG_VIDEO_ADV7511 is not set +# CONFIG_VIDEO_AK881X is not set +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_THS8200 is not set +# end of Video encoders + +# +# Video improvement chips +# +CONFIG_VIDEO_UPD64031A=m +CONFIG_VIDEO_UPD64083=m +# end of Video improvement chips + +# +# Audio/Video compression chips +# +CONFIG_VIDEO_SAA6752HS=m +# end of Audio/Video compression chips + +# +# SDR tuner chips +# +# CONFIG_SDR_MAX2175 is not set +# end of SDR tuner chips + +# +# Miscellaneous helper chips +# +# CONFIG_VIDEO_I2C is not set +CONFIG_VIDEO_M52790=m +# CONFIG_VIDEO_ST_MIPID02 is not set +# CONFIG_VIDEO_THS7303 is not set +# end of Miscellaneous helper chips + +# +# Video serializers and deserializers +# +# CONFIG_VIDEO_DS90UB913 is not set +# CONFIG_VIDEO_DS90UB953 is not set +# CONFIG_VIDEO_DS90UB960 is not set +# CONFIG_VIDEO_MAX96714 is not set +# CONFIG_VIDEO_MAX96717 is not set +# end of Video serializers and deserializers + +# +# Media SPI Adapters +# +# CONFIG_CXD2880_SPI_DRV is not set +# CONFIG_VIDEO_GS1662 is not set +# end of Media SPI Adapters + +CONFIG_MEDIA_TUNER=m + +# +# Customize TV tuners +# +CONFIG_MEDIA_TUNER_E4000=m +CONFIG_MEDIA_TUNER_FC0011=m +CONFIG_MEDIA_TUNER_FC0012=m +CONFIG_MEDIA_TUNER_FC0013=m +CONFIG_MEDIA_TUNER_FC2580=m +CONFIG_MEDIA_TUNER_IT913X=m +CONFIG_MEDIA_TUNER_M88RS6000T=m +CONFIG_MEDIA_TUNER_MAX2165=m +CONFIG_MEDIA_TUNER_MC44S803=m +# CONFIG_MEDIA_TUNER_MSI001 is not set +CONFIG_MEDIA_TUNER_MT2060=m +CONFIG_MEDIA_TUNER_MT2063=m +CONFIG_MEDIA_TUNER_MT20XX=m +CONFIG_MEDIA_TUNER_MT2131=m +CONFIG_MEDIA_TUNER_MT2266=m +CONFIG_MEDIA_TUNER_MXL301RF=m +CONFIG_MEDIA_TUNER_MXL5005S=m +CONFIG_MEDIA_TUNER_MXL5007T=m +CONFIG_MEDIA_TUNER_QM1D1B0004=m +CONFIG_MEDIA_TUNER_QM1D1C0042=m +CONFIG_MEDIA_TUNER_QT1010=m +CONFIG_MEDIA_TUNER_R820T=m +CONFIG_MEDIA_TUNER_SI2157=m +CONFIG_MEDIA_TUNER_SIMPLE=m +CONFIG_MEDIA_TUNER_TDA18212=m +CONFIG_MEDIA_TUNER_TDA18218=m +CONFIG_MEDIA_TUNER_TDA18250=m +CONFIG_MEDIA_TUNER_TDA18271=m +CONFIG_MEDIA_TUNER_TDA827X=m +CONFIG_MEDIA_TUNER_TDA8290=m +CONFIG_MEDIA_TUNER_TDA9887=m +CONFIG_MEDIA_TUNER_TEA5761=m +CONFIG_MEDIA_TUNER_TEA5767=m +CONFIG_MEDIA_TUNER_TUA9001=m +CONFIG_MEDIA_TUNER_XC2028=m +CONFIG_MEDIA_TUNER_XC4000=m +CONFIG_MEDIA_TUNER_XC5000=m +# end of Customize TV tuners + +# +# Customise DVB Frontends +# + +# +# Multistandard (satellite) frontends +# +CONFIG_DVB_M88DS3103=m +CONFIG_DVB_MXL5XX=m +CONFIG_DVB_STB0899=m +CONFIG_DVB_STB6100=m +CONFIG_DVB_STV090x=m +CONFIG_DVB_STV0910=m +CONFIG_DVB_STV6110x=m +CONFIG_DVB_STV6111=m + +# +# Multistandard (cable + terrestrial) frontends +# +CONFIG_DVB_DRXK=m +CONFIG_DVB_MN88472=m +CONFIG_DVB_MN88473=m +CONFIG_DVB_SI2165=m +CONFIG_DVB_TDA18271C2DD=m + +# +# DVB-S (satellite) frontends +# +CONFIG_DVB_CX24110=m +CONFIG_DVB_CX24116=m +CONFIG_DVB_CX24117=m +CONFIG_DVB_CX24120=m +CONFIG_DVB_CX24123=m +CONFIG_DVB_DS3000=m +CONFIG_DVB_MB86A16=m +CONFIG_DVB_MT312=m +CONFIG_DVB_S5H1420=m +CONFIG_DVB_SI21XX=m +CONFIG_DVB_STB6000=m +CONFIG_DVB_STV0288=m +CONFIG_DVB_STV0299=m +CONFIG_DVB_STV0900=m +CONFIG_DVB_STV6110=m +CONFIG_DVB_TDA10071=m +CONFIG_DVB_TDA10086=m +CONFIG_DVB_TDA8083=m +CONFIG_DVB_TDA8261=m +CONFIG_DVB_TDA826X=m +CONFIG_DVB_TS2020=m +CONFIG_DVB_TUA6100=m +CONFIG_DVB_TUNER_CX24113=m +CONFIG_DVB_TUNER_ITD1000=m +CONFIG_DVB_VES1X93=m +CONFIG_DVB_ZL10036=m +CONFIG_DVB_ZL10039=m + +# +# DVB-T (terrestrial) frontends +# +CONFIG_DVB_AF9013=m +CONFIG_DVB_AS102_FE=m +CONFIG_DVB_CX22700=m +CONFIG_DVB_CX22702=m +CONFIG_DVB_CXD2820R=m +CONFIG_DVB_CXD2841ER=m +CONFIG_DVB_DIB3000MB=m +CONFIG_DVB_DIB3000MC=m +CONFIG_DVB_DIB7000M=m +CONFIG_DVB_DIB7000P=m +# CONFIG_DVB_DIB9000 is not set +CONFIG_DVB_DRXD=m +CONFIG_DVB_EC100=m +CONFIG_DVB_GP8PSK_FE=m +CONFIG_DVB_L64781=m +CONFIG_DVB_MT352=m +CONFIG_DVB_NXT6000=m +CONFIG_DVB_RTL2830=m +CONFIG_DVB_RTL2832=m +CONFIG_DVB_RTL2832_SDR=m +# CONFIG_DVB_S5H1432 is not set +CONFIG_DVB_SI2168=m +CONFIG_DVB_SP887X=m +CONFIG_DVB_STV0367=m +CONFIG_DVB_TDA10048=m +CONFIG_DVB_TDA1004X=m +CONFIG_DVB_ZD1301_DEMOD=m +CONFIG_DVB_ZL10353=m +# CONFIG_DVB_CXD2880 is not set + +# +# DVB-C (cable) frontends +# +CONFIG_DVB_STV0297=m +CONFIG_DVB_TDA10021=m +CONFIG_DVB_TDA10023=m +CONFIG_DVB_VES1820=m + +# +# ATSC (North American/Korean Terrestrial/Cable DTV) frontends +# +CONFIG_DVB_AU8522=m +CONFIG_DVB_AU8522_DTV=m +CONFIG_DVB_AU8522_V4L=m +CONFIG_DVB_BCM3510=m +CONFIG_DVB_LG2160=m +CONFIG_DVB_LGDT3305=m +CONFIG_DVB_LGDT3306A=m +CONFIG_DVB_LGDT330X=m +CONFIG_DVB_MXL692=m +CONFIG_DVB_NXT200X=m +CONFIG_DVB_OR51132=m +CONFIG_DVB_OR51211=m +CONFIG_DVB_S5H1409=m +CONFIG_DVB_S5H1411=m + +# +# ISDB-T (terrestrial) frontends +# +CONFIG_DVB_DIB8000=m +CONFIG_DVB_MB86A20S=m +CONFIG_DVB_S921=m + +# +# ISDB-S (satellite) & ISDB-T (terrestrial) frontends +# +# CONFIG_DVB_MN88443X is not set +CONFIG_DVB_TC90522=m + +# +# Digital terrestrial only tuners/PLL +# +CONFIG_DVB_PLL=m +CONFIG_DVB_TUNER_DIB0070=m +CONFIG_DVB_TUNER_DIB0090=m + +# +# SEC control devices for DVB-S +# +CONFIG_DVB_A8293=m +CONFIG_DVB_AF9033=m +# CONFIG_DVB_ASCOT2E is not set +CONFIG_DVB_ATBM8830=m +# CONFIG_DVB_HELENE is not set +# CONFIG_DVB_HORUS3A is not set +CONFIG_DVB_ISL6405=m +CONFIG_DVB_ISL6421=m +CONFIG_DVB_ISL6423=m +CONFIG_DVB_IX2505V=m +# CONFIG_DVB_LGS8GL5 is not set +CONFIG_DVB_LGS8GXX=m +CONFIG_DVB_LNBH25=m +# CONFIG_DVB_LNBH29 is not set +CONFIG_DVB_LNBP21=m +CONFIG_DVB_LNBP22=m +CONFIG_DVB_M88RS2000=m +CONFIG_DVB_TDA665x=m +CONFIG_DVB_DRX39XYJ=m + +# +# Common Interface (EN50221) controller drivers +# +CONFIG_DVB_CXD2099=m +CONFIG_DVB_SP2=m +# end of Customise DVB Frontends + +# +# Tools to develop new frontends +# +# CONFIG_DVB_DUMMY_FE is not set +# end of Media ancillary drivers + +# +# Graphics support +# +CONFIG_APERTURE_HELPERS=y +CONFIG_VIDEO=y +# CONFIG_AUXDISPLAY is not set +# CONFIG_NOVA_CORE is not set +CONFIG_DRM=y + +# +# DRM debugging options +# +# CONFIG_DRM_WERROR is not set +# CONFIG_DRM_DEBUG_MM is not set +# end of DRM debugging options + +CONFIG_DRM_MIPI_DSI=y +CONFIG_DRM_KMS_HELPER=y +# CONFIG_DRM_PANIC is not set +# CONFIG_DRM_RAS is not set +# CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS is not set +# CONFIG_DRM_DEBUG_MODESET_LOCK is not set +CONFIG_DRM_CLIENT=y +CONFIG_DRM_CLIENT_LIB=y +CONFIG_DRM_CLIENT_SELECTION=y +CONFIG_DRM_CLIENT_SETUP=y + +# +# Supported DRM clients +# +CONFIG_DRM_FBDEV_EMULATION=y +CONFIG_DRM_FBDEV_OVERALLOC=100 +# CONFIG_DRM_FBDEV_LEAK_PHYS_SMEM is not set +# CONFIG_DRM_CLIENT_LOG is not set +CONFIG_DRM_CLIENT_DEFAULT_FBDEV=y +CONFIG_DRM_CLIENT_DEFAULT="fbdev" +# end of Supported DRM clients + +CONFIG_DRM_LOAD_EDID_FIRMWARE=y +CONFIG_DRM_DISPLAY_HELPER=m +CONFIG_DRM_BRIDGE_CONNECTOR=y +# CONFIG_DRM_DISPLAY_DP_AUX_CEC is not set +# CONFIG_DRM_DISPLAY_DP_AUX_CHARDEV is not set +CONFIG_DRM_DISPLAY_HDMI_AUDIO_HELPER=y +CONFIG_DRM_DISPLAY_HDMI_CEC_HELPER=y +CONFIG_DRM_DISPLAY_HDMI_HELPER=y +CONFIG_DRM_DISPLAY_HDMI_STATE_HELPER=y +CONFIG_DRM_EXEC=y +CONFIG_DRM_GPUVM=y +CONFIG_DRM_GEM_DMA_HELPER=m +CONFIG_DRM_GEM_SHMEM_HELPER=y +CONFIG_RUST_DRM_GEM_SHMEM_HELPER=y +CONFIG_DRM_SCHED=y +CONFIG_DRM_ADP=m +# CONFIG_DRM_AMDGPU is not set +CONFIG_DRM_APPLE=m +CONFIG_DRM_APPLE_AUDIO=y +# CONFIG_DRM_APPLE_DEBUG is not set + +# +# ARM devices +# +# CONFIG_DRM_HDLCD is not set +# CONFIG_DRM_MALI_DISPLAY is not set +# CONFIG_DRM_KOMEDA is not set +# end of ARM devices + +CONFIG_RUST_DRM_SCHED=y +CONFIG_RUST_DRM_GPUVM=y +CONFIG_DRM_ASAHI=y +# CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR is not set +# CONFIG_DRM_AST is not set +CONFIG_DRM_BRIDGE=y +CONFIG_DRM_PANEL_BRIDGE=y + +# +# Display Interface Bridges +# +# CONFIG_DRM_CHIPONE_ICN6211 is not set +# CONFIG_DRM_CHRONTEL_CH7033 is not set +# CONFIG_DRM_DISPLAY_CONNECTOR is not set +# CONFIG_DRM_I2C_NXP_TDA998X is not set +# CONFIG_DRM_ITE_IT6263 is not set +# CONFIG_DRM_ITE_IT6505 is not set +# CONFIG_DRM_LONTIUM_LT8912B is not set +# CONFIG_DRM_LONTIUM_LT9211 is not set +# CONFIG_DRM_LONTIUM_LT9611 is not set +# CONFIG_DRM_LONTIUM_LT9611UXC is not set +# CONFIG_DRM_LONTIUM_LT8713SX is not set +# CONFIG_DRM_ITE_IT66121 is not set +# CONFIG_DRM_LVDS_CODEC is not set +# CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW is not set +# CONFIG_DRM_NWL_MIPI_DSI is not set +# CONFIG_DRM_NXP_PTN3460 is not set +# CONFIG_DRM_PARADE_PS8622 is not set +# CONFIG_DRM_PARADE_PS8640 is not set +# CONFIG_DRM_SAMSUNG_DSIM is not set +# CONFIG_DRM_SIL_SII8620 is not set +# CONFIG_DRM_SII902X is not set +# CONFIG_DRM_SII9234 is not set +# CONFIG_DRM_SIMPLE_BRIDGE is not set +# CONFIG_DRM_SOLOMON_SSD2825 is not set +# CONFIG_DRM_THINE_THC63LVD1024 is not set +# CONFIG_DRM_TOSHIBA_TC358762 is not set +# CONFIG_DRM_TOSHIBA_TC358764 is not set +# CONFIG_DRM_TOSHIBA_TC358767 is not set +# CONFIG_DRM_TOSHIBA_TC358768 is not set +# CONFIG_DRM_TOSHIBA_TC358775 is not set +# CONFIG_DRM_TI_DLPC3433 is not set +# CONFIG_DRM_TI_TDP158 is not set +# CONFIG_DRM_TI_TFP410 is not set +# CONFIG_DRM_TI_SN65DSI83 is not set +# CONFIG_DRM_TI_SN65DSI86 is not set +# CONFIG_DRM_TI_TPD12S015 is not set +# CONFIG_DRM_WAVESHARE_BRIDGE is not set +# CONFIG_DRM_ANALOGIX_ANX6345 is not set +# CONFIG_DRM_ANALOGIX_ANX78XX is not set +# CONFIG_DRM_ANALOGIX_ANX7625 is not set +# CONFIG_DRM_I2C_ADV7511 is not set +# CONFIG_DRM_CDNS_DSI is not set +# CONFIG_DRM_CDNS_MHDP8546 is not set +# end of Display Interface Bridges + +# CONFIG_DRM_ETNAVIV is not set +# CONFIG_DRM_GUD is not set +# CONFIG_DRM_HISI_HIBMC is not set +# CONFIG_DRM_HISI_KIRIN is not set +# CONFIG_DRM_POWERVR is not set +# CONFIG_DRM_LIMA is not set +# CONFIG_DRM_LOGICVC is not set +# CONFIG_DRM_MGAG200 is not set +# CONFIG_DRM_NOUVEAU is not set +# CONFIG_DRM_NOVA is not set +CONFIG_DRM_PANEL=y + +# +# Display Panels +# +# CONFIG_DRM_PANEL_ABT_Y030XX067A is not set +# CONFIG_DRM_PANEL_ARM_VERSATILE is not set +# CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596 is not set +# CONFIG_DRM_PANEL_AUO_A030JTN01 is not set +# CONFIG_DRM_PANEL_BOE_BF060Y8M_AJ0 is not set +# CONFIG_DRM_PANEL_BOE_HIMAX8279D is not set +# CONFIG_DRM_PANEL_BOE_TD4320 is not set +# CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A is not set +# CONFIG_DRM_PANEL_BOE_TV101WUM_NL6 is not set +# CONFIG_DRM_PANEL_BOE_TV101WUM_LL2 is not set +# CONFIG_DRM_PANEL_EBBG_FT8719 is not set +# CONFIG_DRM_PANEL_ELIDA_KD35T133 is not set +# CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02 is not set +# CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D is not set +# CONFIG_DRM_PANEL_DSI_CM is not set +# CONFIG_DRM_PANEL_LVDS is not set +# CONFIG_DRM_PANEL_HIMAX_HX8279 is not set +# CONFIG_DRM_PANEL_HIMAX_HX83102 is not set +# CONFIG_DRM_PANEL_HIMAX_HX83112A is not set +# CONFIG_DRM_PANEL_HIMAX_HX83112B is not set +# CONFIG_DRM_PANEL_HIMAX_HX83121A is not set +# CONFIG_DRM_PANEL_HIMAX_HX8394 is not set +# CONFIG_DRM_PANEL_HYDIS_HV101HD1 is not set +# CONFIG_DRM_PANEL_ILITEK_IL9322 is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9341 is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9805 is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9806E_DSI is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9881C is not set +# CONFIG_DRM_PANEL_ILITEK_ILI9882T is not set +# CONFIG_DRM_PANEL_INNOLUX_EJ030NA is not set +# CONFIG_DRM_PANEL_INNOLUX_P079ZCA is not set +# CONFIG_DRM_PANEL_JADARD_JD9365DA_H3 is not set +# CONFIG_DRM_PANEL_JDI_LPM102A188A is not set +# CONFIG_DRM_PANEL_JDI_LT070ME05000 is not set +# CONFIG_DRM_PANEL_JDI_R63452 is not set +# CONFIG_DRM_PANEL_KHADAS_TS050 is not set +# CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04 is not set +# CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W is not set +# CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829 is not set +# CONFIG_DRM_PANEL_LINCOLNTECH_LCD197 is not set +# CONFIG_DRM_PANEL_LG_LB035Q02 is not set +# CONFIG_DRM_PANEL_LG_LD070WX3 is not set +# CONFIG_DRM_PANEL_LG_LG4573 is not set +# CONFIG_DRM_PANEL_LG_SW43408 is not set +# CONFIG_DRM_PANEL_LXD_M9189A is not set +# CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966 is not set +# CONFIG_DRM_PANEL_MANTIX_MLAF057WE51 is not set +# CONFIG_DRM_PANEL_MOTOROLA_MOT is not set +# CONFIG_DRM_PANEL_NEC_NL8048HL11 is not set +# CONFIG_DRM_PANEL_NEWVISION_NV3051D is not set +# CONFIG_DRM_PANEL_NEWVISION_NV3052C is not set +# CONFIG_DRM_PANEL_NOVATEK_NT35510 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT35560 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT35950 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT36523 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT36672A is not set +# CONFIG_DRM_PANEL_NOVATEK_NT36672E is not set +# CONFIG_DRM_PANEL_NOVATEK_NT37700F is not set +# CONFIG_DRM_PANEL_NOVATEK_NT37801 is not set +# CONFIG_DRM_PANEL_NOVATEK_NT39016 is not set +# CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO is not set +# CONFIG_DRM_PANEL_ORISETECH_OTA5601A is not set +# CONFIG_DRM_PANEL_ORISETECH_OTM8009A is not set +# CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS is not set +# CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00 is not set +# CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM67191 is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM67200 is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM68200 is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM692E5 is not set +# CONFIG_DRM_PANEL_RAYDIUM_RM69380 is not set +# CONFIG_DRM_PANEL_RENESAS_R61307 is not set +# CONFIG_DRM_PANEL_RENESAS_R69328 is not set +# CONFIG_DRM_PANEL_RONBO_RB070D30 is not set +# CONFIG_DRM_PANEL_SAMSUNG_AMS581VF01 is not set +# CONFIG_DRM_PANEL_SAMSUNG_AMS639RQ08 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS427AP24 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E88A0_AMS452EF01 is not set +# CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20 is not set +# CONFIG_DRM_PANEL_SAMSUNG_DB7430 is not set +# CONFIG_DRM_PANEL_SAMSUNG_LD9040 is not set +# CONFIG_DRM_PANEL_SAMSUNG_LTL106HL02 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E3FA7 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6D16D0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6D27A1 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6D7AA0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E3FC2X01 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E3HA2 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E3HA8 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E63M0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E8AA5X01_AMS561RA01 is not set +# CONFIG_DRM_PANEL_SAMSUNG_S6E8FC0 is not set +# CONFIG_DRM_PANEL_SAMSUNG_SOFEF00 is not set +# CONFIG_DRM_PANEL_SEIKO_43WVF1G is not set +# CONFIG_DRM_PANEL_SHARP_LQ079L1SX01 is not set +# CONFIG_DRM_PANEL_SHARP_LQ101R1SX01 is not set +# CONFIG_DRM_PANEL_SHARP_LS037V7DW01 is not set +# CONFIG_DRM_PANEL_SHARP_LS043T1LE01 is not set +# CONFIG_DRM_PANEL_SHARP_LS060T1SX01 is not set +# CONFIG_DRM_PANEL_SITRONIX_ST7701 is not set +# CONFIG_DRM_PANEL_SITRONIX_ST7703 is not set +# CONFIG_DRM_PANEL_SITRONIX_ST7789V is not set +# CONFIG_DRM_PANEL_SONY_ACX565AKM is not set +# CONFIG_DRM_PANEL_SONY_TD4353_JDI is not set +# CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521 is not set +# CONFIG_DRM_PANEL_STARTEK_KD070FHFID015 is not set +# CONFIG_DRM_PANEL_EDP is not set +CONFIG_DRM_PANEL_SIMPLE=m +CONFIG_DRM_PANEL_SUMMIT=m +# CONFIG_DRM_PANEL_SYNAPTICS_R63353 is not set +# CONFIG_DRM_PANEL_SYNAPTICS_TDDI is not set +# CONFIG_DRM_PANEL_TDO_TL070WSH30 is not set +# CONFIG_DRM_PANEL_TPO_TD028TTEC1 is not set +# CONFIG_DRM_PANEL_TPO_TD043MTEA1 is not set +# CONFIG_DRM_PANEL_TPO_TPG110 is not set +# CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA is not set +# CONFIG_DRM_PANEL_VISIONOX_G2647FB105 is not set +# CONFIG_DRM_PANEL_VISIONOX_R66451 is not set +# CONFIG_DRM_PANEL_VISIONOX_RM69299 is not set +# CONFIG_DRM_PANEL_VISIONOX_RM692E5 is not set +# CONFIG_DRM_PANEL_VISIONOX_VTDR6130 is not set +# CONFIG_DRM_PANEL_WIDECHIPS_WS2401 is not set +# CONFIG_DRM_PANEL_XINPENG_XPP055C272 is not set +# end of Display Panels + +# CONFIG_DRM_PANFROST is not set +# CONFIG_DRM_PANTHOR is not set +# CONFIG_DRM_PL111 is not set +# CONFIG_DRM_QXL is not set +# CONFIG_DRM_RADEON is not set +# CONFIG_DRM_ST7571 is not set +# CONFIG_DRM_ST7586 is not set +# CONFIG_DRM_ST7735R is not set +# CONFIG_DRM_ST7920 is not set +# CONFIG_DRM_SSD130X is not set + +# +# Drivers for system framebuffers +# +CONFIG_DRM_SYSFB_HELPER=y +# CONFIG_DRM_EFIDRM is not set +CONFIG_DRM_SIMPLEDRM=y +# end of Drivers for system framebuffers + +# CONFIG_DRM_TIDSS is not set +# CONFIG_DRM_ARCPGU is not set +CONFIG_DRM_BOCHS=y +# CONFIG_DRM_CIRRUS_QEMU is not set +# CONFIG_DRM_GM12U320 is not set +# CONFIG_DRM_PANEL_MIPI_DBI is not set +# CONFIG_DRM_PIXPAPER is not set +# CONFIG_TINYDRM_HX8357D is not set +# CONFIG_TINYDRM_ILI9163 is not set +# CONFIG_TINYDRM_ILI9225 is not set +# CONFIG_TINYDRM_ILI9341 is not set +# CONFIG_TINYDRM_ILI9486 is not set +# CONFIG_TINYDRM_MI0283QT is not set +# CONFIG_TINYDRM_REPAPER is not set +# CONFIG_TINYDRM_SHARP_MEMORY is not set +# CONFIG_DRM_TYR is not set +# CONFIG_DRM_UDL is not set +# CONFIG_DRM_VGEM is not set +CONFIG_DRM_VIRTIO_GPU=m +CONFIG_DRM_VIRTIO_GPU_KMS=y +# CONFIG_DRM_VKMS is not set +# CONFIG_DRM_VMWGFX is not set +CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y + +# +# Frame buffer Devices +# +CONFIG_FB=y +# CONFIG_FB_CIRRUS is not set +# CONFIG_FB_PM2 is not set +# CONFIG_FB_CYBER2000 is not set +# CONFIG_FB_ASILIANT is not set +# CONFIG_FB_IMSTT is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_EFI is not set +# CONFIG_FB_OPENCORES is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_NVIDIA is not set +# CONFIG_FB_RIVA is not set +# CONFIG_FB_I740 is not set +# CONFIG_FB_MATROX is not set +# CONFIG_FB_RADEON is not set +# CONFIG_FB_ATY128 is not set +# CONFIG_FB_ATY is not set +# CONFIG_FB_S3 is not set +# CONFIG_FB_SAVAGE is not set +# CONFIG_FB_SIS is not set +# CONFIG_FB_NEOMAGIC is not set +# CONFIG_FB_KYRO is not set +# CONFIG_FB_3DFX is not set +# CONFIG_FB_VOODOO1 is not set +# CONFIG_FB_VT8623 is not set +# CONFIG_FB_TRIDENT is not set +# CONFIG_FB_ARK is not set +# CONFIG_FB_PM3 is not set +# CONFIG_FB_CARMINE is not set +# CONFIG_FB_SMSCUFX is not set +CONFIG_FB_UDL=m +# CONFIG_FB_IBM_GXT4500 is not set +# CONFIG_FB_VIRTUAL is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_MB862XX is not set +# CONFIG_FB_SSD1307 is not set +# CONFIG_FB_SM712 is not set +CONFIG_FB_CORE=y +CONFIG_FB_NOTIFY=y +CONFIG_FB_DEVICE=y +CONFIG_FB_SYS_FILLRECT=y +CONFIG_FB_SYS_COPYAREA=y +CONFIG_FB_SYS_IMAGEBLIT=y +# CONFIG_FB_FOREIGN_ENDIAN is not set +CONFIG_FB_SYSMEM_FOPS=y +CONFIG_FB_DEFERRED_IO=y +CONFIG_FB_DMAMEM_HELPERS=y +CONFIG_FB_DMAMEM_HELPERS_DEFERRED=y +CONFIG_FB_SYSMEM_HELPERS=y +CONFIG_FB_SYSMEM_HELPERS_DEFERRED=y +CONFIG_FB_MODE_HELPERS=y +CONFIG_FB_TILEBLITTING=y +# end of Frame buffer Devices + +# +# Backlight & LCD device support +# +CONFIG_LCD_CLASS_DEVICE=m +# CONFIG_LCD_L4F00242T03 is not set +# CONFIG_LCD_LMS283GF05 is not set +# CONFIG_LCD_LTV350QV is not set +# CONFIG_LCD_ILI922X is not set +# CONFIG_LCD_ILI9320 is not set +# CONFIG_LCD_TDO24M is not set +# CONFIG_LCD_VGG2432A4 is not set +CONFIG_LCD_PLATFORM=m +# CONFIG_LCD_AMS369FG06 is not set +# CONFIG_LCD_LMS501KF03 is not set +# CONFIG_LCD_HX8357 is not set +# CONFIG_LCD_OTM3225A is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_AW99706 is not set +# CONFIG_BACKLIGHT_KTD253 is not set +# CONFIG_BACKLIGHT_KTD2801 is not set +# CONFIG_BACKLIGHT_KTZ8866 is not set +# CONFIG_BACKLIGHT_PWM is not set +# CONFIG_BACKLIGHT_APPLE_DWI is not set +# CONFIG_BACKLIGHT_QCOM_WLED is not set +# CONFIG_BACKLIGHT_ADP8860 is not set +# CONFIG_BACKLIGHT_ADP8870 is not set +# CONFIG_BACKLIGHT_LM3509 is not set +# CONFIG_BACKLIGHT_LM3630A is not set +# CONFIG_BACKLIGHT_LM3639 is not set +# CONFIG_BACKLIGHT_LP855X is not set +# CONFIG_BACKLIGHT_MP3309C is not set +CONFIG_BACKLIGHT_GPIO=m +# CONFIG_BACKLIGHT_LV5207LP is not set +# CONFIG_BACKLIGHT_BD6107 is not set +# CONFIG_BACKLIGHT_ARCXCNN is not set +# CONFIG_BACKLIGHT_LED is not set +# end of Backlight & LCD device support + +CONFIG_VIDEOMODE_HELPERS=y +CONFIG_HDMI=y +# CONFIG_FIRMWARE_EDID is not set + +# +# Console display driver support +# +CONFIG_DUMMY_CONSOLE=y +CONFIG_DUMMY_CONSOLE_COLUMNS=80 +CONFIG_DUMMY_CONSOLE_ROWS=25 +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION=y +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +# CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER is not set +# end of Console display driver support + +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_LOGO_LINUX_CLUT224=y +CONFIG_LOGO_LINUX_CLUT224_FILE="drivers/video/logo/logo_linux_clut224.ppm" +CONFIG_TRACE_GPU_MEM=y +# end of Graphics support + +CONFIG_DRM_ACCEL=y +# CONFIG_DRM_ACCEL_ARM_ETHOSU is not set +CONFIG_SOUND=y +CONFIG_SOUND_OSS_CORE=y +CONFIG_SOUND_OSS_CORE_PRECLAIM=y +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +CONFIG_SND_DMAENGINE_PCM=y +CONFIG_SND_HWDEP=m +CONFIG_SND_SEQ_DEVICE=m +CONFIG_SND_RAWMIDI=m +CONFIG_SND_COMPRESS_OFFLOAD=y +CONFIG_SND_COMPRESS_ACCEL=y +CONFIG_SND_JACK=y +CONFIG_SND_JACK_INPUT_DEV=y +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=m +CONFIG_SND_PCM_OSS=m +CONFIG_SND_PCM_OSS_PLUGINS=y +CONFIG_SND_PCM_TIMER=y +CONFIG_SND_HRTIMER=m +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_MAX_CARDS=32 +# CONFIG_SND_SUPPORT_OLD_API is not set +CONFIG_SND_PROC_FS=y +CONFIG_SND_VERBOSE_PROCFS=y +CONFIG_SND_CTL_FAST_LOOKUP=y +# CONFIG_SND_DEBUG is not set +CONFIG_SND_CTL_INPUT_VALIDATION=y +# CONFIG_SND_UTIMER is not set +CONFIG_SND_VMASTER=y +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_SEQUENCER_OSS=m +CONFIG_SND_SEQ_HRTIMER_DEFAULT=y +CONFIG_SND_SEQ_MIDI_EVENT=m +CONFIG_SND_SEQ_MIDI=m +CONFIG_SND_SEQ_VIRMIDI=m +# CONFIG_SND_SEQ_UMP is not set +CONFIG_SND_MPU401_UART=m +CONFIG_SND_DRIVERS=y +CONFIG_SND_DUMMY=m +CONFIG_SND_ALOOP=m +# CONFIG_SND_PCMTEST is not set +CONFIG_SND_VIRMIDI=m +CONFIG_SND_MTPAV=m +CONFIG_SND_SERIAL_U16550=m +# CONFIG_SND_SERIAL_GENERIC is not set +CONFIG_SND_MPU401=m +# CONFIG_SND_PCI is not set + +# +# HD-Audio +# +# CONFIG_SND_HDA_INTEL is not set +# end of HD-Audio + +# CONFIG_SND_SPI is not set +CONFIG_SND_USB=y +CONFIG_SND_USB_AUDIO=m +# CONFIG_SND_USB_AUDIO_MIDI_V2 is not set +CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER=y +CONFIG_SND_USB_UA101=m +CONFIG_SND_USB_CAIAQ=m +CONFIG_SND_USB_CAIAQ_INPUT=y +CONFIG_SND_USB_6FIRE=m +CONFIG_SND_USB_HIFACE=m +# CONFIG_SND_BCD2000 is not set +CONFIG_SND_USB_LINE6=m +CONFIG_SND_USB_POD=m +CONFIG_SND_USB_PODHD=m +CONFIG_SND_USB_TONEPORT=m +CONFIG_SND_USB_VARIAX=m +CONFIG_SND_SOC=y +CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y +CONFIG_SND_SOC_COMPRESS=y +# CONFIG_SND_SOC_USB is not set + +# +# Analog Devices +# +# CONFIG_SND_SOC_ADI_AXI_I2S is not set +# CONFIG_SND_SOC_ADI_AXI_SPDIF is not set +# end of Analog Devices + +# +# AMD +# +# CONFIG_SND_SOC_AMD_ACP is not set +# CONFIG_SND_AMD_ACP_CONFIG is not set +# end of AMD + +# +# Apple +# +CONFIG_SND_SOC_APPLE_AOP_AUDIO=m +CONFIG_SND_SOC_APPLE_MCA=m +CONFIG_SND_SOC_APPLE_MACAUDIO=m +# end of Apple + +# +# Atmel +# +# CONFIG_SND_SOC_MIKROE_PROTO is not set +# end of Atmel + +# +# Au1x +# +# end of Au1x + +# +# Broadcom +# +# CONFIG_SND_BCM63XX_I2S_WHISTLER is not set +# end of Broadcom + +# +# Cirrus Logic +# +# end of Cirrus Logic + +# +# DesignWare +# +# CONFIG_SND_DESIGNWARE_I2S is not set +# end of DesignWare + +# +# Freescale +# + +# +# Common SoC Audio options for Freescale CPUs: +# +CONFIG_SND_SOC_FSL_ASRC=m +CONFIG_SND_SOC_FSL_SAI=m +# CONFIG_SND_SOC_FSL_MQS is not set +CONFIG_SND_SOC_FSL_AUDMIX=m +CONFIG_SND_SOC_FSL_SSI=m +CONFIG_SND_SOC_FSL_SPDIF=m +CONFIG_SND_SOC_FSL_ESAI=m +CONFIG_SND_SOC_FSL_MICFIL=m +CONFIG_SND_SOC_FSL_EASRC=m +# CONFIG_SND_SOC_FSL_XCVR is not set +CONFIG_SND_SOC_FSL_UTILS=m +CONFIG_SND_SOC_IMX_AUDMUX=m +# end of Freescale + +# +# Google +# +# CONFIG_SND_SOC_CHV3_I2S is not set +# end of Google + +# +# Hisilicon +# +# CONFIG_SND_I2S_HI6210_I2S is not set +# end of Hisilicon + +# +# JZ4740 +# +# end of JZ4740 + +# +# Kirkwood +# +# end of Kirkwood + +# +# Loongson +# +# end of Loongson + +# +# Intel +# +# end of Intel + +# +# Mediatek +# +# CONFIG_SND_SOC_MTK_BTCVSD is not set +# end of Mediatek + +# +# PXA +# +# end of PXA + +# +# SoundWire (SDCA) +# +CONFIG_SND_SOC_SDCA_OPTIONAL=y +# end of SoundWire (SDCA) + +# +# ST SPEAr +# +# end of ST SPEAr + +# +# Spreadtrum +# +# end of Spreadtrum + +# +# STMicroelectronics STM32 +# +# end of STMicroelectronics STM32 + +# +# Tegra +# +# end of Tegra + +# +# Xilinx +# +# CONFIG_SND_SOC_XILINX_I2S is not set +# CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER is not set +# CONFIG_SND_SOC_XILINX_SPDIF is not set +# end of Xilinx + +# +# Xtensa +# +# CONFIG_SND_SOC_XTFPGA_I2S is not set +# end of Xtensa + +# CONFIG_SND_SOC_SOF_TOPLEVEL is not set +CONFIG_SND_SOC_I2C_AND_SPI=y + +# +# CODEC drivers +# +CONFIG_SND_SOC_WM_ADSP=m +# CONFIG_SND_SOC_AC97_CODEC is not set +# CONFIG_SND_SOC_ADAU1372_I2C is not set +# CONFIG_SND_SOC_ADAU1372_SPI is not set +# CONFIG_SND_SOC_ADAU1373 is not set +# CONFIG_SND_SOC_ADAU1701 is not set +# CONFIG_SND_SOC_ADAU1761_I2C is not set +# CONFIG_SND_SOC_ADAU1761_SPI is not set +# CONFIG_SND_SOC_ADAU7002 is not set +# CONFIG_SND_SOC_ADAU7118_HW is not set +# CONFIG_SND_SOC_ADAU7118_I2C is not set +# CONFIG_SND_SOC_AK4104 is not set +# CONFIG_SND_SOC_AK4118 is not set +# CONFIG_SND_SOC_AK4375 is not set +# CONFIG_SND_SOC_AK4458 is not set +# CONFIG_SND_SOC_AK4554 is not set +# CONFIG_SND_SOC_AK4613 is not set +# CONFIG_SND_SOC_AK4619 is not set +# CONFIG_SND_SOC_AK4642 is not set +# CONFIG_SND_SOC_AK5386 is not set +# CONFIG_SND_SOC_AK5558 is not set +# CONFIG_SND_SOC_ALC5623 is not set +# CONFIG_SND_SOC_AUDIO_IIO_AUX is not set +# CONFIG_SND_SOC_AW8738 is not set +# CONFIG_SND_SOC_AW88395 is not set +# CONFIG_SND_SOC_AW88166 is not set +# CONFIG_SND_SOC_AW88261 is not set +# CONFIG_SND_SOC_AW88081 is not set +# CONFIG_SND_SOC_AW87390 is not set +# CONFIG_SND_SOC_AW88399 is not set +# CONFIG_SND_SOC_BD28623 is not set +CONFIG_SND_SOC_BT_SCO=m +# CONFIG_SND_SOC_CHV3_CODEC is not set +CONFIG_SND_SOC_CS35L32=m +CONFIG_SND_SOC_CS35L33=m +CONFIG_SND_SOC_CS35L34=m +CONFIG_SND_SOC_CS35L35=m +CONFIG_SND_SOC_CS35L36=m +CONFIG_SND_SOC_CS35L41_LIB=m +CONFIG_SND_SOC_CS35L41=m +CONFIG_SND_SOC_CS35L41_SPI=m +CONFIG_SND_SOC_CS35L41_I2C=m +CONFIG_SND_SOC_CS35L45=m +CONFIG_SND_SOC_CS35L45_SPI=m +CONFIG_SND_SOC_CS35L45_I2C=m +# CONFIG_SND_SOC_CS35L56_I2C is not set +# CONFIG_SND_SOC_CS35L56_SPI is not set +CONFIG_SND_SOC_CS42L42_CORE=m +CONFIG_SND_SOC_CS42L42=m +CONFIG_SND_SOC_CS42L51=m +CONFIG_SND_SOC_CS42L51_I2C=m +CONFIG_SND_SOC_CS42L52=m +CONFIG_SND_SOC_CS42L56=m +CONFIG_SND_SOC_CS42L73=m +CONFIG_SND_SOC_CS42L83=m +CONFIG_SND_SOC_CS42L84=m +CONFIG_SND_SOC_CS4234=m +CONFIG_SND_SOC_CS4265=m +CONFIG_SND_SOC_CS4270=m +CONFIG_SND_SOC_CS4271=m +CONFIG_SND_SOC_CS4271_I2C=m +CONFIG_SND_SOC_CS4271_SPI=m +CONFIG_SND_SOC_CS42XX8=m +CONFIG_SND_SOC_CS42XX8_I2C=m +CONFIG_SND_SOC_CS43130=m +CONFIG_SND_SOC_CS4341=m +CONFIG_SND_SOC_CS4349=m +# CONFIG_SND_SOC_CS48L32 is not set +CONFIG_SND_SOC_CS53L30=m +# CONFIG_SND_SOC_CS530X_I2C is not set +# CONFIG_SND_SOC_CS530X_SPI is not set +# CONFIG_SND_SOC_CX2072X is not set +# CONFIG_SND_SOC_DA7213 is not set +# CONFIG_SND_SOC_DMIC is not set +# CONFIG_SND_SOC_ES7134 is not set +# CONFIG_SND_SOC_ES7241 is not set +# CONFIG_SND_SOC_ES8311 is not set +# CONFIG_SND_SOC_ES8316 is not set +# CONFIG_SND_SOC_ES8323 is not set +# CONFIG_SND_SOC_ES8326 is not set +# CONFIG_SND_SOC_ES8328_I2C is not set +# CONFIG_SND_SOC_ES8328_SPI is not set +# CONFIG_SND_SOC_ES8375 is not set +# CONFIG_SND_SOC_ES8389 is not set +# CONFIG_SND_SOC_FS210X is not set +# CONFIG_SND_SOC_GTM601 is not set +# CONFIG_SND_SOC_HDA is not set +# CONFIG_SND_SOC_ICS43432 is not set +# CONFIG_SND_SOC_IDT821034 is not set +# CONFIG_SND_SOC_MAX98088 is not set +# CONFIG_SND_SOC_MAX98090 is not set +# CONFIG_SND_SOC_MAX98357A is not set +# CONFIG_SND_SOC_MAX98504 is not set +# CONFIG_SND_SOC_MAX9867 is not set +# CONFIG_SND_SOC_MAX98927 is not set +# CONFIG_SND_SOC_MAX98520 is not set +# CONFIG_SND_SOC_MAX98373_I2C is not set +# CONFIG_SND_SOC_MAX98388 is not set +# CONFIG_SND_SOC_MAX98390 is not set +# CONFIG_SND_SOC_MAX98396 is not set +# CONFIG_SND_SOC_MAX9860 is not set +# CONFIG_SND_SOC_MSM8916_WCD_ANALOG is not set +# CONFIG_SND_SOC_MSM8916_WCD_DIGITAL is not set +# CONFIG_SND_SOC_PCM1681 is not set +# CONFIG_SND_SOC_PCM1754 is not set +# CONFIG_SND_SOC_PCM1789_I2C is not set +# CONFIG_SND_SOC_PCM179X_I2C is not set +# CONFIG_SND_SOC_PCM179X_SPI is not set +# CONFIG_SND_SOC_PCM186X_I2C is not set +# CONFIG_SND_SOC_PCM186X_SPI is not set +# CONFIG_SND_SOC_PCM3060_I2C is not set +# CONFIG_SND_SOC_PCM3060_SPI is not set +# CONFIG_SND_SOC_PCM3168A_I2C is not set +# CONFIG_SND_SOC_PCM3168A_SPI is not set +# CONFIG_SND_SOC_PCM5102A is not set +# CONFIG_SND_SOC_PCM512x_I2C is not set +# CONFIG_SND_SOC_PCM512x_SPI is not set +# CONFIG_SND_SOC_PCM6240 is not set +# CONFIG_SND_SOC_PEB2466 is not set +# CONFIG_SND_SOC_RT5575 is not set +# CONFIG_SND_SOC_RT5616 is not set +# CONFIG_SND_SOC_RT5631 is not set +# CONFIG_SND_SOC_RT5640 is not set +# CONFIG_SND_SOC_RT5659 is not set +# CONFIG_SND_SOC_RT9120 is not set +# CONFIG_SND_SOC_RT9123 is not set +# CONFIG_SND_SOC_RT9123P is not set +# CONFIG_SND_SOC_RTQ9124 is not set +# CONFIG_SND_SOC_RTQ9128 is not set +# CONFIG_SND_SOC_SGTL5000 is not set +# CONFIG_SND_SOC_SIMPLE_AMPLIFIER is not set +# CONFIG_SND_SOC_SIMPLE_MUX is not set +# CONFIG_SND_SOC_SMA1303 is not set +# CONFIG_SND_SOC_SMA1307 is not set +# CONFIG_SND_SOC_SPDIF is not set +# CONFIG_SND_SOC_SRC4XXX_I2C is not set +# CONFIG_SND_SOC_SSM2305 is not set +# CONFIG_SND_SOC_SSM2518 is not set +# CONFIG_SND_SOC_SSM2602_SPI is not set +# CONFIG_SND_SOC_SSM2602_I2C is not set +# CONFIG_SND_SOC_SSM3515 is not set +# CONFIG_SND_SOC_SSM4567 is not set +# CONFIG_SND_SOC_STA32X is not set +# CONFIG_SND_SOC_STA350 is not set +# CONFIG_SND_SOC_STI_SAS is not set +CONFIG_SND_SOC_TAS2552=m +CONFIG_SND_SOC_TAS2562=m +CONFIG_SND_SOC_TAS2764=m +CONFIG_SND_SOC_TAS2770=m +CONFIG_SND_SOC_TAS2780=m +# CONFIG_SND_SOC_TAS2781_I2C is not set +CONFIG_SND_SOC_TAS5086=m +CONFIG_SND_SOC_TAS571X=m +CONFIG_SND_SOC_TAS5720=m +CONFIG_SND_SOC_TAS5805M=m +CONFIG_SND_SOC_TAS6424=m +# CONFIG_SND_SOC_TDA7419 is not set +# CONFIG_SND_SOC_TFA9879 is not set +# CONFIG_SND_SOC_TFA989X is not set +# CONFIG_SND_SOC_TLV320ADC3XXX is not set +# CONFIG_SND_SOC_TLV320AIC23_I2C is not set +# CONFIG_SND_SOC_TLV320AIC23_SPI is not set +# CONFIG_SND_SOC_TLV320AIC31XX is not set +# CONFIG_SND_SOC_TLV320AIC32X4_I2C is not set +# CONFIG_SND_SOC_TLV320AIC32X4_SPI is not set +# CONFIG_SND_SOC_TLV320AIC3X_I2C is not set +# CONFIG_SND_SOC_TLV320AIC3X_SPI is not set +# CONFIG_SND_SOC_TLV320ADCX140 is not set +# CONFIG_SND_SOC_TS3A227E is not set +# CONFIG_SND_SOC_TSCS42XX is not set +# CONFIG_SND_SOC_TSCS454 is not set +# CONFIG_SND_SOC_UDA1334 is not set +# CONFIG_SND_SOC_UDA1342 is not set +# CONFIG_SND_SOC_UDA1380 is not set +# CONFIG_SND_SOC_WM8510 is not set +# CONFIG_SND_SOC_WM8523 is not set +# CONFIG_SND_SOC_WM8524 is not set +# CONFIG_SND_SOC_WM8580 is not set +# CONFIG_SND_SOC_WM8711 is not set +# CONFIG_SND_SOC_WM8728 is not set +# CONFIG_SND_SOC_WM8731_I2C is not set +# CONFIG_SND_SOC_WM8731_SPI is not set +# CONFIG_SND_SOC_WM8737 is not set +# CONFIG_SND_SOC_WM8741 is not set +# CONFIG_SND_SOC_WM8750 is not set +# CONFIG_SND_SOC_WM8753 is not set +# CONFIG_SND_SOC_WM8770 is not set +# CONFIG_SND_SOC_WM8776 is not set +# CONFIG_SND_SOC_WM8782 is not set +# CONFIG_SND_SOC_WM8804_I2C is not set +# CONFIG_SND_SOC_WM8804_SPI is not set +# CONFIG_SND_SOC_WM8903 is not set +# CONFIG_SND_SOC_WM8904 is not set +# CONFIG_SND_SOC_WM8940 is not set +# CONFIG_SND_SOC_WM8960 is not set +# CONFIG_SND_SOC_WM8961 is not set +# CONFIG_SND_SOC_WM8962 is not set +# CONFIG_SND_SOC_WM8974 is not set +# CONFIG_SND_SOC_WM8978 is not set +# CONFIG_SND_SOC_WM8985 is not set +# CONFIG_SND_SOC_ZL38060 is not set +# CONFIG_SND_SOC_MAX9759 is not set +# CONFIG_SND_SOC_MT6351 is not set +# CONFIG_SND_SOC_MT6357 is not set +# CONFIG_SND_SOC_MT6358 is not set +# CONFIG_SND_SOC_MT6660 is not set +# CONFIG_SND_SOC_NAU8315 is not set +# CONFIG_SND_SOC_NAU8325 is not set +# CONFIG_SND_SOC_NAU8540 is not set +# CONFIG_SND_SOC_NAU8810 is not set +# CONFIG_SND_SOC_NAU8821 is not set +# CONFIG_SND_SOC_NAU8822 is not set +# CONFIG_SND_SOC_NAU8824 is not set +# CONFIG_SND_SOC_NTP8918 is not set +# CONFIG_SND_SOC_NTP8835 is not set +# CONFIG_SND_SOC_TPA6130A2 is not set +# CONFIG_SND_SOC_LPASS_WSA_MACRO is not set +# CONFIG_SND_SOC_LPASS_VA_MACRO is not set +# CONFIG_SND_SOC_LPASS_RX_MACRO is not set +# CONFIG_SND_SOC_LPASS_TX_MACRO is not set +# end of CODEC drivers + +# +# Generic drivers +# +CONFIG_SND_SIMPLE_CARD_UTILS=m +CONFIG_SND_SIMPLE_CARD=m +CONFIG_SND_AUDIO_GRAPH_CARD=m +CONFIG_SND_AUDIO_GRAPH_CARD2=m +# CONFIG_SND_AUDIO_GRAPH_CARD2_CUSTOM_SAMPLE is not set +# CONFIG_SND_TEST_COMPONENT is not set +# end of Generic drivers + +CONFIG_SND_VIRTIO=m +CONFIG_HID_SUPPORT=y +CONFIG_HID=y +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=m +CONFIG_HID_GENERIC=y +CONFIG_HID_HAPTIC=y + +# +# Special HID drivers +# +CONFIG_HID_A4TECH=m +CONFIG_HID_ACCUTOUCH=m +CONFIG_HID_ACRUX=m +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_APPLE=m +CONFIG_HID_APPLEIR=m +# CONFIG_HID_ASUS is not set +CONFIG_HID_AUREAL=m +CONFIG_HID_BELKIN=m +CONFIG_HID_BETOP_FF=m +# CONFIG_HID_BIGBEN_FF is not set +CONFIG_HID_CHERRY=m +CONFIG_HID_CHICONY=m +CONFIG_HID_CORSAIR=m +CONFIG_HID_COUGAR=m +CONFIG_HID_MACALLY=m +CONFIG_HID_PRODIKEYS=m +CONFIG_HID_CMEDIA=m +CONFIG_HID_CP2112=m +# CONFIG_HID_CREATIVE_SB0540 is not set +CONFIG_HID_CYPRESS=m +CONFIG_HID_DRAGONRISE=m +CONFIG_DRAGONRISE_FF=y +CONFIG_HID_EMS_FF=m +# CONFIG_HID_ELAN is not set +CONFIG_HID_ELECOM=m +CONFIG_HID_ELO=m +# CONFIG_HID_EVISION is not set +CONFIG_HID_EZKEY=m +CONFIG_HID_FT260=m +CONFIG_HID_GEMBIRD=m +CONFIG_HID_GFRM=m +CONFIG_HID_GLORIOUS=m +CONFIG_HID_HOLTEK=m +CONFIG_HOLTEK_FF=y +# CONFIG_HID_GOODIX_SPI is not set +# CONFIG_HID_GOOGLE_STADIA_FF is not set +# CONFIG_HID_VIVALDI is not set +CONFIG_HID_GT683R=m +CONFIG_HID_KEYTOUCH=m +CONFIG_HID_KYE=m +# CONFIG_HID_KYSONA is not set +CONFIG_HID_UCLOGIC=m +CONFIG_HID_WALTOP=m +CONFIG_HID_VIEWSONIC=m +# CONFIG_HID_VRC2 is not set +CONFIG_HID_XIAOMI=m +CONFIG_HID_GYRATION=m +CONFIG_HID_ICADE=m +CONFIG_HID_ITE=m +CONFIG_HID_JABRA=m +CONFIG_HID_TWINHAN=m +CONFIG_HID_KENSINGTON=m +CONFIG_HID_LCPOWER=m +CONFIG_HID_LED=m +# CONFIG_HID_LENOVO is not set +# CONFIG_HID_LENOVO_GO is not set +# CONFIG_HID_LENOVO_GO_S is not set +CONFIG_HID_LETSKETCH=m +CONFIG_HID_LOGITECH=m +CONFIG_HID_LOGITECH_DJ=m +CONFIG_HID_LOGITECH_HIDPP=m +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_LOGIWHEELS_FF=y +CONFIG_HID_MAGICMOUSE=m +CONFIG_HID_MALTRON=m +CONFIG_HID_MAYFLASH=m +# CONFIG_HID_MEGAWORLD_FF is not set +CONFIG_HID_REDRAGON=m +CONFIG_HID_MICROSOFT=m +CONFIG_HID_MONTEREY=m +CONFIG_HID_MULTITOUCH=m +CONFIG_HID_NINTENDO=m +CONFIG_NINTENDO_FF=y +CONFIG_HID_NTI=m +CONFIG_HID_NTRIG=m +# CONFIG_HID_NVIDIA_SHIELD is not set +CONFIG_HID_ORTEK=m +CONFIG_HID_PANTHERLORD=m +CONFIG_PANTHERLORD_FF=y +CONFIG_HID_PENMOUNT=m +CONFIG_HID_PETALYNX=m +CONFIG_HID_PICOLCD=m +CONFIG_HID_PICOLCD_FB=y +CONFIG_HID_PICOLCD_BACKLIGHT=y +CONFIG_HID_PICOLCD_LCD=y +CONFIG_HID_PICOLCD_LEDS=y +CONFIG_HID_PICOLCD_CIR=y +CONFIG_HID_PLANTRONICS=m +CONFIG_HID_PLAYSTATION=m +CONFIG_PLAYSTATION_FF=y +# CONFIG_HID_PXRC is not set +# CONFIG_HID_RAPOO is not set +# CONFIG_HID_RAZER is not set +CONFIG_HID_PRIMAX=m +CONFIG_HID_RETRODE=m +CONFIG_HID_ROCCAT=m +CONFIG_HID_SAITEK=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SEMITEK=m +# CONFIG_HID_SIGMAMICRO is not set +CONFIG_HID_SONY=m +CONFIG_SONY_FF=y +CONFIG_HID_SPEEDLINK=m +CONFIG_HID_STEAM=m +# CONFIG_STEAM_FF is not set +CONFIG_HID_STEELSERIES=m +CONFIG_HID_SUNPLUS=m +# CONFIG_HID_RMI is not set +CONFIG_HID_GREENASIA=m +CONFIG_GREENASIA_FF=y +CONFIG_HID_SMARTJOYPLUS=m +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_TIVO=m +CONFIG_HID_TOPSEED=m +# CONFIG_HID_TOPRE is not set +CONFIG_HID_THINGM=m +CONFIG_HID_THRUSTMASTER=m +CONFIG_THRUSTMASTER_FF=y +CONFIG_HID_UDRAW_PS3=m +CONFIG_HID_U2FZERO=m +# CONFIG_HID_UNIVERSAL_PIDFF is not set +CONFIG_HID_WACOM=m +CONFIG_HID_WIIMOTE=m +# CONFIG_HID_WINWING is not set +CONFIG_HID_XINMO=m +CONFIG_HID_ZEROPLUS=m +CONFIG_ZEROPLUS_FF=y +CONFIG_HID_ZYDACRON=m +CONFIG_HID_SENSOR_HUB=m +CONFIG_HID_SENSOR_CUSTOM_SENSOR=m +CONFIG_HID_ALPS=m +# CONFIG_HID_MCP2200 is not set +CONFIG_HID_MCP2221=m +# CONFIG_HID_HUAWEI is not set +# end of Special HID drivers + +# +# HID-BPF support +# +# end of HID-BPF support + +CONFIG_I2C_HID=y +# CONFIG_I2C_HID_OF is not set +# CONFIG_I2C_HID_OF_ELAN is not set +# CONFIG_I2C_HID_OF_GOODIX is not set + +# +# USB HID support +# +CONFIG_USB_HID=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y + +# +# USB HID Boot Protocol drivers +# +# CONFIG_USB_KBD is not set +# CONFIG_USB_MOUSE is not set +# end of USB HID Boot Protocol drivers +# end of USB HID support + +# +# SPI HID support +# +CONFIG_SPI_HID_APPLE_OF=y +# end of SPI HID support + +CONFIG_SPI_HID_APPLE_CORE=y + +# +# DockChannel HID support +# +CONFIG_HID_DOCKCHANNEL=m +# end of DockChannel HID support + +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +CONFIG_USB_SUPPORT=y +CONFIG_USB_COMMON=y +CONFIG_USB_LED_TRIG=y +CONFIG_USB_ULPI_BUS=y +CONFIG_USB_CONN_GPIO=y +CONFIG_USB_ARCH_HAS_HCD=y +CONFIG_USB=y +CONFIG_USB_PCI=y +# CONFIG_USB_PCI_AMD is not set +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y + +# +# Miscellaneous USB options +# +CONFIG_USB_DEFAULT_PERSIST=y +# CONFIG_USB_FEW_INIT_RETRIES is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_OTG=y +# CONFIG_USB_OTG_PRODUCTLIST is not set +# CONFIG_USB_OTG_DISABLE_EXTERNAL_HUB is not set +# CONFIG_USB_OTG_FSM is not set +# CONFIG_USB_LEDS_TRIGGER_USBPORT is not set +CONFIG_USB_AUTOSUSPEND_DELAY=2 +CONFIG_USB_DEFAULT_AUTHORIZATION_MODE=1 +CONFIG_USB_MON=m + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +CONFIG_USB_XHCI_HCD=m +# CONFIG_USB_XHCI_DBGCAP is not set +CONFIG_USB_XHCI_PCI=m +CONFIG_USB_XHCI_PCI_RENESAS=m +CONFIG_USB_XHCI_PCI_ASMEDIA=y +CONFIG_USB_XHCI_PLATFORM=m +# CONFIG_USB_XHCI_SIDEBAND is not set +# CONFIG_USB_EHCI_HCD is not set +# CONFIG_USB_OXU210HP_HCD is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_MAX3421_HCD is not set +# CONFIG_USB_OHCI_HCD is not set +# CONFIG_USB_UHCI_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HCD_TEST_MODE is not set + +# +# USB Device Class drivers +# +CONFIG_USB_ACM=m +CONFIG_USB_PRINTER=m +CONFIG_USB_WDM=m +CONFIG_USB_TMC=m + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; see USB_STORAGE Help for more info +# +CONFIG_USB_STORAGE=m +# CONFIG_USB_STORAGE_DEBUG is not set +CONFIG_USB_STORAGE_REALTEK=m +CONFIG_REALTEK_AUTOPM=y +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_ONETOUCH=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_STORAGE_ENE_UB6250=m +CONFIG_USB_UAS=m + +# +# USB Imaging devices +# +CONFIG_USB_MDC800=m +CONFIG_USB_MICROTEK=m +CONFIG_USBIP_CORE=m +CONFIG_USBIP_VHCI_HCD=m +CONFIG_USBIP_VHCI_HC_PORTS=8 +CONFIG_USBIP_VHCI_NR_HCS=1 +CONFIG_USBIP_HOST=m +CONFIG_USBIP_VUDC=m +# CONFIG_USBIP_DEBUG is not set + +# +# USB dual-mode controller drivers +# +# CONFIG_USB_CDNS_SUPPORT is not set +# CONFIG_USB_MUSB_HDRC is not set +CONFIG_USB_DWC3=m +CONFIG_USB_DWC3_ULPI=y +# CONFIG_USB_DWC3_HOST is not set +# CONFIG_USB_DWC3_GADGET is not set +CONFIG_USB_DWC3_DUAL_ROLE=y + +# +# Platform Glue Driver Support +# +CONFIG_USB_DWC3_HAPS=m +CONFIG_USB_DWC3_OF_SIMPLE=m +CONFIG_USB_DWC3_GENERIC_PLAT=m +CONFIG_USB_DWC3_APPLE=m +# CONFIG_USB_DWC3_GOOGLE is not set +# CONFIG_USB_DWC2 is not set +CONFIG_USB_CHIPIDEA=m +CONFIG_USB_CHIPIDEA_UDC=y +CONFIG_USB_CHIPIDEA_PCI=m +CONFIG_USB_CHIPIDEA_MSM=m +CONFIG_USB_CHIPIDEA_NPCM=m +CONFIG_USB_CHIPIDEA_IMX=m +CONFIG_USB_CHIPIDEA_GENERIC=m +CONFIG_USB_CHIPIDEA_TEGRA=m +CONFIG_USB_ISP1760=y +CONFIG_USB_ISP1760_HCD=y +CONFIG_USB_ISP1760_HOST_ROLE=y + +# +# USB port drivers +# +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=m +CONFIG_USB_SERIAL_AIRCABLE=m +CONFIG_USB_SERIAL_ARK3116=m +CONFIG_USB_SERIAL_BELKIN=m +CONFIG_USB_SERIAL_CH341=m +CONFIG_USB_SERIAL_WHITEHEAT=m +CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m +CONFIG_USB_SERIAL_CP210X=m +CONFIG_USB_SERIAL_CYPRESS_M8=m +CONFIG_USB_SERIAL_EMPEG=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_SERIAL_VISOR=m +CONFIG_USB_SERIAL_IPAQ=m +CONFIG_USB_SERIAL_IR=m +CONFIG_USB_SERIAL_EDGEPORT=m +CONFIG_USB_SERIAL_EDGEPORT_TI=m +CONFIG_USB_SERIAL_F81232=m +CONFIG_USB_SERIAL_F8153X=m +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_IPW=m +CONFIG_USB_SERIAL_IUU=m +CONFIG_USB_SERIAL_KEYSPAN_PDA=m +CONFIG_USB_SERIAL_KEYSPAN=m +CONFIG_USB_SERIAL_KLSI=m +CONFIG_USB_SERIAL_KOBIL_SCT=m +CONFIG_USB_SERIAL_MCT_U232=m +CONFIG_USB_SERIAL_METRO=m +CONFIG_USB_SERIAL_MOS7720=m +CONFIG_USB_SERIAL_MOS7840=m +CONFIG_USB_SERIAL_MXUPORT=m +CONFIG_USB_SERIAL_NAVMAN=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OTI6858=m +CONFIG_USB_SERIAL_QCAUX=m +CONFIG_USB_SERIAL_QUALCOMM=m +CONFIG_USB_SERIAL_SPCP8X5=m +CONFIG_USB_SERIAL_SAFE=m +CONFIG_USB_SERIAL_SAFE_PADDED=y +CONFIG_USB_SERIAL_SIERRAWIRELESS=m +CONFIG_USB_SERIAL_SYMBOL=m +CONFIG_USB_SERIAL_TI=m +CONFIG_USB_SERIAL_CYBERJACK=m +CONFIG_USB_SERIAL_WWAN=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_USB_SERIAL_OMNINET=m +CONFIG_USB_SERIAL_OPTICON=m +CONFIG_USB_SERIAL_XSENS_MT=m +CONFIG_USB_SERIAL_WISHBONE=m +CONFIG_USB_SERIAL_SSU100=m +CONFIG_USB_SERIAL_QT2=m +CONFIG_USB_SERIAL_UPD78F0730=m +CONFIG_USB_SERIAL_XR=m +CONFIG_USB_SERIAL_DEBUG=m + +# +# USB Miscellaneous drivers +# +CONFIG_USB_EMI62=m +CONFIG_USB_EMI26=m +CONFIG_USB_ADUTUX=m +CONFIG_USB_SEVSEG=m +CONFIG_USB_LEGOTOWER=m +CONFIG_USB_LCD=m +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +CONFIG_USB_IDMOUSE=m +CONFIG_USB_APPLEDISPLAY=m +CONFIG_APPLE_MFI_FASTCHARGE=m +CONFIG_USB_LD=m +CONFIG_USB_TRANCEVIBRATOR=m +CONFIG_USB_IOWARRIOR=m +# CONFIG_USB_TEST is not set +# CONFIG_USB_EHSET_TEST_FIXTURE is not set +CONFIG_USB_ISIGHTFW=m +CONFIG_USB_YUREX=m +CONFIG_USB_EZUSB_FX2=m +# CONFIG_USB_HUB_USB251XB is not set +# CONFIG_USB_HSIC_USB3503 is not set +# CONFIG_USB_HSIC_USB4604 is not set +# CONFIG_USB_LINK_LAYER_TEST is not set +CONFIG_USB_CHAOSKEY=m +# CONFIG_USB_ONBOARD_DEV is not set +CONFIG_USB_ATM=m +# CONFIG_USB_SPEEDTOUCH is not set +CONFIG_USB_CXACRU=m +CONFIG_USB_UEAGLEATM=m +CONFIG_USB_XUSBATM=m + +# +# USB Physical Layer drivers +# +CONFIG_USB_PHY=y +CONFIG_NOP_USB_XCEIV=m +# CONFIG_USB_ISP1301 is not set +CONFIG_USB_ULPI=y +CONFIG_USB_ULPI_VIEWPORT=y +# end of USB Physical Layer drivers + +CONFIG_USB_GADGET=m +# CONFIG_USB_GADGET_DEBUG is not set +# CONFIG_USB_GADGET_DEBUG_FILES is not set +# CONFIG_USB_GADGET_DEBUG_FS is not set +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS=2 +# CONFIG_U_SERIAL_CONSOLE is not set + +# +# USB Peripheral Controller +# +# CONFIG_USB_GR_UDC is not set +# CONFIG_USB_R8A66597 is not set +# CONFIG_USB_PXA27X is not set +# CONFIG_USB_SNP_UDC_PLAT is not set +# CONFIG_USB_M66592 is not set +# CONFIG_USB_BDC_UDC is not set +# CONFIG_USB_AMD5536UDC is not set +# CONFIG_USB_NET2280 is not set +# CONFIG_USB_GOKU is not set +# CONFIG_USB_EG20T is not set +# CONFIG_USB_GADGET_XILINX is not set +# CONFIG_USB_MAX3420_UDC is not set +# CONFIG_USB_DUMMY_HCD is not set +# end of USB Peripheral Controller + +CONFIG_USB_LIBCOMPOSITE=m +CONFIG_USB_F_ACM=m +CONFIG_USB_U_SERIAL=m +CONFIG_USB_U_ETHER=m +CONFIG_USB_U_AUDIO=m +CONFIG_USB_F_SERIAL=m +CONFIG_USB_F_OBEX=m +CONFIG_USB_F_NCM=m +CONFIG_USB_F_ECM=m +CONFIG_USB_F_EEM=m +CONFIG_USB_F_SUBSET=m +CONFIG_USB_F_RNDIS=m +CONFIG_USB_F_MASS_STORAGE=m +CONFIG_USB_F_FS=m +CONFIG_USB_F_UAC1=m +CONFIG_USB_F_UAC1_LEGACY=m +CONFIG_USB_F_UAC2=m +CONFIG_USB_F_UVC=m +CONFIG_USB_F_MIDI=m +CONFIG_USB_F_HID=m +CONFIG_USB_F_PRINTER=m +CONFIG_USB_F_TCM=m +CONFIG_USB_CONFIGFS=m +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +# CONFIG_USB_CONFIGFS_F_LB_SS is not set +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_UAC1=y +CONFIG_USB_CONFIGFS_F_UAC1_LEGACY=y +CONFIG_USB_CONFIGFS_F_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +# CONFIG_USB_CONFIGFS_F_MIDI2 is not set +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_USB_CONFIGFS_F_PRINTER=y +CONFIG_USB_CONFIGFS_F_TCM=y + +# +# USB Gadget precomposed configurations +# +# CONFIG_USB_ZERO is not set +CONFIG_USB_AUDIO=m +CONFIG_GADGET_UAC1=y +# CONFIG_GADGET_UAC1_LEGACY is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_ETH_EEM=y +CONFIG_USB_G_NCM=m +CONFIG_USB_GADGETFS=m +CONFIG_USB_FUNCTIONFS=m +CONFIG_USB_FUNCTIONFS_ETH=y +CONFIG_USB_FUNCTIONFS_RNDIS=y +CONFIG_USB_FUNCTIONFS_GENERIC=y +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_GADGET_TARGET=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_MIDI_GADGET=m +CONFIG_USB_G_PRINTER=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_ACM_MS=m +CONFIG_USB_G_MULTI=m +CONFIG_USB_G_MULTI_RNDIS=y +CONFIG_USB_G_MULTI_CDC=y +CONFIG_USB_G_HID=m +# CONFIG_USB_G_DBGP is not set +CONFIG_USB_G_WEBCAM=m +# CONFIG_USB_RAW_GADGET is not set +# end of USB Gadget precomposed configurations + +CONFIG_TYPEC=m +CONFIG_TYPEC_TCPM=m +CONFIG_TYPEC_TCPCI=m +# CONFIG_TYPEC_RT1711H is not set +# CONFIG_TYPEC_TCPCI_MAXIM is not set +# CONFIG_TYPEC_FUSB302 is not set +CONFIG_TYPEC_UCSI=m +# CONFIG_UCSI_CCG is not set +# CONFIG_UCSI_STM32G0 is not set +CONFIG_TYPEC_TPS6598X_CORE=m +CONFIG_TYPEC_TPS6598X=m +# CONFIG_TYPEC_SN201202X is not set +# CONFIG_TYPEC_ANX7411 is not set +# CONFIG_TYPEC_RT1719 is not set +# CONFIG_TYPEC_HD3SS3220 is not set +# CONFIG_TYPEC_STUSB160X is not set +# CONFIG_TYPEC_WUSB3801 is not set + +# +# USB Type-C Multiplexer/DeMultiplexer Switch support +# +# CONFIG_TYPEC_MUX_FSA4480 is not set +# CONFIG_TYPEC_MUX_GPIO_SBU is not set +# CONFIG_TYPEC_MUX_PI3USB30532 is not set +# CONFIG_TYPEC_MUX_IT5205 is not set +# CONFIG_TYPEC_MUX_NB7VPQ904M is not set +# CONFIG_TYPEC_MUX_PS883X is not set +# CONFIG_TYPEC_MUX_PTN36502 is not set +# CONFIG_TYPEC_MUX_TUSB1046 is not set +# CONFIG_TYPEC_MUX_WCD939X_USBSS is not set +# end of USB Type-C Multiplexer/DeMultiplexer Switch support + +# +# USB Type-C Alternate Mode drivers +# +CONFIG_TYPEC_DP_ALTMODE=m +# CONFIG_TYPEC_NVIDIA_ALTMODE is not set +# CONFIG_TYPEC_TBT_ALTMODE is not set +# end of USB Type-C Alternate Mode drivers + +CONFIG_USB_ROLE_SWITCH=y +CONFIG_MMC=m +CONFIG_PWRSEQ_EMMC=m +CONFIG_PWRSEQ_SD8787=m +CONFIG_PWRSEQ_SIMPLE=m +CONFIG_MMC_BLOCK=m +CONFIG_MMC_BLOCK_MINORS=32 +# CONFIG_SDIO_UART is not set +# CONFIG_MMC_TEST is not set + +# +# MMC/SD/SDIO Host Controller Drivers +# +# CONFIG_MMC_DEBUG is not set +# CONFIG_MMC_ARMMMCI is not set +CONFIG_MMC_SDHCI=m +CONFIG_MMC_SDHCI_IO_ACCESSORS=y +CONFIG_MMC_SDHCI_UHS2=m +CONFIG_MMC_SDHCI_PCI=m +# CONFIG_MMC_RICOH_MMC is not set +# CONFIG_MMC_SDHCI_PLTFM is not set +# CONFIG_MMC_TIFM_SD is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_CB710 is not set +# CONFIG_MMC_VIA_SDMMC is not set +# CONFIG_MMC_DW is not set +# CONFIG_MMC_VUB300 is not set +# CONFIG_MMC_USHC is not set +# CONFIG_MMC_USDHI6ROL0 is not set +CONFIG_MMC_CQHCI=m +# CONFIG_MMC_HSQ is not set +# CONFIG_MMC_TOSHIBA_PCI is not set +# CONFIG_MMC_MTK is not set +# CONFIG_SCSI_UFSHCD is not set +# CONFIG_MEMSTICK is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_FLASH=m +CONFIG_LEDS_CLASS_MULTICOLOR=m +# CONFIG_LEDS_BRIGHTNESS_HW_CHANGED is not set + +# +# LED drivers +# +# CONFIG_LEDS_AN30259A is not set +# CONFIG_LEDS_OSRAM_AMS_AS3668 is not set +# CONFIG_LEDS_AW200XX is not set +# CONFIG_LEDS_AW2013 is not set +# CONFIG_LEDS_BCM6328 is not set +# CONFIG_LEDS_BCM6358 is not set +# CONFIG_LEDS_CR0014114 is not set +# CONFIG_LEDS_EL15203000 is not set +# CONFIG_LEDS_LM3530 is not set +# CONFIG_LEDS_LM3532 is not set +# CONFIG_LEDS_LM3642 is not set +# CONFIG_LEDS_LM3692X is not set +# CONFIG_LEDS_PCA9532 is not set +CONFIG_LEDS_GPIO=m +# CONFIG_LEDS_LP3944 is not set +# CONFIG_LEDS_LP3952 is not set +# CONFIG_LEDS_LP50XX is not set +# CONFIG_LEDS_LP55XX_COMMON is not set +# CONFIG_LEDS_LP8860 is not set +# CONFIG_LEDS_LP8864 is not set +# CONFIG_LEDS_PCA955X is not set +# CONFIG_LEDS_PCA963X is not set +# CONFIG_LEDS_PCA995X is not set +# CONFIG_LEDS_DAC124S085 is not set +CONFIG_LEDS_PWM=y +# CONFIG_LEDS_REGULATOR is not set +# CONFIG_LEDS_BD2606MVV is not set +# CONFIG_LEDS_BD2802 is not set +# CONFIG_LEDS_LT3593 is not set +# CONFIG_LEDS_TCA6507 is not set +# CONFIG_LEDS_TLC591XX is not set +# CONFIG_LEDS_LM355x is not set +# CONFIG_LEDS_IS31FL319X is not set +# CONFIG_LEDS_IS31FL32XX is not set + +# +# LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM) +# +# CONFIG_LEDS_BLINKM is not set +CONFIG_LEDS_SYSCON=y +# CONFIG_LEDS_MLXREG is not set +CONFIG_LEDS_USER=m +# CONFIG_LEDS_SPI_BYTE is not set +# CONFIG_LEDS_LM3697 is not set +# CONFIG_LEDS_ST1202 is not set + +# +# Flash and Torch LED drivers +# +# CONFIG_LEDS_AAT1290 is not set +# CONFIG_LEDS_AS3645A is not set +# CONFIG_LEDS_KTD2692 is not set +# CONFIG_LEDS_LM3601X is not set +# CONFIG_LEDS_RT4505 is not set +# CONFIG_LEDS_RT8515 is not set +# CONFIG_LEDS_SGM3140 is not set +# CONFIG_LEDS_SY7802 is not set +# CONFIG_LEDS_TPS6131X is not set + +# +# RGB LED drivers +# +# CONFIG_LEDS_GROUP_MULTICOLOR is not set +# CONFIG_LEDS_KTD202X is not set +# CONFIG_LEDS_LP5812 is not set +# CONFIG_LEDS_NCP5623 is not set +# CONFIG_LEDS_PWM_MULTICOLOR is not set +# CONFIG_LEDS_QCOM_LPG is not set + +# +# LED Triggers +# +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_MTD=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_LEDS_TRIGGER_ACTIVITY=y +# CONFIG_LEDS_TRIGGER_GPIO is not set +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y + +# +# iptables trigger is under Netfilter config (LED target) +# +CONFIG_LEDS_TRIGGER_TRANSIENT=y +CONFIG_LEDS_TRIGGER_CAMERA=y +CONFIG_LEDS_TRIGGER_PANIC=y +CONFIG_LEDS_TRIGGER_NETDEV=y +CONFIG_LEDS_TRIGGER_PATTERN=m +CONFIG_LEDS_TRIGGER_TTY=m +# CONFIG_LEDS_TRIGGER_INPUT_EVENTS is not set + +# +# Simatic LED drivers +# +CONFIG_ACCESSIBILITY=y +CONFIG_A11Y_BRAILLE_CONSOLE=y + +# +# Speakup console speech +# +CONFIG_SPEAKUP=m +CONFIG_SPEAKUP_SYNTH_ACNTSA=m +CONFIG_SPEAKUP_SYNTH_APOLLO=m +CONFIG_SPEAKUP_SYNTH_AUDPTR=m +CONFIG_SPEAKUP_SYNTH_BNS=m +CONFIG_SPEAKUP_SYNTH_DECTLK=m +CONFIG_SPEAKUP_SYNTH_DECEXT=m +CONFIG_SPEAKUP_SYNTH_LTLK=m +CONFIG_SPEAKUP_SYNTH_SOFT=m +CONFIG_SPEAKUP_SYNTH_SPKOUT=m +CONFIG_SPEAKUP_SYNTH_TXPRT=m +# CONFIG_SPEAKUP_SYNTH_DUMMY is not set +# end of Speakup console speech + +# CONFIG_INFINIBAND is not set +CONFIG_EDAC_SUPPORT=y +# CONFIG_EDAC is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_SYSTOHC is not set +# CONFIG_RTC_DEBUG is not set +CONFIG_RTC_NVMEM=y + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +CONFIG_RTC_INTF_DEV_UIE_EMUL=y +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_ABB5ZES3 is not set +# CONFIG_RTC_DRV_ABEOZ9 is not set +# CONFIG_RTC_DRV_ABX80X is not set +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_HYM8563 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_MAX31335 is not set +# CONFIG_RTC_DRV_NCT3018Y is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_ISL12022 is not set +# CONFIG_RTC_DRV_ISL12026 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8523 is not set +# CONFIG_RTC_DRV_PCF85363 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_BQ32K is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8010 is not set +# CONFIG_RTC_DRV_RX8111 is not set +# CONFIG_RTC_DRV_RX8581 is not set +# CONFIG_RTC_DRV_RX8025 is not set +# CONFIG_RTC_DRV_EM3027 is not set +# CONFIG_RTC_DRV_RV3028 is not set +# CONFIG_RTC_DRV_RV3032 is not set +# CONFIG_RTC_DRV_RV8803 is not set +# CONFIG_RTC_DRV_SD2405AL is not set +# CONFIG_RTC_DRV_SD3078 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T93 is not set +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1302 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1343 is not set +# CONFIG_RTC_DRV_DS1347 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6916 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RX4581 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_PCF2123 is not set +# CONFIG_RTC_DRV_MCP795 is not set +CONFIG_RTC_I2C_AND_SPI=y + +# +# SPI and I2C RTC drivers +# +# CONFIG_RTC_DRV_DS3232 is not set +# CONFIG_RTC_DRV_PCF2127 is not set +# CONFIG_RTC_DRV_PCF85063 is not set +# CONFIG_RTC_DRV_RV3029C2 is not set +# CONFIG_RTC_DRV_RX6110 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1685_FAMILY is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_DS2404 is not set +# CONFIG_RTC_DRV_EFI is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_MSM6242 is not set +# CONFIG_RTC_DRV_RP5C01 is not set +# CONFIG_RTC_DRV_ZYNQMP is not set + +# +# on-CPU RTC drivers +# +# CONFIG_RTC_DRV_PL030 is not set +# CONFIG_RTC_DRV_PL031 is not set +# CONFIG_RTC_DRV_CADENCE is not set +# CONFIG_RTC_DRV_FTRTC010 is not set +# CONFIG_RTC_DRV_R7301 is not set +# CONFIG_RTC_DRV_GOLDFISH is not set +CONFIG_RTC_DRV_MACSMC=y + +# +# HID Sensor RTC drivers +# +# CONFIG_RTC_DRV_HID_SENSOR_TIME is not set +CONFIG_DMADEVICES=y +# CONFIG_DMADEVICES_DEBUG is not set + +# +# DMA Devices +# +CONFIG_DMA_ENGINE=y +CONFIG_DMA_VIRTUAL_CHANNELS=y +CONFIG_DMA_OF=y +# CONFIG_ALTERA_MSGDMA is not set +# CONFIG_AMBA_PL08X is not set +CONFIG_APPLE_ADMAC=y +CONFIG_APPLE_SIO=m +# CONFIG_ARM_DMA350 is not set +# CONFIG_DW_AXI_DMAC is not set +# CONFIG_FSL_EDMA is not set +# CONFIG_FSL_QDMA is not set +# CONFIG_INTEL_IDMA64 is not set +# CONFIG_MV_XOR_V2 is not set +# CONFIG_PL330_DMA is not set +# CONFIG_PLX_DMA is not set +# CONFIG_SWITCHTEC_DMA is not set +# CONFIG_XILINX_DMA is not set +# CONFIG_XILINX_XDMA is not set +# CONFIG_XILINX_ZYNQMP_DMA is not set +# CONFIG_XILINX_ZYNQMP_DPDMA is not set +# CONFIG_AMD_QDMA is not set +# CONFIG_QCOM_HIDMA_MGMT is not set +# CONFIG_QCOM_HIDMA is not set +# CONFIG_DW_DMAC is not set +# CONFIG_DW_DMAC_PCI is not set +# CONFIG_DW_EDMA is not set +# CONFIG_SF_PDMA is not set + +# +# DMA Clients +# +# CONFIG_ASYNC_TX_DMA is not set +# CONFIG_DMATEST is not set + +# +# DMABUF options +# +CONFIG_SYNC_FILE=y +# CONFIG_SW_SYNC is not set +# CONFIG_UDMABUF is not set +# CONFIG_DMABUF_DEBUG is not set +# CONFIG_DMABUF_SELFTESTS is not set +# CONFIG_DMABUF_HEAPS is not set +# end of DMABUF options + +# CONFIG_UIO is not set +CONFIG_VFIO=m +CONFIG_VFIO_GROUP=y +CONFIG_VFIO_CONTAINER=y +CONFIG_VFIO_IOMMU_TYPE1=m +# CONFIG_VFIO_NOIOMMU is not set +CONFIG_VFIO_VIRQFD=y +# CONFIG_VFIO_DEBUGFS is not set + +# +# VFIO support for PCI devices +# +CONFIG_VFIO_PCI_CORE=m +CONFIG_VFIO_PCI_INTX=y +CONFIG_VFIO_PCI=m +# CONFIG_VIRTIO_VFIO_PCI is not set +# CONFIG_NVGRACE_GPU_VFIO_PCI is not set +# end of VFIO support for PCI devices + +# +# VFIO support for platform devices +# +CONFIG_VFIO_PLATFORM_BASE=m +CONFIG_VFIO_PLATFORM=m +# CONFIG_VFIO_AMBA is not set + +# +# VFIO platform reset drivers +# +# CONFIG_VFIO_PLATFORM_CALXEDAXGMAC_RESET is not set +# CONFIG_VFIO_PLATFORM_AMDXGBE_RESET is not set +# end of VFIO platform reset drivers +# end of VFIO support for platform devices + +CONFIG_IRQ_BYPASS_MANAGER=y +# CONFIG_VIRT_DRIVERS is not set +CONFIG_VIRTIO_ANCHOR=y +CONFIG_VIRTIO=y +CONFIG_VIRTIO_PCI_LIB=m +CONFIG_VIRTIO_PCI_LIB_LEGACY=m +CONFIG_VIRTIO_MENU=y +CONFIG_VIRTIO_PCI=m +CONFIG_VIRTIO_PCI_LEGACY=y +CONFIG_VIRTIO_BALLOON=m +CONFIG_VIRTIO_INPUT=m +CONFIG_VIRTIO_MMIO=m +# CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES is not set +CONFIG_VIRTIO_DMA_SHARED_BUFFER=m +# CONFIG_VIRTIO_DEBUG is not set +# CONFIG_VIRTIO_RTC is not set +# CONFIG_VDPA is not set +CONFIG_VHOST_IOTLB=m +CONFIG_VHOST_TASK=y +CONFIG_VHOST=m +CONFIG_VHOST_MENU=y +CONFIG_VHOST_NET=m +CONFIG_VHOST_SCSI=m +CONFIG_VHOST_VSOCK=m +# CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set +CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL=y + +# +# Microsoft Hyper-V guest support +# +# CONFIG_HYPERV is not set +# end of Microsoft Hyper-V guest support + +# CONFIG_GREYBUS is not set +# CONFIG_COMEDI is not set +# CONFIG_GPIB is not set +CONFIG_STAGING=y +# CONFIG_RTL8723BS is not set + +# +# IIO staging drivers +# + +# +# Accelerometers +# +# CONFIG_ADIS16203 is not set +# end of Accelerometers + +# +# Analog to digital converters +# +# CONFIG_AD7816 is not set +# end of Analog to digital converters + +# +# Analog digital bi-direction converters +# +# CONFIG_ADT7316 is not set +# end of Analog digital bi-direction converters + +# +# Direct Digital Synthesis +# +# CONFIG_AD9832 is not set +# CONFIG_AD9834 is not set +# end of Direct Digital Synthesis + +# +# Network Analyzer, Impedance Converters +# +# CONFIG_AD5933 is not set +# end of Network Analyzer, Impedance Converters +# end of IIO staging drivers + +# CONFIG_FB_SM750 is not set +CONFIG_STAGING_MEDIA=y +# CONFIG_DVB_AV7110 is not set +# CONFIG_VIDEO_MAX96712 is not set +# CONFIG_STAGING_MEDIA_DEPRECATED is not set +# CONFIG_FB_TFT is not set +# CONFIG_XIL_AXIS_FIFO is not set +# CONFIG_VME_BUS is not set +# CONFIG_GOLDFISH is not set +# CONFIG_CHROME_PLATFORMS is not set +# CONFIG_MELLANOX_PLATFORM is not set +# CONFIG_SURFACE_PLATFORMS is not set +CONFIG_ARM64_PLATFORM_DEVICES=y +CONFIG_HAVE_CLK=y +CONFIG_HAVE_CLK_PREPARE=y +CONFIG_COMMON_CLK=y + +# +# Clock driver for ARM Reference designs +# +# CONFIG_CLK_ICST is not set +CONFIG_CLK_SP810=y +# end of Clock driver for ARM Reference designs + +# CONFIG_LMK04832 is not set +CONFIG_COMMON_CLK_APPLE_NCO=y +# CONFIG_COMMON_CLK_MAX9485 is not set +# CONFIG_COMMON_CLK_SI5341 is not set +# CONFIG_COMMON_CLK_SI5351 is not set +# CONFIG_COMMON_CLK_SI514 is not set +# CONFIG_COMMON_CLK_SI544 is not set +# CONFIG_COMMON_CLK_SI570 is not set +# CONFIG_COMMON_CLK_CDCE706 is not set +# CONFIG_COMMON_CLK_CDCE925 is not set +# CONFIG_COMMON_CLK_CS2000_CP is not set +# CONFIG_COMMON_CLK_AXI_CLKGEN is not set +# CONFIG_COMMON_CLK_XGENE is not set +# CONFIG_COMMON_CLK_PWM is not set +# CONFIG_COMMON_CLK_RS9_PCIE is not set +# CONFIG_COMMON_CLK_SI521XX is not set +# CONFIG_COMMON_CLK_VC3 is not set +# CONFIG_COMMON_CLK_VC5 is not set +# CONFIG_COMMON_CLK_VC7 is not set +# CONFIG_COMMON_CLK_FIXED_MMIO is not set +# CONFIG_XILINX_VCU is not set +# CONFIG_COMMON_CLK_XLNX_CLKWZRD is not set +# CONFIG_HWSPINLOCK is not set + +# +# Clock Source drivers +# +CONFIG_TIMER_OF=y +CONFIG_TIMER_PROBE=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y +# CONFIG_FSL_ERRATUM_A008585 is not set +# CONFIG_HISILICON_ERRATUM_161010101 is not set +# CONFIG_ARM64_ERRATUM_858921 is not set +# CONFIG_ARM_TIMER_SP804 is not set +# end of Clock Source drivers + +# CONFIG_MAILBOX is not set +CONFIG_IOMMU_IOVA=y +CONFIG_IOMMU_API=y +CONFIG_IOMMU_SUPPORT=y + +# +# Generic IOMMU Pagetable Support +# +CONFIG_IOMMU_IO_PGTABLE=y +CONFIG_IOMMU_IO_PGTABLE_LPAE=y +CONFIG_IOMMU_IO_PGTABLE_ARMV7S=y +# CONFIG_IOMMU_IO_PGTABLE_ARMV7S_SELFTEST is not set +CONFIG_IOMMU_IO_PGTABLE_DART=y +# end of Generic IOMMU Pagetable Support + +# CONFIG_IOMMU_DEBUGFS is not set +CONFIG_IOMMU_DEFAULT_DMA_STRICT=y +# CONFIG_IOMMU_DEFAULT_DMA_LAZY is not set +# CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set +CONFIG_OF_IOMMU=y +CONFIG_IOMMU_DMA=y +# CONFIG_ARM_SMMU is not set +# CONFIG_ARM_SMMU_V3 is not set +# CONFIG_IOMMUFD is not set +CONFIG_APPLE_DART=y +# CONFIG_VIRTIO_IOMMU is not set + +# +# Remoteproc drivers +# +# CONFIG_REMOTEPROC is not set +# end of Remoteproc drivers + +# +# Rpmsg drivers +# +# CONFIG_RPMSG_VIRTIO is not set +# end of Rpmsg drivers + +# CONFIG_SOUNDWIRE is not set + +# +# SOC (System On Chip) specific Drivers +# + +# +# Amlogic SoC drivers +# +# end of Amlogic SoC drivers + +# +# Apple SoC drivers +# +CONFIG_APPLE_DOCKCHANNEL=y +CONFIG_APPLE_MAILBOX=y +CONFIG_APPLE_PMGR_MISC=y +CONFIG_APPLE_RTKIT=y +CONFIG_APPLE_RTKIT_HELPER=y +CONFIG_APPLE_SART=y +CONFIG_APPLE_TUNABLE=m +CONFIG_RUST_APPLE_MAILBOX=y +CONFIG_RUST_APPLE_RTKIT=y +CONFIG_APPLE_AOP=m +CONFIG_APPLE_SEP=y +CONFIG_APPLE_PMP=y +# end of Apple SoC drivers + +# +# Broadcom SoC drivers +# +# end of Broadcom SoC drivers + +# +# NXP/Freescale QorIQ SoC drivers +# +# CONFIG_QUICC_ENGINE is not set +# CONFIG_FSL_RCPM is not set +# end of NXP/Freescale QorIQ SoC drivers + +# +# fujitsu SoC drivers +# +# end of fujitsu SoC drivers + +# +# i.MX SoC drivers +# +# end of i.MX SoC drivers + +# +# Enable LiteX SoC Builder specific drivers +# +# CONFIG_LITEX_SOC_CONTROLLER is not set +# end of Enable LiteX SoC Builder specific drivers + +# CONFIG_WPCM450_SOC is not set + +# +# Qualcomm SoC drivers +# +# CONFIG_QCOM_PBS is not set +# end of Qualcomm SoC drivers + +# CONFIG_SOC_TI is not set + +# +# Xilinx SoC drivers +# +# end of Xilinx SoC drivers +# end of SOC (System On Chip) specific Drivers + +# +# PM Domains +# + +# +# Amlogic PM Domains +# +# end of Amlogic PM Domains + +CONFIG_APPLE_PMGR_PWRSTATE=y +CONFIG_APPLE_PMP_REPORT=y + +# +# Broadcom PM Domains +# +# end of Broadcom PM Domains + +# +# i.MX PM Domains +# +# end of i.MX PM Domains + +# +# Qualcomm PM Domains +# +# end of Qualcomm PM Domains +# end of PM Domains + +CONFIG_PM_DEVFREQ=y + +# +# DEVFREQ Governors +# +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y +CONFIG_DEVFREQ_GOV_PERFORMANCE=y +CONFIG_DEVFREQ_GOV_POWERSAVE=y +CONFIG_DEVFREQ_GOV_USERSPACE=y +CONFIG_DEVFREQ_GOV_PASSIVE=y + +# +# DEVFREQ Drivers +# +CONFIG_PM_DEVFREQ_EVENT=y +CONFIG_EXTCON=y + +# +# Extcon Device Drivers +# +# CONFIG_EXTCON_ADC_JACK is not set +# CONFIG_EXTCON_FSA9480 is not set +CONFIG_EXTCON_GPIO=y +# CONFIG_EXTCON_LC824206XA is not set +# CONFIG_EXTCON_MAX3355 is not set +# CONFIG_EXTCON_MAX14526 is not set +# CONFIG_EXTCON_PTN5150 is not set +# CONFIG_EXTCON_RT8973A is not set +# CONFIG_EXTCON_SM5502 is not set +CONFIG_EXTCON_USB_GPIO=y +# CONFIG_EXTCON_USBC_TUSB320 is not set +# CONFIG_MEMORY is not set +CONFIG_IIO=y +# CONFIG_IIO_BUFFER is not set +# CONFIG_IIO_CONFIGFS is not set +# CONFIG_IIO_TRIGGER is not set +# CONFIG_IIO_SW_DEVICE is not set +# CONFIG_IIO_SW_TRIGGER is not set +# CONFIG_IIO_TRIGGERED_EVENT is not set + +# +# Accelerometers +# +# CONFIG_ADIS16201 is not set +# CONFIG_ADIS16209 is not set +# CONFIG_ADXL313_I2C is not set +# CONFIG_ADXL313_SPI is not set +# CONFIG_ADXL345_I2C is not set +# CONFIG_ADXL345_SPI is not set +# CONFIG_ADXL355_I2C is not set +# CONFIG_ADXL355_SPI is not set +# CONFIG_ADXL367_SPI is not set +# CONFIG_ADXL367_I2C is not set +# CONFIG_ADXL372_SPI is not set +# CONFIG_ADXL372_I2C is not set +# CONFIG_ADXL380_SPI is not set +# CONFIG_ADXL380_I2C is not set +# CONFIG_BMA180 is not set +# CONFIG_BMA220 is not set +# CONFIG_BMA400 is not set +# CONFIG_BMC150_ACCEL is not set +# CONFIG_BMI088_ACCEL is not set +# CONFIG_DA280 is not set +# CONFIG_DA311 is not set +# CONFIG_DMARD06 is not set +# CONFIG_DMARD09 is not set +# CONFIG_DMARD10 is not set +# CONFIG_FXLS8962AF_I2C is not set +# CONFIG_FXLS8962AF_SPI is not set +# CONFIG_HID_SENSOR_ACCEL_3D is not set +# CONFIG_IIO_ST_ACCEL_3AXIS is not set +# CONFIG_IIO_KX022A_SPI is not set +# CONFIG_IIO_KX022A_I2C is not set +# CONFIG_KXSD9 is not set +# CONFIG_KXCJK1013 is not set +# CONFIG_MC3230 is not set +# CONFIG_MMA7455_I2C is not set +# CONFIG_MMA7455_SPI is not set +# CONFIG_MMA7660 is not set +# CONFIG_MMA8452 is not set +# CONFIG_MMA9551 is not set +# CONFIG_MMA9553 is not set +# CONFIG_MSA311 is not set +# CONFIG_MXC4005 is not set +# CONFIG_MXC6255 is not set +# CONFIG_SCA3000 is not set +# CONFIG_SCA3300 is not set +# CONFIG_STK8312 is not set +# CONFIG_STK8BA50 is not set +# end of Accelerometers + +# +# Analog to digital converters +# +# CONFIG_AD4000 is not set +# CONFIG_AD4030 is not set +# CONFIG_AD4080 is not set +# CONFIG_AD4130 is not set +# CONFIG_AD4134 is not set +# CONFIG_AD4170_4 is not set +# CONFIG_AD4695 is not set +# CONFIG_AD4851 is not set +# CONFIG_AD7091R5 is not set +# CONFIG_AD7091R8 is not set +# CONFIG_AD7124 is not set +# CONFIG_AD7173 is not set +# CONFIG_AD7191 is not set +# CONFIG_AD7192 is not set +# CONFIG_AD7266 is not set +# CONFIG_AD7280 is not set +# CONFIG_AD7291 is not set +# CONFIG_AD7292 is not set +# CONFIG_AD7298 is not set +# CONFIG_AD7380 is not set +# CONFIG_AD7476 is not set +# CONFIG_AD7606_IFACE_PARALLEL is not set +# CONFIG_AD7606_IFACE_SPI is not set +# CONFIG_AD7625 is not set +# CONFIG_AD7766 is not set +# CONFIG_AD7768_1 is not set +# CONFIG_AD7779 is not set +# CONFIG_AD7780 is not set +# CONFIG_AD7791 is not set +# CONFIG_AD7793 is not set +# CONFIG_AD7887 is not set +# CONFIG_AD7923 is not set +# CONFIG_AD7944 is not set +# CONFIG_AD7949 is not set +# CONFIG_AD799X is not set +# CONFIG_AD9467 is not set +# CONFIG_ADE9000 is not set +# CONFIG_CC10001_ADC is not set +# CONFIG_ENVELOPE_DETECTOR is not set +# CONFIG_GEHC_PMC_ADC is not set +# CONFIG_HI8435 is not set +# CONFIG_HX711 is not set +# CONFIG_INA2XX_ADC is not set +# CONFIG_LTC2309 is not set +# CONFIG_LTC2471 is not set +# CONFIG_LTC2485 is not set +# CONFIG_LTC2496 is not set +# CONFIG_LTC2497 is not set +# CONFIG_MAX1027 is not set +# CONFIG_MAX11100 is not set +# CONFIG_MAX1118 is not set +# CONFIG_MAX11205 is not set +# CONFIG_MAX11410 is not set +# CONFIG_MAX1241 is not set +# CONFIG_MAX1363 is not set +# CONFIG_MAX14001 is not set +# CONFIG_MAX34408 is not set +# CONFIG_MAX9611 is not set +# CONFIG_MCP320X is not set +# CONFIG_MCP3422 is not set +# CONFIG_MCP3564 is not set +# CONFIG_MCP3911 is not set +# CONFIG_NAU7802 is not set +# CONFIG_NCT7201 is not set +# CONFIG_PAC1921 is not set +# CONFIG_PAC1934 is not set +# CONFIG_QCOM_SPMI_IADC is not set +# CONFIG_QCOM_SPMI_VADC is not set +# CONFIG_QCOM_SPMI_ADC5 is not set +# CONFIG_QCOM_SPMI_ADC5_GEN3 is not set +# CONFIG_ROHM_BD79112 is not set +# CONFIG_ROHM_BD79124 is not set +# CONFIG_RICHTEK_RTQ6056 is not set +# CONFIG_SD_ADC_MODULATOR is not set +# CONFIG_TI_ADC081C is not set +# CONFIG_TI_ADC0832 is not set +# CONFIG_TI_ADC084S021 is not set +# CONFIG_TI_ADC108S102 is not set +# CONFIG_TI_ADC12138 is not set +# CONFIG_TI_ADC128S052 is not set +# CONFIG_TI_ADC161S626 is not set +# CONFIG_TI_ADS1015 is not set +# CONFIG_TI_ADS1018 is not set +# CONFIG_TI_ADS1100 is not set +# CONFIG_TI_ADS1119 is not set +# CONFIG_TI_ADS124S08 is not set +# CONFIG_TI_ADS1298 is not set +# CONFIG_TI_ADS131E08 is not set +# CONFIG_TI_ADS131M02 is not set +# CONFIG_TI_ADS7138 is not set +# CONFIG_TI_ADS7924 is not set +# CONFIG_TI_ADS7950 is not set +# CONFIG_TI_ADS8344 is not set +# CONFIG_TI_ADS8688 is not set +# CONFIG_TI_LMP92064 is not set +# CONFIG_TI_TLC4541 is not set +# CONFIG_TI_TSC2046 is not set +# CONFIG_VF610_ADC is not set +# CONFIG_XILINX_XADC is not set +# end of Analog to digital converters + +# +# Analog to digital and digital to analog converters +# +# CONFIG_AD74115 is not set +# CONFIG_AD74413R is not set +# end of Analog to digital and digital to analog converters + +# +# Analog Front Ends +# +# CONFIG_IIO_RESCALE is not set +# end of Analog Front Ends + +# +# Amplifiers +# +# CONFIG_AD8366 is not set +# CONFIG_ADA4250 is not set +# CONFIG_ADL8113 is not set +# CONFIG_HMC425 is not set +# end of Amplifiers + +# +# Capacitance to digital converters +# +# CONFIG_AD7150 is not set +# CONFIG_AD7746 is not set +# end of Capacitance to digital converters + +# +# Chemical Sensors +# +# CONFIG_AOSONG_AGS02MA is not set +# CONFIG_ATLAS_PH_SENSOR is not set +# CONFIG_ATLAS_EZO_SENSOR is not set +# CONFIG_BME680 is not set +# CONFIG_CCS811 is not set +# CONFIG_ENS160 is not set +# CONFIG_IAQCORE is not set +# CONFIG_MHZ19B is not set +# CONFIG_PMS7003 is not set +# CONFIG_SCD30_CORE is not set +# CONFIG_SCD4X is not set +# CONFIG_SEN0322 is not set +# CONFIG_SENSIRION_SGP30 is not set +# CONFIG_SENSIRION_SGP40 is not set +# CONFIG_SPS30_I2C is not set +# CONFIG_SPS30_SERIAL is not set +# CONFIG_SENSEAIR_SUNRISE_CO2 is not set +# CONFIG_VZ89X is not set +# end of Chemical Sensors + +CONFIG_IIO_AOP_SENSOR_LAS=m +CONFIG_IIO_AOP_SENSOR_ALS=m + +# +# Hid Sensor IIO Common +# +# CONFIG_HID_SENSOR_IIO_COMMON is not set +# end of Hid Sensor IIO Common + +# +# IIO SCMI Sensors +# +# end of IIO SCMI Sensors + +# +# SSP Sensor Common +# +# CONFIG_IIO_SSP_SENSORHUB is not set +# end of SSP Sensor Common + +# +# Digital to analog converters +# +# CONFIG_AD3530R is not set +# CONFIG_AD3552R_HS is not set +# CONFIG_AD3552R is not set +# CONFIG_AD5064 is not set +# CONFIG_AD5360 is not set +# CONFIG_AD5380 is not set +# CONFIG_AD5421 is not set +# CONFIG_AD5446_SPI is not set +# CONFIG_AD5446_I2C is not set +# CONFIG_AD5449 is not set +# CONFIG_AD5592R is not set +# CONFIG_AD5593R is not set +# CONFIG_AD5504 is not set +# CONFIG_AD5624R_SPI is not set +# CONFIG_AD9739A is not set +# CONFIG_LTC2688 is not set +# CONFIG_AD5686_SPI is not set +# CONFIG_AD5696_I2C is not set +# CONFIG_AD5755 is not set +# CONFIG_AD5758 is not set +# CONFIG_AD5761 is not set +# CONFIG_AD5764 is not set +# CONFIG_AD5766 is not set +# CONFIG_AD5770R is not set +# CONFIG_AD5791 is not set +# CONFIG_AD7293 is not set +# CONFIG_AD7303 is not set +# CONFIG_AD8460 is not set +# CONFIG_AD8801 is not set +# CONFIG_BD79703 is not set +# CONFIG_DPOT_DAC is not set +# CONFIG_DS4424 is not set +# CONFIG_LTC1660 is not set +# CONFIG_LTC2632 is not set +# CONFIG_LTC2664 is not set +# CONFIG_M62332 is not set +# CONFIG_MAX517 is not set +# CONFIG_MAX22007 is not set +# CONFIG_MAX5522 is not set +# CONFIG_MAX5821 is not set +# CONFIG_MCP4725 is not set +# CONFIG_MCP4728 is not set +# CONFIG_MCP47FEB02 is not set +# CONFIG_MCP4821 is not set +# CONFIG_MCP4922 is not set +# CONFIG_TI_DAC082S085 is not set +# CONFIG_TI_DAC5571 is not set +# CONFIG_TI_DAC7311 is not set +# CONFIG_TI_DAC7612 is not set +# CONFIG_VF610_DAC is not set +# end of Digital to analog converters + +# +# IIO dummy driver +# +# end of IIO dummy driver + +# +# Filters +# +# CONFIG_ADMV8818 is not set +# end of Filters + +# +# Frequency Synthesizers DDS/PLL +# + +# +# Clock Generator/Distribution +# +# CONFIG_AD9523 is not set +# end of Clock Generator/Distribution + +# +# Phase-Locked Loop (PLL) frequency synthesizers +# +# CONFIG_ADF4350 is not set +# CONFIG_ADF4371 is not set +# CONFIG_ADF4377 is not set +# CONFIG_ADMFM2000 is not set +# CONFIG_ADMV1013 is not set +# CONFIG_ADMV1014 is not set +# CONFIG_ADMV4420 is not set +# CONFIG_ADRF6780 is not set +# end of Phase-Locked Loop (PLL) frequency synthesizers +# end of Frequency Synthesizers DDS/PLL + +# +# Digital gyroscope sensors +# +# CONFIG_ADIS16080 is not set +# CONFIG_ADIS16130 is not set +# CONFIG_ADIS16136 is not set +# CONFIG_ADIS16260 is not set +# CONFIG_ADXRS290 is not set +# CONFIG_ADXRS450 is not set +# CONFIG_BMG160 is not set +# CONFIG_FXAS21002C is not set +# CONFIG_HID_SENSOR_GYRO_3D is not set +# CONFIG_MPU3050_I2C is not set +# CONFIG_IIO_ST_GYRO_3AXIS is not set +# CONFIG_ITG3200 is not set +# end of Digital gyroscope sensors + +# +# Health Sensors +# + +# +# Heart Rate Monitors +# +# CONFIG_AFE4403 is not set +# CONFIG_AFE4404 is not set +# CONFIG_MAX30100 is not set +# CONFIG_MAX30102 is not set +# end of Heart Rate Monitors +# end of Health Sensors + +# +# Humidity sensors +# +# CONFIG_AM2315 is not set +# CONFIG_DHT11 is not set +# CONFIG_ENS210 is not set +# CONFIG_HDC100X is not set +# CONFIG_HDC2010 is not set +# CONFIG_HDC3020 is not set +# CONFIG_HID_SENSOR_HUMIDITY is not set +# CONFIG_HTS221 is not set +# CONFIG_HTU21 is not set +# CONFIG_SI7005 is not set +# CONFIG_SI7020 is not set +# end of Humidity sensors + +# +# Inertial measurement units +# +# CONFIG_ADIS16400 is not set +# CONFIG_ADIS16460 is not set +# CONFIG_ADIS16475 is not set +# CONFIG_ADIS16480 is not set +# CONFIG_ADIS16550 is not set +# CONFIG_BMI160_I2C is not set +# CONFIG_BMI160_SPI is not set +# CONFIG_BMI270_I2C is not set +# CONFIG_BMI270_SPI is not set +# CONFIG_BMI323_I2C is not set +# CONFIG_BMI323_SPI is not set +# CONFIG_BOSCH_BNO055_SERIAL is not set +# CONFIG_BOSCH_BNO055_I2C is not set +# CONFIG_FXOS8700_I2C is not set +# CONFIG_FXOS8700_SPI is not set +# CONFIG_KMX61 is not set +# CONFIG_INV_ICM42600_I2C is not set +# CONFIG_INV_ICM42600_SPI is not set +# CONFIG_INV_ICM45600_I2C is not set +# CONFIG_INV_ICM45600_SPI is not set +# CONFIG_INV_MPU6050_I2C is not set +# CONFIG_INV_MPU6050_SPI is not set +# CONFIG_SMI240 is not set +# CONFIG_SMI330_I2C is not set +# CONFIG_SMI330_SPI is not set +# CONFIG_IIO_ST_LSM6DSX is not set +# CONFIG_IIO_ST_LSM9DS0 is not set +# end of Inertial measurement units + +# +# Light sensors +# +# CONFIG_ADJD_S311 is not set +# CONFIG_ADUX1020 is not set +# CONFIG_AL3000A is not set +# CONFIG_AL3010 is not set +# CONFIG_AL3320A is not set +# CONFIG_APDS9160 is not set +# CONFIG_APDS9300 is not set +# CONFIG_APDS9306 is not set +# CONFIG_APDS9960 is not set +# CONFIG_AS73211 is not set +# CONFIG_BH1745 is not set +# CONFIG_BH1750 is not set +# CONFIG_BH1780 is not set +# CONFIG_CM32181 is not set +# CONFIG_CM3232 is not set +# CONFIG_CM3323 is not set +# CONFIG_CM3605 is not set +# CONFIG_CM36651 is not set +# CONFIG_GP2AP002 is not set +# CONFIG_GP2AP020A00F is not set +# CONFIG_SENSORS_ISL29018 is not set +# CONFIG_SENSORS_ISL29028 is not set +# CONFIG_ISL29125 is not set +# CONFIG_ISL76682 is not set +# CONFIG_HID_SENSOR_ALS is not set +# CONFIG_HID_SENSOR_PROX is not set +# CONFIG_JSA1212 is not set +# CONFIG_ROHM_BU27034 is not set +# CONFIG_RPR0521 is not set +# CONFIG_LTR390 is not set +# CONFIG_LTR501 is not set +# CONFIG_LTRF216A is not set +# CONFIG_LV0104CS is not set +# CONFIG_MAX44000 is not set +# CONFIG_MAX44009 is not set +# CONFIG_NOA1305 is not set +# CONFIG_OPT3001 is not set +# CONFIG_OPT4001 is not set +# CONFIG_OPT4060 is not set +# CONFIG_PA12203001 is not set +# CONFIG_SI1133 is not set +# CONFIG_SI1145 is not set +# CONFIG_STK3310 is not set +# CONFIG_ST_UVIS25 is not set +# CONFIG_TCS3414 is not set +# CONFIG_TCS3472 is not set +# CONFIG_SENSORS_TSL2563 is not set +# CONFIG_TSL2583 is not set +# CONFIG_TSL2591 is not set +# CONFIG_TSL2772 is not set +# CONFIG_TSL4531 is not set +# CONFIG_US5182D is not set +# CONFIG_VCNL4000 is not set +# CONFIG_VCNL4035 is not set +# CONFIG_VEML3235 is not set +# CONFIG_VEML6030 is not set +# CONFIG_VEML6040 is not set +# CONFIG_VEML6046X00 is not set +# CONFIG_VEML6070 is not set +# CONFIG_VEML6075 is not set +# CONFIG_VL6180 is not set +# CONFIG_ZOPT2201 is not set +# end of Light sensors + +# +# Magnetometer sensors +# +# CONFIG_AF8133J is not set +# CONFIG_AK8974 is not set +# CONFIG_AK8975 is not set +# CONFIG_AK09911 is not set +# CONFIG_ALS31300 is not set +# CONFIG_BMC150_MAGN_I2C is not set +# CONFIG_BMC150_MAGN_SPI is not set +# CONFIG_MAG3110 is not set +# CONFIG_HID_SENSOR_MAGNETOMETER_3D is not set +# CONFIG_MMC35240 is not set +# CONFIG_MMC5633 is not set +# CONFIG_IIO_ST_MAGN_3AXIS is not set +# CONFIG_INFINEON_TLV493D is not set +# CONFIG_SENSORS_HMC5843_I2C is not set +# CONFIG_SENSORS_HMC5843_SPI is not set +# CONFIG_SENSORS_RM3100_I2C is not set +# CONFIG_SENSORS_RM3100_SPI is not set +# CONFIG_SI7210 is not set +# CONFIG_TI_TMAG5273 is not set +# CONFIG_YAMAHA_YAS530 is not set +# end of Magnetometer sensors + +# +# Multiplexers +# +# CONFIG_IIO_MUX is not set +# end of Multiplexers + +# +# Inclinometer sensors +# +# CONFIG_HID_SENSOR_INCLINOMETER_3D is not set +# CONFIG_HID_SENSOR_DEVICE_ROTATION is not set +# end of Inclinometer sensors + +# +# Linear and angular position sensors +# +# CONFIG_HID_SENSOR_CUSTOM_INTEL_HINGE is not set +# end of Linear and angular position sensors + +# +# Digital potentiometers +# +# CONFIG_AD5110 is not set +# CONFIG_AD5272 is not set +# CONFIG_DS1803 is not set +# CONFIG_MAX5432 is not set +# CONFIG_MAX5481 is not set +# CONFIG_MAX5487 is not set +# CONFIG_MCP4018 is not set +# CONFIG_MCP4131 is not set +# CONFIG_MCP4531 is not set +# CONFIG_MCP41010 is not set +# CONFIG_TPL0102 is not set +# CONFIG_X9250 is not set +# end of Digital potentiometers + +# +# Digital potentiostats +# +# CONFIG_LMP91000 is not set +# end of Digital potentiostats + +# +# Pressure sensors +# +# CONFIG_ABP060MG is not set +# CONFIG_ABP2030PA_I2C is not set +# CONFIG_ABP2030PA_SPI is not set +# CONFIG_ROHM_BM1390 is not set +# CONFIG_BMP280 is not set +# CONFIG_DLHL60D is not set +# CONFIG_DPS310 is not set +# CONFIG_HID_SENSOR_PRESS is not set +# CONFIG_HP03 is not set +# CONFIG_HSC030PA is not set +# CONFIG_ICP10100 is not set +# CONFIG_MPL115_I2C is not set +# CONFIG_MPL115_SPI is not set +# CONFIG_MPL3115 is not set +# CONFIG_MPRLS0025PA_I2C is not set +# CONFIG_MPRLS0025PA_SPI is not set +# CONFIG_MS5611 is not set +# CONFIG_MS5637 is not set +# CONFIG_SDP500 is not set +# CONFIG_IIO_ST_PRESS is not set +# CONFIG_T5403 is not set +# CONFIG_HP206C is not set +# CONFIG_ZPA2326 is not set +# CONFIG_ADP810 is not set +# end of Pressure sensors + +# +# Lightning sensors +# +# CONFIG_AS3935 is not set +# end of Lightning sensors + +# +# Proximity and distance sensors +# +# CONFIG_D3323AA is not set +# CONFIG_HX9023S is not set +# CONFIG_IRSD200 is not set +# CONFIG_ISL29501 is not set +# CONFIG_LIDAR_LITE_V2 is not set +# CONFIG_MB1232 is not set +# CONFIG_PING is not set +# CONFIG_RFD77402 is not set +# CONFIG_SRF04 is not set +# CONFIG_SX9310 is not set +# CONFIG_SX9324 is not set +# CONFIG_SX9360 is not set +# CONFIG_SX9500 is not set +# CONFIG_SRF08 is not set +# CONFIG_VCNL3020 is not set +# CONFIG_VL53L0X_I2C is not set +# CONFIG_VL53L1X_I2C is not set +# CONFIG_AW96103 is not set +# end of Proximity and distance sensors + +# +# Resolver to digital converters +# +# CONFIG_AD2S90 is not set +# CONFIG_AD2S1200 is not set +# CONFIG_AD2S1210 is not set +# end of Resolver to digital converters + +# +# Temperature sensors +# +# CONFIG_LTC2983 is not set +# CONFIG_MAXIM_THERMOCOUPLE is not set +# CONFIG_HID_SENSOR_TEMP is not set +# CONFIG_MLX90614 is not set +# CONFIG_MLX90632 is not set +# CONFIG_MLX90635 is not set +# CONFIG_TMP006 is not set +# CONFIG_TMP007 is not set +# CONFIG_TMP117 is not set +# CONFIG_TSYS01 is not set +# CONFIG_TSYS02D is not set +# CONFIG_MAX30208 is not set +# CONFIG_MAX31856 is not set +# CONFIG_MAX31865 is not set +# CONFIG_MCP9600 is not set +# end of Temperature sensors + +# CONFIG_NTB is not set +CONFIG_PWM=y +# CONFIG_PWM_DEBUG is not set +# CONFIG_PWM_PROVIDE_GPIO is not set +CONFIG_PWM_APPLE=y +# CONFIG_PWM_ARGON_FAN_HAT is not set +# CONFIG_PWM_ATMEL_TCB is not set +# CONFIG_PWM_CLK is not set +# CONFIG_PWM_DWC is not set +# CONFIG_PWM_FSL_FTM is not set +# CONFIG_PWM_GPIO is not set +# CONFIG_PWM_MC33XS2410 is not set +# CONFIG_PWM_PCA9685 is not set +# CONFIG_PWM_XILINX is not set + +# +# IRQ chip support +# +CONFIG_IRQCHIP=y +CONFIG_ARM_GIC=y +CONFIG_ARM_GIC_MAX_NR=1 +CONFIG_ARM_GIC_V2M=y +CONFIG_ARM_GIC_V3=y +CONFIG_ARM_GIC_ITS_PARENT=y +CONFIG_ARM_GIC_V3_ITS=y +CONFIG_ARM_GIC_V5=y +CONFIG_IRQ_MSI_LIB=y +# CONFIG_AL_FIC is not set +# CONFIG_XILINX_INTC is not set +CONFIG_APPLE_AIC=y +# end of IRQ chip support + +# CONFIG_IPACK_BUS is not set +CONFIG_RESET_CONTROLLER=y +# CONFIG_RESET_GPIO is not set +# CONFIG_RESET_SIMPLE is not set +# CONFIG_RESET_TI_SYSCON is not set +# CONFIG_RESET_TI_TPS380X is not set + +# +# PHY Subsystem +# +CONFIG_GENERIC_PHY=y +# CONFIG_PHY_CAN_TRANSCEIVER is not set +# CONFIG_PHY_GOOGLE_USB is not set +# CONFIG_PHY_NXP_PTN3222 is not set +CONFIG_PHY_APPLE_ATC=m +CONFIG_PHY_APPLE_DPTX=m + +# +# PHY drivers for Broadcom platforms +# +# CONFIG_BCM_KONA_USB2_PHY is not set +# end of PHY drivers for Broadcom platforms + +# CONFIG_PHY_CADENCE_TORRENT is not set +# CONFIG_PHY_CADENCE_DPHY is not set +# CONFIG_PHY_CADENCE_DPHY_RX is not set +# CONFIG_PHY_CADENCE_SIERRA is not set +# CONFIG_PHY_CADENCE_SALVO is not set +# CONFIG_PHY_PXA_28NM_HSIC is not set +# CONFIG_PHY_PXA_28NM_USB2 is not set +# CONFIG_PHY_CPCAP_USB is not set +# CONFIG_PHY_MAPPHONE_MDM6600 is not set +# CONFIG_PHY_OCELOT_SERDES is not set +# CONFIG_PHY_QCOM_USB_HS is not set +# CONFIG_PHY_QCOM_USB_HSIC is not set +# CONFIG_PHY_TUSB1210 is not set +# end of PHY Subsystem + +CONFIG_POWERCAP=y +# CONFIG_IDLE_INJECT is not set +# CONFIG_DTPM is not set +# CONFIG_MCB is not set + +# +# Performance monitor support +# +# CONFIG_ARM_CCI_PMU is not set +# CONFIG_ARM_CCN is not set +# CONFIG_ARM_CMN is not set +# CONFIG_ARM_NI is not set +CONFIG_ARM_PMU=y +# CONFIG_ARM_SMMU_V3_PMU is not set +CONFIG_ARM_PMUV3=y +# CONFIG_ARM_DSU_PMU is not set +# CONFIG_ARM_SPE_PMU is not set +CONFIG_ARM64_BRBE=y +CONFIG_APPLE_M1_CPU_PMU=y +# CONFIG_HISI_PCIE_PMU is not set +# CONFIG_HNS3_PMU is not set +# CONFIG_DWC_PCIE_PMU is not set +# CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU is not set +# end of Performance monitor support + +CONFIG_RAS=y +CONFIG_USB4=m +CONFIG_USB4_NET=m +# CONFIG_USB4_DEBUGFS_WRITE is not set +# CONFIG_USB4_DMA_TEST is not set +CONFIG_USB4_APPLE_SOC=m +CONFIG_RESET_APPLE_CIO=y + +# +# Android +# +# CONFIG_ANDROID_BINDER_IPC is not set +# CONFIG_ANDROID_BINDER_IPC_RUST is not set +# end of Android + +# CONFIG_LIBNVDIMM is not set +# CONFIG_DAX is not set +CONFIG_NVMEM=y +CONFIG_NVMEM_SYSFS=y +CONFIG_NVMEM_LAYOUTS=y + +# +# Layout Types +# +# CONFIG_NVMEM_LAYOUT_SL28_VPD is not set +# CONFIG_NVMEM_LAYOUT_ONIE_TLV is not set +# CONFIG_NVMEM_LAYOUT_U_BOOT_ENV is not set +# end of Layout Types + +CONFIG_NVMEM_APPLE_EFUSES=y +CONFIG_NVMEM_APPLE_SPMI=y +# CONFIG_NVMEM_RMEM is not set +# CONFIG_NVMEM_SPMI_SDAM is not set +# CONFIG_NVMEM_U_BOOT_ENV is not set + +# +# HW tracing support +# +# CONFIG_STM is not set +# CONFIG_INTEL_TH is not set +# CONFIG_HISI_PTT is not set +# end of HW tracing support + +# CONFIG_FPGA is not set +# CONFIG_FSI is not set +# CONFIG_TEE is not set +CONFIG_MULTIPLEXER=y +# CONFIG_MUX_CORE is not set + +# +# Multiplexer drivers +# +# CONFIG_MUX_ADG792A is not set +# CONFIG_MUX_ADGS1408 is not set +CONFIG_MUX_APPLE_DPXBAR=m +# CONFIG_MUX_GPIO is not set +# CONFIG_MUX_MMIO is not set +# end of Multiplexer drivers + +CONFIG_PM_OPP=y +# CONFIG_SIOX is not set +# CONFIG_SLIMBUS is not set +# CONFIG_INTERCONNECT is not set +# CONFIG_COUNTER is not set +# CONFIG_MOST is not set +# CONFIG_PECI is not set +# CONFIG_HTE is not set +# CONFIG_CDX_BUS is not set +# end of Device Drivers + +# +# File systems +# +CONFIG_DCACHE_WORD_ACCESS=y +CONFIG_VALIDATE_FS_PARSER=y +CONFIG_FS_IOMAP=y +CONFIG_FS_STACK=y +CONFIG_BUFFER_HEAD=y +CONFIG_LEGACY_DIRECT_IO=y +# CONFIG_EXT2_FS is not set +CONFIG_EXT4_FS=y +CONFIG_EXT4_USE_FOR_EXT2=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +# CONFIG_EXT4_DEBUG is not set +CONFIG_JBD2=y +# CONFIG_JBD2_DEBUG is not set +CONFIG_FS_MBCACHE=y +CONFIG_JFS_FS=m +CONFIG_JFS_POSIX_ACL=y +CONFIG_JFS_SECURITY=y +# CONFIG_JFS_DEBUG is not set +# CONFIG_JFS_STATISTICS is not set +CONFIG_XFS_FS=m +CONFIG_XFS_SUPPORT_V4=y +CONFIG_XFS_SUPPORT_ASCII_CI=y +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +# CONFIG_XFS_RT is not set +# CONFIG_XFS_ONLINE_SCRUB is not set +# CONFIG_XFS_WARN is not set +# CONFIG_XFS_DEBUG is not set +CONFIG_GFS2_FS=m +CONFIG_GFS2_FS_LOCKING_DLM=y +CONFIG_OCFS2_FS=m +CONFIG_OCFS2_FS_O2CB=m +CONFIG_OCFS2_FS_USERSPACE_CLUSTER=m +# CONFIG_OCFS2_FS_STATS is not set +# CONFIG_OCFS2_DEBUG_MASKLOG is not set +# CONFIG_OCFS2_DEBUG_FS is not set +CONFIG_BTRFS_FS=m +CONFIG_BTRFS_FS_POSIX_ACL=y +# CONFIG_BTRFS_FS_RUN_SANITY_TESTS is not set +# CONFIG_BTRFS_DEBUG is not set +# CONFIG_BTRFS_ASSERT is not set +# CONFIG_BTRFS_EXPERIMENTAL is not set +CONFIG_NILFS2_FS=m +# CONFIG_F2FS_FS is not set +# CONFIG_ZONEFS_FS is not set +CONFIG_FS_POSIX_ACL=y +CONFIG_EXPORTFS=y +CONFIG_EXPORTFS_BLOCK_OPS=y +CONFIG_FILE_LOCKING=y +CONFIG_FS_ENCRYPTION=y +CONFIG_FS_ENCRYPTION_ALGS=y +# CONFIG_FS_VERITY is not set +CONFIG_FSNOTIFY=y +CONFIG_DNOTIFY=y +CONFIG_INOTIFY_USER=y +CONFIG_FANOTIFY=y +CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y +CONFIG_QUOTA=y +CONFIG_QUOTA_NETLINK_INTERFACE=y +# CONFIG_QUOTA_DEBUG is not set +CONFIG_QUOTA_TREE=y +# CONFIG_QFMT_V1 is not set +CONFIG_QFMT_V2=y +CONFIG_QUOTACTL=y +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=m +CONFIG_CUSE=m +# CONFIG_VIRTIO_FS is not set +CONFIG_FUSE_PASSTHROUGH=y +CONFIG_FUSE_IO_URING=y +CONFIG_OVERLAY_FS=m +# CONFIG_OVERLAY_FS_REDIRECT_DIR is not set +CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW=y +# CONFIG_OVERLAY_FS_INDEX is not set +# CONFIG_OVERLAY_FS_XINO_AUTO is not set +# CONFIG_OVERLAY_FS_METACOPY is not set +# CONFIG_OVERLAY_FS_DEBUG is not set + +# +# Caches +# +CONFIG_NETFS_SUPPORT=m +CONFIG_NETFS_STATS=y +# CONFIG_NETFS_DEBUG is not set +CONFIG_FSCACHE=y +# CONFIG_FSCACHE_STATS is not set +CONFIG_CACHEFILES=m +# CONFIG_CACHEFILES_DEBUG is not set +# CONFIG_CACHEFILES_ERROR_INJECTION is not set +CONFIG_CACHEFILES_ONDEMAND=y +# end of Caches + +# +# CD-ROM/DVD Filesystems +# +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +# end of CD-ROM/DVD Filesystems + +# +# DOS/FAT/EXFAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=m +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="ascii" +# CONFIG_FAT_DEFAULT_UTF8 is not set +CONFIG_EXFAT_FS=m +CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8" +CONFIG_NTFS_FS=m +# CONFIG_NTFS_DEBUG is not set +CONFIG_NTFS_FS_POSIX_ACL=y +CONFIG_NTFS3_FS=m +# CONFIG_NTFS3_64BIT_CLUSTER is not set +CONFIG_NTFS3_LZX_XPRESS=y +CONFIG_NTFS3_FS_POSIX_ACL=y +# end of DOS/FAT/EXFAT/NT Filesystems + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_KCORE=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_PROC_CHILDREN=y +CONFIG_KERNFS=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_TMPFS_XATTR=y +# CONFIG_TMPFS_INODE64 is not set +# CONFIG_TMPFS_QUOTA is not set +CONFIG_ARCH_SUPPORTS_HUGETLBFS=y +CONFIG_HUGETLBFS=y +CONFIG_HUGETLB_PAGE=y +CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING=y +CONFIG_ARCH_HAS_GIGANTIC_PAGE=y +CONFIG_CONFIGFS_FS=y +CONFIG_EFIVAR_FS=y +# end of Pseudo filesystems + +CONFIG_MISC_FILESYSTEMS=y +# CONFIG_ORANGEFS_FS is not set +# CONFIG_ADFS_FS is not set +CONFIG_AFFS_FS=m +CONFIG_ECRYPT_FS=m +# CONFIG_ECRYPT_FS_MESSAGING is not set +CONFIG_HFS_FS=m +CONFIG_HFSPLUS_FS=m +CONFIG_BEFS_FS=m +# CONFIG_BEFS_DEBUG is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +# CONFIG_JFFS2_FS is not set +CONFIG_CRAMFS=m +CONFIG_CRAMFS_BLOCKDEV=y +# CONFIG_CRAMFS_MTD is not set +CONFIG_SQUASHFS=m +CONFIG_SQUASHFS_FILE_CACHE=y +# CONFIG_SQUASHFS_FILE_DIRECT is not set +CONFIG_SQUASHFS_DECOMP_SINGLE=y +# CONFIG_SQUASHFS_CHOICE_DECOMP_BY_MOUNT is not set +CONFIG_SQUASHFS_COMPILE_DECOMP_SINGLE=y +# CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI is not set +# CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI_PERCPU is not set +CONFIG_SQUASHFS_XATTR=y +# CONFIG_SQUASHFS_COMP_CACHE_FULL is not set +CONFIG_SQUASHFS_ZLIB=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +# CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set +# CONFIG_SQUASHFS_EMBEDDED is not set +CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3 +# CONFIG_VXFS_FS is not set +CONFIG_MINIX_FS=m +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_QNX6FS_FS is not set +CONFIG_ROMFS_FS=m +CONFIG_ROMFS_BACKED_BY_BLOCK=y +# CONFIG_ROMFS_BACKED_BY_MTD is not set +# CONFIG_ROMFS_BACKED_BY_BOTH is not set +CONFIG_ROMFS_ON_BLOCK=y +CONFIG_PSTORE=y +CONFIG_PSTORE_DEFAULT_KMSG_BYTES=10240 +CONFIG_PSTORE_COMPRESS=y +# CONFIG_PSTORE_CONSOLE is not set +# CONFIG_PSTORE_PMSG is not set +CONFIG_PSTORE_RAM=m +# CONFIG_PSTORE_BLK is not set +CONFIG_UFS_FS=m +# CONFIG_UFS_FS_WRITE is not set +# CONFIG_UFS_DEBUG is not set +CONFIG_EROFS_FS=m +# CONFIG_EROFS_FS_DEBUG is not set +CONFIG_EROFS_FS_XATTR=y +CONFIG_EROFS_FS_POSIX_ACL=y +CONFIG_EROFS_FS_SECURITY=y +CONFIG_EROFS_FS_BACKED_BY_FILE=y +CONFIG_EROFS_FS_ZIP=y +CONFIG_EROFS_FS_ZIP_LZMA=y +CONFIG_EROFS_FS_ZIP_DEFLATE=y +CONFIG_EROFS_FS_ZIP_ZSTD=y +# CONFIG_EROFS_FS_ZIP_ACCEL is not set +CONFIG_EROFS_FS_ONDEMAND=y +# CONFIG_EROFS_FS_PCPU_KTHREAD is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +# CONFIG_NFS_V2 is not set +CONFIG_NFS_V3=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_0=y +CONFIG_NFS_V4_2=y +CONFIG_PNFS_FILE_LAYOUT=y +CONFIG_PNFS_BLOCK=y +CONFIG_PNFS_FLEXFILE_LAYOUT=y +CONFIG_NFS_V4_1_IMPLEMENTATION_ID_DOMAIN="kernel.org" +# CONFIG_NFS_V4_1_MIGRATION is not set +CONFIG_NFS_V4_SECURITY_LABEL=y +# CONFIG_NFS_FSCACHE is not set +# CONFIG_NFS_USE_LEGACY_DNS is not set +CONFIG_NFS_USE_KERNEL_DNS=y +CONFIG_NFS_DEBUG=y +CONFIG_NFS_DISABLE_UDP_SUPPORT=y +# CONFIG_NFS_V4_2_READ_PLUS is not set +CONFIG_NFSD=m +# CONFIG_NFSD_V2 is not set +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_NFSD_PNFS=y +CONFIG_NFSD_BLOCKLAYOUT=y +CONFIG_NFSD_SCSILAYOUT=y +CONFIG_NFSD_FLEXFILELAYOUT=y +CONFIG_NFSD_V4_2_INTER_SSC=y +CONFIG_NFSD_V4_SECURITY_LABEL=y +# CONFIG_NFSD_LEGACY_CLIENT_TRACKING is not set +CONFIG_NFSD_V4_POSIX_ACLS=y +CONFIG_GRACE_PERIOD=y +CONFIG_LOCKD=y +CONFIG_LOCKD_V4=y +CONFIG_NFS_ACL_SUPPORT=y +CONFIG_NFS_COMMON=y +# CONFIG_NFS_LOCALIO is not set +CONFIG_NFS_V4_2_SSC_HELPER=y +CONFIG_SUNRPC=y +CONFIG_SUNRPC_GSS=m +CONFIG_SUNRPC_BACKCHANNEL=y +CONFIG_SUNRPC_SWAP=y +CONFIG_RPCSEC_GSS_KRB5=m +CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1=y +# CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA is not set +# CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2 is not set +CONFIG_SUNRPC_DEBUG=y +# CONFIG_SUNRPC_DEBUG_TRACE is not set +CONFIG_CEPH_FS=m +# CONFIG_CEPH_FSCACHE is not set +CONFIG_CEPH_FS_POSIX_ACL=y +# CONFIG_CEPH_FS_SECURITY_LABEL is not set +CONFIG_CIFS=m +# CONFIG_CIFS_STATS2 is not set +CONFIG_CIFS_ALLOW_INSECURE_LEGACY=y +CONFIG_CIFS_UPCALL=y +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_POSIX=y +CONFIG_CIFS_DEBUG=y +# CONFIG_CIFS_DEBUG2 is not set +# CONFIG_CIFS_DEBUG_DUMP_KEYS is not set +CONFIG_CIFS_DFS_UPCALL=y +# CONFIG_CIFS_SWN_UPCALL is not set +# CONFIG_CIFS_FSCACHE is not set +# CONFIG_CIFS_COMPRESSION is not set +# CONFIG_SMB_SERVER is not set +CONFIG_SMBFS=m +CONFIG_CODA_FS=m +# CONFIG_AFS_FS is not set +CONFIG_9P_FS=m +# CONFIG_9P_FSCACHE is not set +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_9P_FS_SECURITY=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=m +CONFIG_NLS_CODEPAGE_775=m +CONFIG_NLS_CODEPAGE_850=m +CONFIG_NLS_CODEPAGE_852=m +CONFIG_NLS_CODEPAGE_855=m +CONFIG_NLS_CODEPAGE_857=m +CONFIG_NLS_CODEPAGE_860=m +CONFIG_NLS_CODEPAGE_861=m +CONFIG_NLS_CODEPAGE_862=m +CONFIG_NLS_CODEPAGE_863=m +CONFIG_NLS_CODEPAGE_864=m +CONFIG_NLS_CODEPAGE_865=m +CONFIG_NLS_CODEPAGE_866=m +CONFIG_NLS_CODEPAGE_869=m +CONFIG_NLS_CODEPAGE_936=m +CONFIG_NLS_CODEPAGE_950=m +CONFIG_NLS_CODEPAGE_932=m +CONFIG_NLS_CODEPAGE_949=m +CONFIG_NLS_CODEPAGE_874=m +CONFIG_NLS_ISO8859_8=m +CONFIG_NLS_CODEPAGE_1250=m +CONFIG_NLS_CODEPAGE_1251=m +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=m +CONFIG_NLS_ISO8859_2=m +CONFIG_NLS_ISO8859_3=m +CONFIG_NLS_ISO8859_4=m +CONFIG_NLS_ISO8859_5=m +CONFIG_NLS_ISO8859_6=m +CONFIG_NLS_ISO8859_7=m +CONFIG_NLS_ISO8859_9=m +CONFIG_NLS_ISO8859_13=m +CONFIG_NLS_ISO8859_14=m +CONFIG_NLS_ISO8859_15=m +CONFIG_NLS_KOI8_R=m +CONFIG_NLS_KOI8_U=m +CONFIG_NLS_MAC_ROMAN=m +CONFIG_NLS_MAC_CELTIC=m +CONFIG_NLS_MAC_CENTEURO=m +CONFIG_NLS_MAC_CROATIAN=m +CONFIG_NLS_MAC_CYRILLIC=m +CONFIG_NLS_MAC_GAELIC=m +CONFIG_NLS_MAC_GREEK=m +CONFIG_NLS_MAC_ICELAND=m +CONFIG_NLS_MAC_INUIT=m +CONFIG_NLS_MAC_ROMANIAN=m +CONFIG_NLS_MAC_TURKISH=m +CONFIG_NLS_UTF8=y +CONFIG_NLS_UCS2_UTILS=m +CONFIG_DLM=m +CONFIG_DLM_DEBUG=y +CONFIG_UNICODE=m +CONFIG_IO_WQ=y +# end of File systems + +# +# Security options +# +CONFIG_KEYS=y +# CONFIG_KEYS_REQUEST_CACHE is not set +CONFIG_PERSISTENT_KEYRINGS=y +# CONFIG_BIG_KEYS is not set +# CONFIG_TRUSTED_KEYS is not set +CONFIG_ENCRYPTED_KEYS=y +# CONFIG_USER_DECRYPTED_DATA is not set +CONFIG_KEY_DH_OPERATIONS=y +# CONFIG_KEY_NOTIFICATIONS is not set +# CONFIG_SECURITY_DMESG_RESTRICT is not set +CONFIG_PROC_MEM_ALWAYS_FORCE=y +# CONFIG_PROC_MEM_FORCE_PTRACE is not set +# CONFIG_PROC_MEM_NO_FORCE is not set +CONFIG_SECURITY=y +CONFIG_HAS_SECURITY_AUDIT=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +CONFIG_SECURITY_NETWORK_XFRM=y +CONFIG_SECURITY_PATH=y +# CONFIG_STATIC_USERMODEHELPER is not set +# CONFIG_SECURITY_SELINUX is not set +# CONFIG_SECURITY_SMACK is not set +# CONFIG_SECURITY_TOMOYO is not set +CONFIG_SECURITY_APPARMOR=y +# CONFIG_SECURITY_APPARMOR_DEBUG is not set +CONFIG_SECURITY_APPARMOR_INTROSPECT_POLICY=y +CONFIG_SECURITY_APPARMOR_HASH=y +CONFIG_SECURITY_APPARMOR_HASH_DEFAULT=y +CONFIG_SECURITY_APPARMOR_EXPORT_BINARY=y +CONFIG_SECURITY_APPARMOR_PARANOID_LOAD=y +# CONFIG_SECURITY_LOADPIN is not set +CONFIG_SECURITY_YAMA=y +# CONFIG_SECURITY_SAFESETID is not set +# CONFIG_SECURITY_LOCKDOWN_LSM is not set +CONFIG_SECURITY_LANDLOCK=y +# CONFIG_SECURITY_IPE is not set +# CONFIG_INTEGRITY is not set +# CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set +# CONFIG_DEFAULT_SECURITY_APPARMOR is not set +CONFIG_DEFAULT_SECURITY_DAC=y +CONFIG_LSM="landlock,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor" + +# +# Kernel hardening options +# + +# +# Memory initialization +# +CONFIG_CC_HAS_AUTO_VAR_INIT_PATTERN=y +CONFIG_CC_HAS_AUTO_VAR_INIT_ZERO_BARE=y +CONFIG_CC_HAS_AUTO_VAR_INIT_ZERO=y +CONFIG_INIT_STACK_NONE=y +# CONFIG_INIT_STACK_ALL_PATTERN is not set +# CONFIG_INIT_STACK_ALL_ZERO is not set +CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y +# CONFIG_INIT_ON_FREE_DEFAULT_ON is not set +CONFIG_CC_HAS_ZERO_CALL_USED_REGS=y +# CONFIG_ZERO_CALL_USED_REGS is not set +# end of Memory initialization + +# +# Bounds checking +# +# CONFIG_FORTIFY_SOURCE is not set +# CONFIG_HARDENED_USERCOPY is not set +# end of Bounds checking + +# +# Hardening of kernel data structures +# +# CONFIG_LIST_HARDENED is not set +# CONFIG_RUST_BITMAP_HARDENED is not set +# CONFIG_BUG_ON_DATA_CORRUPTION is not set +# end of Hardening of kernel data structures + +CONFIG_RANDSTRUCT_NONE=y +# end of Kernel hardening options +# end of Security options + +CONFIG_ASYNC_CORE=m +CONFIG_ASYNC_MEMCPY=m +CONFIG_ASYNC_XOR=m +CONFIG_ASYNC_PQ=m +CONFIG_ASYNC_RAID6_RECOV=m +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +CONFIG_CRYPTO_AEAD=y +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_SIG=y +CONFIG_CRYPTO_SIG2=y +CONFIG_CRYPTO_SKCIPHER=y +CONFIG_CRYPTO_SKCIPHER2=y +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_RNG=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_AKCIPHER2=y +CONFIG_CRYPTO_AKCIPHER=y +CONFIG_CRYPTO_KPP2=y +CONFIG_CRYPTO_KPP=y +CONFIG_CRYPTO_ACOMP2=y +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +CONFIG_CRYPTO_USER=m +# CONFIG_CRYPTO_SELFTESTS is not set +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_PCRYPT=m +CONFIG_CRYPTO_CRYPTD=y +CONFIG_CRYPTO_AUTHENC=m +CONFIG_CRYPTO_KRB5ENC=m +# CONFIG_CRYPTO_BENCHMARK is not set +CONFIG_CRYPTO_ENGINE=m +# end of Crypto core or helper + +# +# Public-key cryptography +# +CONFIG_CRYPTO_RSA=y +CONFIG_CRYPTO_DH=y +# CONFIG_CRYPTO_DH_RFC7919_GROUPS is not set +CONFIG_CRYPTO_ECC=m +CONFIG_CRYPTO_ECDH=m +# CONFIG_CRYPTO_ECDSA is not set +# CONFIG_CRYPTO_ECRDSA is not set +# CONFIG_CRYPTO_MLDSA is not set +# end of Public-key cryptography + +# +# Block ciphers +# +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_ANUBIS=m +CONFIG_CRYPTO_ARIA=m +CONFIG_CRYPTO_BLOWFISH=m +CONFIG_CRYPTO_BLOWFISH_COMMON=m +CONFIG_CRYPTO_CAMELLIA=m +CONFIG_CRYPTO_CAST_COMMON=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_CAST6=m +CONFIG_CRYPTO_DES=m +CONFIG_CRYPTO_FCRYPT=m +CONFIG_CRYPTO_KHAZAD=m +CONFIG_CRYPTO_SEED=m +CONFIG_CRYPTO_SERPENT=m +CONFIG_CRYPTO_SM4=y +CONFIG_CRYPTO_SM4_GENERIC=m +CONFIG_CRYPTO_TEA=m +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_TWOFISH_COMMON=m +# end of Block ciphers + +# +# Length-preserving ciphers and modes +# +CONFIG_CRYPTO_ADIANTUM=m +CONFIG_CRYPTO_ARC4=m +CONFIG_CRYPTO_CHACHA20=y +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_CTR=y +CONFIG_CRYPTO_CTS=y +CONFIG_CRYPTO_ECB=y +# CONFIG_CRYPTO_HCTR2 is not set +CONFIG_CRYPTO_LRW=m +CONFIG_CRYPTO_PCBC=m +CONFIG_CRYPTO_XTS=y +# end of Length-preserving ciphers and modes + +# +# AEAD (authenticated encryption with associated data) ciphers +# +CONFIG_CRYPTO_AEGIS128=m +# CONFIG_CRYPTO_AEGIS128_SIMD is not set +CONFIG_CRYPTO_CHACHA20POLY1305=m +CONFIG_CRYPTO_CCM=m +CONFIG_CRYPTO_GCM=y +CONFIG_CRYPTO_GENIV=y +CONFIG_CRYPTO_SEQIV=y +CONFIG_CRYPTO_ECHAINIV=m +CONFIG_CRYPTO_ESSIV=m +# end of AEAD (authenticated encryption with associated data) ciphers + +# +# Hashes, digests, and MACs +# +CONFIG_CRYPTO_BLAKE2B=y +CONFIG_CRYPTO_CMAC=m +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_MD4=m +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_RMD160=m +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_SHA512=y +CONFIG_CRYPTO_SHA3=y +CONFIG_CRYPTO_SM3=m +# CONFIG_CRYPTO_STREEBOG is not set +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_XCBC=m +CONFIG_CRYPTO_XXHASH=y +# end of Hashes, digests, and MACs + +# +# CRCs (cyclic redundancy checks) +# +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_CRC32=y +# end of CRCs (cyclic redundancy checks) + +# +# Compression +# +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_LZO=y +CONFIG_CRYPTO_842=m +CONFIG_CRYPTO_LZ4=m +CONFIG_CRYPTO_LZ4HC=m +CONFIG_CRYPTO_ZSTD=m +# end of Compression + +# +# Random number generation +# +CONFIG_CRYPTO_DRBG_MENU=y +CONFIG_CRYPTO_DRBG_HMAC=y +CONFIG_CRYPTO_DRBG_HASH=y +CONFIG_CRYPTO_DRBG_CTR=y +CONFIG_CRYPTO_DRBG=y +CONFIG_CRYPTO_JITTERENTROPY=y +CONFIG_CRYPTO_JITTERENTROPY_MEMORY_BLOCKS=64 +CONFIG_CRYPTO_JITTERENTROPY_MEMORY_BLOCKSIZE=32 +CONFIG_CRYPTO_JITTERENTROPY_OSR=1 +CONFIG_CRYPTO_KDF800108_CTR=y +CONFIG_CRYPTO_DF80090A=y +# end of Random number generation + +# +# Userspace interface +# +CONFIG_CRYPTO_USER_API=y +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y +CONFIG_CRYPTO_USER_API_RNG=y +# CONFIG_CRYPTO_USER_API_RNG_CAVP is not set +CONFIG_CRYPTO_USER_API_AEAD=y +CONFIG_CRYPTO_USER_API_ENABLE_OBSOLETE=y +# end of Userspace interface + +# +# Accelerated Cryptographic Algorithms for CPU (arm64) +# +CONFIG_CRYPTO_GHASH_ARM64_CE=y +CONFIG_CRYPTO_AES_ARM64_CE_BLK=y +CONFIG_CRYPTO_AES_ARM64_NEON_BLK=y +CONFIG_CRYPTO_AES_ARM64_BS=y +CONFIG_CRYPTO_SM4_ARM64_CE=y +CONFIG_CRYPTO_SM4_ARM64_CE_BLK=y +CONFIG_CRYPTO_SM4_ARM64_NEON_BLK=y +CONFIG_CRYPTO_AES_ARM64_CE_CCM=y +# CONFIG_CRYPTO_SM4_ARM64_CE_CCM is not set +# CONFIG_CRYPTO_SM4_ARM64_CE_GCM is not set +# end of Accelerated Cryptographic Algorithms for CPU (arm64) + +CONFIG_CRYPTO_HW=y +# CONFIG_CRYPTO_DEV_ATMEL_ECC is not set +# CONFIG_CRYPTO_DEV_ATMEL_SHA204A is not set +# CONFIG_CRYPTO_DEV_CCP is not set +# CONFIG_CRYPTO_DEV_NITROX_CNN55XX is not set +# CONFIG_CRYPTO_DEV_QAT_DH895xCC is not set +# CONFIG_CRYPTO_DEV_QAT_C3XXX is not set +# CONFIG_CRYPTO_DEV_QAT_C62X is not set +# CONFIG_CRYPTO_DEV_QAT_4XXX is not set +# CONFIG_CRYPTO_DEV_QAT_420XX is not set +# CONFIG_CRYPTO_DEV_QAT_DH895xCCVF is not set +# CONFIG_CRYPTO_DEV_QAT_C3XXXVF is not set +# CONFIG_CRYPTO_DEV_QAT_C62XVF is not set +CONFIG_CRYPTO_DEV_VIRTIO=m +# CONFIG_CRYPTO_DEV_SAFEXCEL is not set +# CONFIG_CRYPTO_DEV_CCREE is not set +# CONFIG_CRYPTO_DEV_HISI_SEC is not set +# CONFIG_CRYPTO_DEV_AMLOGIC_GXL is not set +CONFIG_ASYMMETRIC_KEY_TYPE=y +CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y +CONFIG_X509_CERTIFICATE_PARSER=y +CONFIG_PKCS8_PRIVATE_KEY_PARSER=y +CONFIG_PKCS7_MESSAGE_PARSER=y +# CONFIG_PKCS7_TEST_KEY is not set +# CONFIG_SIGNED_PE_FILE_VERIFICATION is not set +# CONFIG_FIPS_SIGNATURE_SELFTEST is not set + +# +# Certificates for signature checking +# +CONFIG_SYSTEM_TRUSTED_KEYRING=y +CONFIG_SYSTEM_TRUSTED_KEYS="" +# CONFIG_SYSTEM_EXTRA_CERTIFICATE is not set +# CONFIG_SECONDARY_TRUSTED_KEYRING is not set +# CONFIG_SYSTEM_BLACKLIST_KEYRING is not set +CONFIG_OPENSSL_SUPPORTS_ML_DSA=y +# end of Certificates for signature checking + +CONFIG_CRYPTO_KRB5=m +# CONFIG_CRYPTO_KRB5_SELFTESTS is not set +CONFIG_BINARY_PRINTF=y + +# +# Library routines +# +CONFIG_RAID6_PQ=m +CONFIG_RAID6_PQ_BENCHMARK=y +CONFIG_LINEAR_RANGES=y +# CONFIG_PACKING is not set +CONFIG_BITREVERSE=y +CONFIG_HAVE_ARCH_BITREVERSE=y +CONFIG_GENERIC_STRNCPY_FROM_USER=y +CONFIG_GENERIC_STRNLEN_USER=y +CONFIG_GENERIC_NET_UTILS=y +CONFIG_CORDIC=m +# CONFIG_PRIME_NUMBERS is not set +CONFIG_RATIONAL=y +CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y +CONFIG_ARCH_HAS_FAST_MULTIPLIER=y +CONFIG_ARCH_USE_SYM_ANNOTATIONS=y +# CONFIG_INDIRECT_PIO is not set +# CONFIG_TRACE_MMIO_ACCESS is not set +CONFIG_CRC16=y +CONFIG_CRC_CCITT=y +CONFIG_CRC_ITU_T=m +CONFIG_CRC_T10DIF=y +CONFIG_CRC_T10DIF_ARCH=y +CONFIG_CRC32=y +CONFIG_CRC32_ARCH=y +CONFIG_CRC64=y +CONFIG_CRC64_ARCH=y +CONFIG_CRC_OPTIMIZATIONS=y +CONFIG_CRYPTO_HASH_INFO=y +CONFIG_CRYPTO_LIB_UTILS=y +CONFIG_CRYPTO_LIB_AES=y +CONFIG_CRYPTO_LIB_AES_ARCH=y +CONFIG_CRYPTO_LIB_AES_CBC_MACS=y +CONFIG_CRYPTO_LIB_ARC4=m +CONFIG_CRYPTO_LIB_GF128MUL=y +CONFIG_CRYPTO_LIB_BLAKE2B=y +CONFIG_CRYPTO_LIB_CHACHA=y +CONFIG_CRYPTO_LIB_CHACHA_ARCH=y +CONFIG_CRYPTO_LIB_CURVE25519=m +CONFIG_CRYPTO_LIB_CURVE25519_GENERIC=y +CONFIG_CRYPTO_LIB_DES=m +CONFIG_CRYPTO_LIB_GF128HASH=y +CONFIG_CRYPTO_LIB_GF128HASH_ARCH=y +CONFIG_CRYPTO_LIB_MD5=y +CONFIG_CRYPTO_LIB_NH=m +CONFIG_CRYPTO_LIB_NH_ARCH=y +CONFIG_CRYPTO_LIB_POLY1305=m +CONFIG_CRYPTO_LIB_POLY1305_ARCH=y +CONFIG_CRYPTO_LIB_POLY1305_GENERIC=y +CONFIG_CRYPTO_LIB_POLY1305_RSIZE=9 +CONFIG_CRYPTO_LIB_CHACHA20POLY1305=m +CONFIG_CRYPTO_LIB_SHA1=y +CONFIG_CRYPTO_LIB_SHA1_ARCH=y +CONFIG_CRYPTO_LIB_SHA256=y +CONFIG_CRYPTO_LIB_SHA256_ARCH=y +CONFIG_CRYPTO_LIB_SHA512=y +CONFIG_CRYPTO_LIB_SHA512_ARCH=y +CONFIG_CRYPTO_LIB_SHA3=y +CONFIG_CRYPTO_LIB_SHA3_ARCH=y +CONFIG_CRYPTO_LIB_SM3=m +CONFIG_CRYPTO_LIB_SM3_ARCH=y +CONFIG_XOR_BLOCKS=m +CONFIG_XOR_BLOCKS_ARCH=y +CONFIG_XXHASH=y +CONFIG_AUDIT_GENERIC=y +CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y +# CONFIG_RANDOM32_SELFTEST is not set +CONFIG_842_COMPRESS=m +CONFIG_842_DECOMPRESS=m +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_LZ4_COMPRESS=m +CONFIG_LZ4HC_COMPRESS=m +CONFIG_LZ4_DECOMPRESS=y +CONFIG_ZSTD_COMMON=y +CONFIG_ZSTD_COMPRESS=y +CONFIG_ZSTD_DECOMPRESS=y +CONFIG_XZ_DEC=y +# CONFIG_XZ_DEC_X86 is not set +# CONFIG_XZ_DEC_POWERPC is not set +CONFIG_XZ_DEC_ARM=y +CONFIG_XZ_DEC_ARMTHUMB=y +CONFIG_XZ_DEC_ARM64=y +# CONFIG_XZ_DEC_SPARC is not set +CONFIG_XZ_DEC_RISCV=y +CONFIG_XZ_DEC_MICROLZMA=y +CONFIG_XZ_DEC_BCJ=y +# CONFIG_XZ_DEC_TEST is not set +CONFIG_DECOMPRESS_GZIP=y +CONFIG_DECOMPRESS_BZIP2=y +CONFIG_DECOMPRESS_LZMA=y +CONFIG_DECOMPRESS_XZ=y +CONFIG_DECOMPRESS_LZO=y +CONFIG_DECOMPRESS_LZ4=y +CONFIG_DECOMPRESS_ZSTD=y +CONFIG_GENERIC_ALLOCATOR=y +CONFIG_REED_SOLOMON=m +CONFIG_REED_SOLOMON_ENC8=y +CONFIG_REED_SOLOMON_DEC8=y +CONFIG_TEXTSEARCH=y +CONFIG_TEXTSEARCH_KMP=m +CONFIG_TEXTSEARCH_BM=m +CONFIG_TEXTSEARCH_FSM=m +CONFIG_INTERVAL_TREE=y +CONFIG_XARRAY_MULTI=y +CONFIG_ASSOCIATIVE_ARRAY=y +CONFIG_CLOSURES=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_IOPORT_MAP=y +CONFIG_HAS_DMA=y +CONFIG_DMA_OPS_HELPERS=y +CONFIG_NEED_SG_DMA_FLAGS=y +CONFIG_NEED_SG_DMA_LENGTH=y +CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_ARCH_DMA_ADDR_T_64BIT=y +CONFIG_DMA_DECLARE_COHERENT=y +CONFIG_ARCH_HAS_SETUP_DMA_OPS=y +CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE=y +CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU=y +CONFIG_ARCH_HAS_DMA_PREP_COHERENT=y +CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y +CONFIG_ARCH_HAS_BATCHED_DMA_SYNC=y +CONFIG_SWIOTLB=y +# CONFIG_SWIOTLB_DYNAMIC is not set +CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC=y +CONFIG_DMA_NEED_SYNC=y +# CONFIG_DMA_RESTRICTED_POOL is not set +CONFIG_DMA_NONCOHERENT_MMAP=y +CONFIG_DMA_COHERENT_POOL=y +CONFIG_DMA_DIRECT_REMAP=y +CONFIG_DMA_CMA=y + +# +# Default contiguous memory area size: +# +CONFIG_CMA_SIZE_MBYTES=64 +CONFIG_CMA_SIZE_SEL_MBYTES=y +# CONFIG_CMA_SIZE_SEL_PERCENTAGE is not set +# CONFIG_CMA_SIZE_SEL_MIN is not set +# CONFIG_CMA_SIZE_SEL_MAX is not set +CONFIG_CMA_ALIGNMENT=8 +# CONFIG_DMA_API_DEBUG is not set +# CONFIG_DMA_MAP_BENCHMARK is not set +CONFIG_SGL_ALLOC=y +CONFIG_CPU_RMAP=y +CONFIG_DQL=y +CONFIG_GLOB=y +CONFIG_NLATTR=y +CONFIG_LRU_CACHE=m +CONFIG_CLZ_TAB=y +CONFIG_IRQ_POLL=y +CONFIG_MPILIB=y +CONFIG_DIMLIB=y +CONFIG_LIBFDT=y +CONFIG_OID_REGISTRY=y +CONFIG_UCS2_STRING=y +CONFIG_HAVE_GENERIC_VDSO=y +CONFIG_GENERIC_GETTIMEOFDAY=y +CONFIG_VDSO_GETRANDOM=y +CONFIG_FONT_SUPPORT=y +CONFIG_FONTS=y +# CONFIG_FONT_8x8 is not set +CONFIG_FONT_8x16=y +# CONFIG_FONT_6x11 is not set +# CONFIG_FONT_7x14 is not set +# CONFIG_FONT_PEARL_8x8 is not set +# CONFIG_FONT_ACORN_8x8 is not set +# CONFIG_FONT_MINI_4x6 is not set +# CONFIG_FONT_6x10 is not set +# CONFIG_FONT_10x18 is not set +# CONFIG_FONT_SUN8x16 is not set +# CONFIG_FONT_SUN12x22 is not set +# CONFIG_FONT_TER10x18 is not set +CONFIG_FONT_TER16x32=y +# CONFIG_FONT_6x8 is not set +CONFIG_SG_POOL=y +CONFIG_ARCH_HAS_CPU_CACHE_INVALIDATE_MEMREGION=y +CONFIG_GENERIC_CPU_CACHE_MAINTENANCE=y +CONFIG_ARCH_STACKWALK=y +CONFIG_SBITMAP=y +# CONFIG_LWQ_TEST is not set +# end of Library routines + +CONFIG_GENERIC_IOREMAP=y +CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED=y +CONFIG_UNION_FIND=y + +# +# Kernel hacking +# + +# +# printk and dmesg options +# +CONFIG_PRINTK_TIME=y +# CONFIG_PRINTK_CALLER is not set +CONFIG_PRINTK_EXECUTION_CTX=y +# CONFIG_STACKTRACE_BUILD_ID is not set +CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7 +CONFIG_CONSOLE_LOGLEVEL_QUIET=4 +CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 +CONFIG_BOOT_PRINTK_DELAY=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DYNAMIC_DEBUG_CORE=y +CONFIG_SYMBOLIC_ERRNAME=y +CONFIG_DEBUG_BUGVERBOSE=y +# CONFIG_DEBUG_BUGVERBOSE_DETAILED is not set +# end of printk and dmesg options + +CONFIG_DEBUG_KERNEL=y +CONFIG_DEBUG_MISC=y + +# +# Compile-time checks and compiler options +# +CONFIG_DEBUG_INFO=y +CONFIG_AS_HAS_NON_CONST_ULEB128=y +# CONFIG_DEBUG_INFO_NONE is not set +# CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT is not set +# CONFIG_DEBUG_INFO_DWARF4 is not set +CONFIG_DEBUG_INFO_DWARF5=y +# CONFIG_DEBUG_INFO_REDUCED is not set +CONFIG_DEBUG_INFO_COMPRESSED_NONE=y +# CONFIG_DEBUG_INFO_COMPRESSED_ZLIB is not set +# CONFIG_DEBUG_INFO_COMPRESSED_ZSTD is not set +# CONFIG_DEBUG_INFO_SPLIT is not set +CONFIG_DEBUG_INFO_BTF=y +CONFIG_PAHOLE_HAS_LANG_EXCLUDE=y +CONFIG_DEBUG_INFO_BTF_MODULES=y +# CONFIG_MODULE_ALLOW_BTF_MISMATCH is not set +# CONFIG_GDB_SCRIPTS is not set +CONFIG_FRAME_WARN=2048 +CONFIG_STRIP_ASM_SYMS=y +# CONFIG_READABLE_ASM is not set +# CONFIG_HEADERS_INSTALL is not set +# CONFIG_DEBUG_SECTION_MISMATCH is not set +CONFIG_SECTION_MISMATCH_WARN_ONLY=y +# CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_64B is not set +CONFIG_ARCH_WANT_FRAME_POINTERS=y +CONFIG_FRAME_POINTER=y +# CONFIG_VMLINUX_MAP is not set +# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set +# end of Compile-time checks and compiler options + +# +# Generic Kernel Debugging Instruments +# +CONFIG_MAGIC_SYSRQ=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x0 +CONFIG_MAGIC_SYSRQ_SERIAL=y +CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE="" +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_FS_ALLOW_ALL=y +# CONFIG_DEBUG_FS_ALLOW_NONE is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_KGDB is not set +CONFIG_ARCH_HAS_UBSAN=y +# CONFIG_UBSAN is not set +CONFIG_HAVE_ARCH_KCSAN=y +CONFIG_HAVE_KCSAN_COMPILER=y +# CONFIG_KCSAN is not set +# end of Generic Kernel Debugging Instruments + +# +# Networking Debugging +# +# CONFIG_NET_DEV_REFCNT_TRACKER is not set +# CONFIG_NET_NS_REFCNT_TRACKER is not set +# CONFIG_DEBUG_NET is not set +# CONFIG_DEBUG_NET_SMALL_RTNL is not set +# end of Networking Debugging + +# +# Memory Debugging +# +# CONFIG_PAGE_EXTENSION is not set +# CONFIG_DEBUG_PAGEALLOC is not set +# CONFIG_SLUB_DEBUG is not set +# CONFIG_PAGE_OWNER is not set +# CONFIG_PAGE_TABLE_CHECK is not set +# CONFIG_PAGE_POISONING is not set +# CONFIG_DEBUG_PAGE_REF is not set +# CONFIG_DEBUG_RODATA_TEST is not set +CONFIG_ARCH_HAS_DEBUG_WX=y +CONFIG_DEBUG_WX=y +CONFIG_ARCH_HAS_PTDUMP=y +CONFIG_PTDUMP=y +# CONFIG_PTDUMP_DEBUGFS is not set +CONFIG_HAVE_DEBUG_KMEMLEAK=y +# CONFIG_DEBUG_KMEMLEAK is not set +# CONFIG_PER_VMA_LOCK_STATS is not set +# CONFIG_DEBUG_OBJECTS is not set +# CONFIG_SHRINKER_DEBUG is not set +# CONFIG_DEBUG_STACK_USAGE is not set +# CONFIG_SCHED_STACK_END_CHECK is not set +CONFIG_ARCH_HAS_DEBUG_VM_PGTABLE=y +# CONFIG_DEBUG_VFS is not set +# CONFIG_DEBUG_VM is not set +# CONFIG_DEBUG_VM_PGTABLE is not set +CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y +# CONFIG_DEBUG_VIRTUAL is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +# CONFIG_DEBUG_PER_CPU_MAPS is not set +# CONFIG_MEM_ALLOC_PROFILING is not set +CONFIG_HAVE_ARCH_KASAN=y +CONFIG_HAVE_ARCH_KASAN_SW_TAGS=y +CONFIG_HAVE_ARCH_KASAN_HW_TAGS=y +CONFIG_HAVE_ARCH_KASAN_VMALLOC=y +CONFIG_CC_HAS_KASAN_GENERIC=y +CONFIG_CC_HAS_KASAN_SW_TAGS=y +CONFIG_CC_HAS_WORKING_NOSANITIZE_ADDRESS=y +# CONFIG_KASAN is not set +CONFIG_HAVE_ARCH_KFENCE=y +# CONFIG_KFENCE is not set +# end of Memory Debugging + +# CONFIG_DEBUG_SHIRQ is not set + +# +# Debug Oops, Lockups and Hangs +# +# CONFIG_PANIC_ON_OOPS is not set +CONFIG_PANIC_TIMEOUT=0 +CONFIG_LOCKUP_DETECTOR=y +CONFIG_SOFTLOCKUP_DETECTOR=y +CONFIG_SOFTLOCKUP_DETECTOR_INTR_STORM=y +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=0 +CONFIG_HAVE_HARDLOCKUP_DETECTOR_BUDDY=y +# CONFIG_HARDLOCKUP_DETECTOR is not set +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 +CONFIG_BOOTPARAM_HUNG_TASK_PANIC=0 +CONFIG_DETECT_HUNG_TASK_BLOCKER=y +# CONFIG_WQ_WATCHDOG is not set +# CONFIG_WQ_CPU_INTENSIVE_REPORT is not set +# CONFIG_TEST_LOCKUP is not set +# end of Debug Oops, Lockups and Hangs + +# +# Scheduler Debugging +# +CONFIG_SCHED_INFO=y +CONFIG_SCHEDSTATS=y +# end of Scheduler Debugging + +CONFIG_DEBUG_PREEMPT=y +# CONFIG_DEBUG_ATOMIC is not set + +# +# Lock Debugging (spinlocks, mutexes, etc...) +# +CONFIG_LOCK_DEBUGGING_SUPPORT=y +# CONFIG_PROVE_LOCKING is not set +# CONFIG_LOCK_STAT is not set +# CONFIG_DEBUG_RT_MUTEXES is not set +# CONFIG_DEBUG_SPINLOCK is not set +# CONFIG_DEBUG_MUTEXES is not set +# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set +# CONFIG_DEBUG_RWSEMS is not set +# CONFIG_DEBUG_LOCK_ALLOC is not set +# CONFIG_DEBUG_ATOMIC_SLEEP is not set +# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set +# CONFIG_LOCK_TORTURE_TEST is not set +# CONFIG_WW_MUTEX_SELFTEST is not set +# CONFIG_SCF_TORTURE_TEST is not set +# CONFIG_CSD_LOCK_WAIT_DEBUG is not set +# end of Lock Debugging (spinlocks, mutexes, etc...) + +# CONFIG_DEBUG_IRQFLAGS is not set +CONFIG_STACKTRACE=y +# CONFIG_DEBUG_KOBJECT is not set + +# +# Debug kernel data structures +# +# CONFIG_DEBUG_LIST is not set +# CONFIG_DEBUG_PLIST is not set +# CONFIG_DEBUG_SG is not set +# CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_DEBUG_CLOSURES is not set +# CONFIG_DEBUG_MAPLE_TREE is not set +# end of Debug kernel data structures + +# +# RCU Debugging +# +CONFIG_TORTURE_TEST=m +# CONFIG_RCU_SCALE_TEST is not set +CONFIG_RCU_TORTURE_TEST=m +# CONFIG_RCU_TORTURE_TEST_CHK_RDR_STATE is not set +# CONFIG_RCU_TORTURE_TEST_LOG_CPU is not set +# CONFIG_RCU_TORTURE_TEST_LOG_GP is not set +# CONFIG_RCU_REF_SCALE_TEST is not set +CONFIG_RCU_CPU_STALL_TIMEOUT=60 +CONFIG_RCU_EXP_CPU_STALL_TIMEOUT=0 +# CONFIG_RCU_CPU_STALL_CPUTIME is not set +# CONFIG_RCU_TRACE is not set +# CONFIG_RCU_EQS_DEBUG is not set +# end of RCU Debugging + +# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set +# CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set +# CONFIG_LATENCYTOP is not set +# CONFIG_DEBUG_CGROUP_REF is not set +CONFIG_USER_STACKTRACE_SUPPORT=y +CONFIG_NOP_TRACER=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_FUNCTION_GRAPH_FREGS=y +CONFIG_HAVE_FTRACE_GRAPH_FUNC=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_EXTRA_IPI_TRACEPOINTS=y +CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS=y +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_C_RECORDMCOUNT=y +CONFIG_HAVE_BUILDTIME_MCOUNT_SORT=y +CONFIG_TRACE_CLOCK=y +CONFIG_RING_BUFFER=y +CONFIG_EVENT_TRACING=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_TRACING=y +CONFIG_TRACING_SUPPORT=y +CONFIG_FTRACE=y +CONFIG_TRACEFS_AUTOMOUNT_DEPRECATED=y +# CONFIG_BOOTTIME_TRACING is not set +# CONFIG_FUNCTION_TRACER is not set +CONFIG_FUNCTION_TRACE_ARGS=y +# CONFIG_STACK_TRACER is not set +# CONFIG_IRQSOFF_TRACER is not set +# CONFIG_PREEMPT_TRACER is not set +# CONFIG_SCHED_TRACER is not set +# CONFIG_HWLAT_TRACER is not set +# CONFIG_OSNOISE_TRACER is not set +# CONFIG_TIMERLAT_TRACER is not set +# CONFIG_ENABLE_DEFAULT_TRACERS is not set +# CONFIG_FTRACE_SYSCALLS is not set +# CONFIG_TRACER_SNAPSHOT is not set +CONFIG_BRANCH_PROFILE_NONE=y +# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set +# CONFIG_PROFILE_ALL_BRANCHES is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +CONFIG_PROBE_EVENTS_BTF_ARGS=y +CONFIG_KPROBE_EVENTS=y +CONFIG_UPROBE_EVENTS=y +CONFIG_EPROBE_EVENTS=y +CONFIG_BPF_EVENTS=y +CONFIG_DYNAMIC_EVENTS=y +CONFIG_PROBE_EVENTS=y +# CONFIG_BPF_KPROBE_OVERRIDE is not set +# CONFIG_SYNTH_EVENTS is not set +# CONFIG_USER_EVENTS is not set +# CONFIG_HIST_TRIGGERS is not set +# CONFIG_TRACE_EVENT_INJECT is not set +# CONFIG_TRACEPOINT_BENCHMARK is not set +# CONFIG_RING_BUFFER_BENCHMARK is not set +# CONFIG_TRACE_EVAL_MAP_FILE is not set +# CONFIG_RING_BUFFER_STARTUP_TEST is not set +# CONFIG_RING_BUFFER_VALIDATE_TIME_DELTAS is not set +# CONFIG_PREEMPTIRQ_DELAY_TEST is not set +# CONFIG_KPROBE_EVENT_GEN_TEST is not set +# CONFIG_RV is not set +# CONFIG_TRACE_REMOTE_TEST is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_SAMPLE_FTRACE_DIRECT=y +CONFIG_HAVE_SAMPLE_FTRACE_DIRECT_MULTI=y +CONFIG_STRICT_DEVMEM=y +# CONFIG_IO_STRICT_DEVMEM is not set + +# +# arm64 Debugging +# +# CONFIG_PID_IN_CONTEXTIDR is not set +# CONFIG_DEBUG_EFI is not set +# CONFIG_ARM64_RELOC_TEST is not set +# CONFIG_CORESIGHT is not set +# end of arm64 Debugging + +# +# Kernel Testing and Coverage +# +# CONFIG_KUNIT is not set +# CONFIG_NOTIFIER_ERROR_INJECTION is not set +CONFIG_FUNCTION_ERROR_INJECTION=y +# CONFIG_FAULT_INJECTION is not set +CONFIG_ARCH_HAS_KCOV=y +# CONFIG_KCOV is not set +# CONFIG_RUNTIME_TESTING_MENU is not set +CONFIG_ARCH_USE_MEMTEST=y +# CONFIG_MEMTEST is not set +# end of Kernel Testing and Coverage + +# +# Rust hacking +# +# CONFIG_RUST_DEBUG_ASSERTIONS is not set +CONFIG_RUST_OVERFLOW_CHECKS=y +# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set +# end of Rust hacking +# end of Kernel hacking + +CONFIG_IO_URING_ZCRX=y +CONFIG_IO_URING_BPF=y +CONFIG_IO_URING_BPF_OPS=y diff --git a/pkgbuilds/linux-aurora/rust-toolchain.toml b/pkgbuilds/linux-aurora/rust-toolchain.toml new file mode 100644 index 0000000..6a1cc41 --- /dev/null +++ b/pkgbuilds/linux-aurora/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.93.1" +components = ["rustc", "cargo", "rust-src"] +targets = ["aarch64-unknown-linux-gnu"] diff --git a/pkgbuilds/linux-firmware-cirrus/.omarchy/package.json b/pkgbuilds/linux-firmware-cirrus/.omarchy/package.json new file mode 100644 index 0000000..ba6af6c --- /dev/null +++ b/pkgbuilds/linux-firmware-cirrus/.omarchy/package.json @@ -0,0 +1,5 @@ +{ + "source": "local", + "release_ring": "fast", + "sync": false +} diff --git a/pkgbuilds/linux-firmware-cirrus/PKGBUILD b/pkgbuilds/linux-firmware-cirrus/PKGBUILD new file mode 100644 index 0000000..0c46f9b --- /dev/null +++ b/pkgbuilds/linux-firmware-cirrus/PKGBUILD @@ -0,0 +1,63 @@ +# Maintainer: Spencer Bull +# +# Self-retiring shim: ships Arch's linux-firmware-cirrus 20260910-2 payload to +# the stable channel while stable's pinned Arch snapshot is still on +# linux-firmware 20260810-2 (which lacks the Dell XPS 13 DX13260 / 1028:0e54 +# CS35L56 amplifier firmware aliases, leaving that machine's speakers silent). +# +# Nothing is rebuilt. The signed Arch package is verified against the Arch +# packager key and its payload reinstalled as-is, minus the files that Arch +# moved out of linux-firmware-other in 20260910 (the cs42l45 SDCA tree): on +# the stable snapshot those are still owned by linux-firmware-other 20260810-2 +# and would conflict, so they are left to that package. +# +# Versioning is deliberate: 20260810-3 orders above the snapshot's 20260810-2 +# and BELOW Arch's real 20260910-2, so as soon as the stable snapshot advances +# pacman replaces this shim with the genuine package in the same transaction +# that upgrades linux-firmware-other, and nothing is lost. Delete this recipe +# once stable's snapshot is at linux-firmware >= 20260910. + +pkgname=linux-firmware-cirrus +pkgver=20260810 +pkgrel=3 +_fwver=20260910 +_fwrel=2 +_basever=20260810 +_baserel=2 +pkgdesc="Firmware files for Linux - Firmware for Cirrus Logic audio devices (Arch - payload, stable-snapshot shim)" +arch=('any') +url="https://gitlab.com/kernel-firmware/linux-firmware" +license=('LicenseRef-WHENCE' 'LicenseRef-cirrus') +depends=('linux-firmware-whence') +options=('!strip' '!debug') +_cirrus="linux-firmware-cirrus-${_fwver}-${_fwrel}-any.pkg.tar.zst" +_other="linux-firmware-other-${_basever}-${_baserel}-any.pkg.tar.zst" +source=( + "https://archive.archlinux.org/packages/l/linux-firmware-cirrus/${_cirrus}" + "https://archive.archlinux.org/packages/l/linux-firmware-cirrus/${_cirrus}.sig" + "https://archive.archlinux.org/packages/l/linux-firmware-other/${_other}" + "https://archive.archlinux.org/packages/l/linux-firmware-other/${_other}.sig" +) +noextract=("${_cirrus}" "${_other}") +sha256sums=('70100c551b079bd8abec3d9c96a16defd04766b2a4afc6d7be9128d37e9840f7' + 'SKIP' + 'b0f016ee0d0532b977211b0cbb630bca75184f68a9ace82675eb3cf9acce4f6c' + 'SKIP') +# Jan Alexander Steffens (heftig) , Arch Linux packager +validpgpkeys=('83BC8889351B5DEBBB68416EB8AC08600F108CDF') + +package() { + # Payload only; makepkg regenerates .PKGINFO/.MTREE/.BUILDINFO. + bsdtar -xf "${srcdir}/${_cirrus}" -C "${pkgdir}" \ + --exclude='.PKGINFO' --exclude='.MTREE' --exclude='.BUILDINFO' \ + --exclude='.INSTALL' --exclude='.CHANGELOG' + + # Drop every file the snapshot's linux-firmware-other still owns. + local f + while IFS= read -r f; do + [[ -e "${pkgdir}/${f}" || -L "${pkgdir}/${f}" ]] && rm -f "${pkgdir}/${f}" + done < <(bsdtar -tf "${srcdir}/${_other}" | grep -v '^\.' | grep -v '/$') + + # Remove directories emptied by the step above. + find "${pkgdir}/usr/lib/firmware" -depth -type d -empty -delete +} diff --git a/pkgbuilds/linux-firmware-cirrus/keys/pgp/83BC8889351B5DEBBB68416EB8AC08600F108CDF.asc b/pkgbuilds/linux-firmware-cirrus/keys/pgp/83BC8889351B5DEBBB68416EB8AC08600F108CDF.asc new file mode 100644 index 0000000..e6ae5d8 --- /dev/null +++ b/pkgbuilds/linux-firmware-cirrus/keys/pgp/83BC8889351B5DEBBB68416EB8AC08600F108CDF.asc @@ -0,0 +1,60 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZXeS9hYJKwYBBAHaRw8BAQdAT04Na8ee0UltkhyNi2RGHYdjZDgg+X/K8Jix +dSSQ+Ni0NkphbiBBbGV4YW5kZXIgU3RlZmZlbnMgKGhlZnRpZykgPGhlZnRpZ0Bh +cmNobGludXgub3JnPoiQBBMWCgA4AhsDAheAFiEEg7yIiTUbXeu7aEFuuKwIYA8Q +jN8FAmWq/kcFCwkIBwMFFQoJCAsFFgIDAQACHgUACgkQuKwIYA8QjN+2qAEAh/RL +Zq7Hmqv/z09yq0m6IEb0kbXaW50POi/V+2VcJ9wBAN5Ik/fFgnGMlvZF7Rugu37o +2fk7jnUVsWJca9QmytgGiHUEEBYKAB0WIQSZtmGEcqOzuBQYW67X09gjuIvbmwUC +aYWy0QAKCRDX09gjuIvbm7vJAP9xqmmI8jNGEUQNidh9yZCmVOr3vT5/sUcHGo0J +08o/TgD/Wix2/4DHfnk6fu+onERrK5uF9BAacERnCsG5lMWQ2wSJAjMEEAEKAB0W +IQSR/+BwDoBhnOtzI1yojiPjd1FOAAUCZa0cywAKCRCojiPjd1FOAIKbEACipPSS +sA2G7ntHRGEHSWKgqEKseGGr7kflVdsmJn1tjKI8/VGKa77NkCRgqIJRwmE9m9+F +Cx+a3ILmMOxsjj5RCVFce9NJuscVGHaphZcp7Z0MVoR9hhtnsyjWk/CYOAv+JLKq +NTYhdmat+ixY4fwPzjdV6sxCDL/s6Fci0zPLUam7QVr9LIS4U3UH7smkKj6jM+G/ +sEn0QM02EKy2iJLlbPNN3pppYWqZ5p9xXeB6EJ89JvZMI3k92xy456JjkCgLN860 +NPmdmnLLtlrGWJRgNK6+iaFjLt/BL9gU8aGe+K3ONXES2k7iZnh+oLAEV8Z07dgv +Gd0o+OFEPrC8kETbQK45r1yAjZVO+tp4dSxvV/BD+7KhUXAZnOKWLaOMomfsG17E +KKa894cBLJG7LWN/d/Uk9fjfjd4ZYbSpIALJiEoKrm1ytj2KgnxKveTtY69i63/N +0BpNVrkYIJffWR6zSosC8geAWfqm7pR6JARUAZrw0YtJbluDrRgOO0XFn8hDqRUm +FPWgXuvqNdR2JyD4aB87LKGhzVFzQvbHa+S4sG7+w5nWQB0Eca+hiVpJDpMYjoU+ +f+L3yUb6POwvUJe7VT/2PYOwidnV3guTevnn9a4erKoW/6wKAr13cW+KTKTR0bdC +S0UTOWbJnbi2Hfhyr7NZgs4T4OcpH3kj+R4WPIkCMwQQAQoAHRYhBNiv3aB6W27f +p9jM2tbQVfknhD8cBQJlg4C5AAoJENbQVfknhD8cpPQP/19uAVMaG1lQNaAK9XWT +Z1/lAr6sxYT6B2+MqnRtbkr4Gh3ts7Ey5BI39HxNWRIUGUt7pq4FjCQxWeJC9mfp +/hqj9rl1s32lbBzevbkjESro+cHPfrv0vsVlwJA2W3rURQlQzbEq3fvDzc/wz4sN +vgQDiUlxH5JYXZ5OexsDATLyfHNpJh+4NxqB24axjDe/1ZK8hoYl+eX46dP9JKWB +F8GA3Ebwst3de/81oNngBjDgNPju6cvgykoT2jCBn4asp1xKekZh5ZbRG40jAeoQ +QAvEgTxIbwNf4HBVhvGjQ6G5lCNO6gQonq1X4UbHgH8tRGSSgnz2yDAdcxtaj1eC +e5BMhjuoHuxS4DCtR97Dtn7YjrYoVus5eMkhEZGuWKK4hkggsLbyUx5llxZxLXPD +mBAYLkqgZsHgiqLeHAdrEmAFaRfskVg0MzfCcEE9StWnWf0ixk3thuwQAaPI4GCB +nNLnmqCcOyCN1Qa3iXjdOzp5bU0qwFi/qFQagq0nu8sJim7q0g0Bk7J+iGRht3no +L5iT9d+8CQaN1it+L+elSYfrr/LamghRvh9epOtGUVH3GQYS4Bgo2rgktGXKtcX+ +P/jR/aM034g8VPlbV+Kas/c6mIk9JzKOvJJ4+Lpvr0ZzRG32ez9dAlDogE3xASi6 +sMWbY+t4unGs+nY3SqIOyHLTiHUEEBYKAB0WIQRp5kceOuBlKXUpgy5roPWiA39P +QQUCZX7ZYAAKCRBroPWiA39PQadvAQD7F/N3xuyWogrJV7TMZk2PFteviEW2Dv9d +dSUGasK3dQEAxf3HxRDvrv3yJLhgNKa+ksr4bBBmruvilpWS+X4InwKIdQQQFgoA +HRYhBDVy+iobBn8ixYrxVfi4IbQqb9zXBQJlfKbrAAoJEPi4IbQqb9zXe7MBAIzF +QqdV8CJXYIcZJtUUIQ7a/AN2enHBpoa/qXEre5bAAP4uNEUMKiDZRpHAh/KmqarM +vF+c1BOpEJbQsPqv0L5hDIh1BBAWCgAdFiEEKsCkLvsLXLx6BALtTclbbXvpiS4F +AmV8nsgACgkQTclbbXvpiS6bpwD/W0sMOH4lmR4t9Sc8hJB+uBLGYxzoNIgaNa5x +vbdm5hwBAIPYr1SVl0+yghsxg5k75jStRL2S5MZW2iSV3ynNLTkFiHkEEBYKACEW +IQSi/zo2qqVmVBCQZKsZgC+LDXD8MAUCZXe6yAMFAngACgkQGYAviw1w/DCmCAD8 +Cfvn8O+N/AJTOKY8lZzk+OSX3tSTQTOUiLHKRl+RX6IA/1Bku44c1YJVZ+RhWkGQ +n0C8ZN/DYzHh9JInl3blLcIMtDhKYW4gQWxleGFuZGVyIFN0ZWZmZW5zIChoZWZ0 +aWcpIDxqYW4uc3RlZmZlbnNAZ21haWwuY29tPoiTBBMWCgA7AhsDAheAAhkBFiEE +g7yIiTUbXeu7aEFuuKwIYA8QjN8FAmWq/kIFCwkIBwMFFQoJCAsFFgIDAQACHgUA +CgkQuKwIYA8QjN+ySAD+PI99JJsFWz2CaS3enxjUMCWJZJvSV9G1FqmeTKtH95oA +/ismVRjBbwbCrDDEsZVIK3NeRyRyhiWIVFXWix/KnH4CiHkEEBYKACEWIQSi/zo2 +qqVmVBCQZKsZgC+LDXD8MAUCZXe6xQMFAngACgkQGYAviw1w/DDnzAD6AwROKYI8 +7DZ6a1onZeR5wOV50bt2LCB4XxNiupHcpLMA/i4dmwa4Bkzyh/h+v0kN2PSssueX +7kFPNcnyhe3KbUUDuDMEZXeUSxYJKwYBBAHaRw8BAQdAkvIbYwde3OFqoAy6QOO9 +BPwFNCll8tgQ6iAmQMkOjtWIeAQYFgoAIBYhBIO8iIk1G13ru2hBbrisCGAPEIzf +BQJld5RLAhsgAAoJELisCGAPEIzfhU8A/3NZzIEk3dmCAL0XLtylcFp/HExnN+5Q +RGmT0+SzzuGaAP9ozoMlSjtcGLAZMglLk8/mYzKveR89RJlB0cZtUU6UArg4BGV3 +kvYSCisGAQQBl1UBBQEBB0Dh/7CubQh/MabODq3IcoqeGUzEGUPU8GXCVrDmPHih +WQMBCAeIeAQYFgoAIBYhBIO8iIk1G13ru2hBbrisCGAPEIzfBQJld5L2AhsMAAoJ +ELisCGAPEIzfao0A/AiJPB4igiyHjPgR8OpKh4Nz+pwmFrD/j5l2YC0Xi2dOAP4j +LDEa1VCNNhq7vWA8SqUjareBzHpwlG2ObUwYxORjBQ== +=OXp6 +-----END PGP PUBLIC KEY BLOCK----- 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/0011-kbuild-optimize-for-performance-o3.patch b/pkgbuilds/linux-omarchy-bore/0011-kbuild-optimize-for-performance-o3.patch new file mode 100644 index 0000000..9506ee1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0011-kbuild-optimize-for-performance-o3.patch @@ -0,0 +1,31 @@ +diff --git a/Makefile b/Makefile +--- a/Makefile ++++ b/Makefile +@@ -935,6 +935,9 @@ KBUILD_RUSTFLAGS += -Copt-level=2 + else ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE + KBUILD_CFLAGS += -Os + KBUILD_RUSTFLAGS += -Copt-level=s ++else ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3 ++KBUILD_CFLAGS += -O3 ++KBUILD_RUSTFLAGS += -Copt-level=3 + endif + + # Always set `debug-assertions` and `overflow-checks` because their default +diff --git a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -1622,6 +1622,14 @@ config CC_OPTIMIZE_FOR_SIZE + Choosing this option will pass "-Os" to your compiler resulting + in a smaller kernel. + ++config CC_OPTIMIZE_FOR_PERFORMANCE_O3 ++ bool "Optimize harder for performance (-O3)" ++ help ++ Build with the "-O3" compiler flag: more inlining, loop ++ unrolling and vectorization than -O2, at the cost of a larger ++ kernel image and larger modules. Rust code is built at ++ opt-level 3. ++ + endchoice + + config HAVE_LD_DEAD_CODE_DATA_ELIMINATION diff --git a/pkgbuilds/linux-omarchy-bore/0011-kbuild-optimize-for-performance-o3.patch.sig b/pkgbuilds/linux-omarchy-bore/0011-kbuild-optimize-for-performance-o3.patch.sig new file mode 100644 index 0000000..48ab131 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0011-kbuild-optimize-for-performance-o3.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/0220-x86-amd-zen5-tlb-sizes.patch b/pkgbuilds/linux-omarchy-bore/0220-x86-amd-zen5-tlb-sizes.patch new file mode 100644 index 0000000..1c5c5b3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0220-x86-amd-zen5-tlb-sizes.patch @@ -0,0 +1,95 @@ +diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h +--- a/arch/x86/include/asm/cpufeatures.h ++++ b/arch/x86/include/asm/cpufeatures.h +@@ -471,6 +471,8 @@ + #define X86_FEATURE_AUTOIBRS (20*32+ 8) /* Automatic IBRS */ + #define X86_FEATURE_NO_SMM_CTL_MSR (20*32+ 9) /* SMM_CTL MSR is not present */ + ++#define X86_FEATURE_L2_TLB_SIZE_X32 (20*32+14) /* L2 TLB sizes are encoded as multiples of 32 */ ++ + #define X86_FEATURE_GP_ON_USER_CPUID (20*32+17) /* User CPUID faulting */ + + #define X86_FEATURE_PREFETCHI (20*32+20) /* Prefetch Data/Instruction to Cache Level */ +diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c +--- a/arch/x86/kernel/cpu/amd.c ++++ b/arch/x86/kernel/cpu/amd.c +@@ -1190,7 +1190,7 @@ static unsigned int amd_size_cache(struct cpuinfo_x86 *c, unsigned int size) + + static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + { +- u32 ebx, eax, ecx, edx; ++ u32 ebx, eax, ecx, edx, shift, tmp; + u16 mask = 0xfff; + + if (c->x86 < 0xf) +@@ -1199,10 +1199,12 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + if (c->extended_cpuid_level < 0x80000006) + return; + ++ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; ++ + cpuid(0x80000006, &eax, &ebx, &ecx, &edx); + +- tlb_lld_4k = (ebx >> 16) & mask; +- tlb_lli_4k = ebx & mask; ++ tlb_lld_4k = ((ebx >> 16) & mask) << shift; ++ tlb_lli_4k = (ebx & mask) << shift; + + /* + * K8 doesn't have 2M/4M entries in the L2 TLB so read out the L1 TLB +@@ -1214,16 +1216,18 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + } + + /* Handle DTLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ +- if (!((eax >> 16) & mask)) ++ tmp = ((eax >> 16) & mask) << shift; ++ if (!tmp) + tlb_lld_2m = (cpuid_eax(0x80000005) >> 16) & 0xff; + else +- tlb_lld_2m = (eax >> 16) & mask; ++ tlb_lld_2m = tmp; + + /* a 4M entry uses two 2M entries */ + tlb_lld_4m = tlb_lld_2m >> 1; + + /* Handle ITLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ +- if (!(eax & mask)) { ++ tmp = (eax & mask) << shift; ++ if (!tmp) { + /* Erratum 658 */ + if (c->x86 == 0x15 && c->x86_model <= 0x1f) { + tlb_lli_2m = 1024; +@@ -1231,8 +1235,9 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + cpuid(0x80000005, &eax, &ebx, &ecx, &edx); + tlb_lli_2m = eax & 0xff; + } +- } else +- tlb_lli_2m = eax & mask; ++ } else { ++ tlb_lli_2m = tmp; ++ } + + tlb_lli_4m = tlb_lli_2m >> 1; + +diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c +--- a/arch/x86/kernel/cpu/common.c ++++ b/arch/x86/kernel/cpu/common.c +@@ -857,7 +857,7 @@ static void get_model_name(struct cpuinfo_x86 *c) + + void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) + { +- unsigned int n, dummy, ebx, ecx, edx, l2size; ++ unsigned int n, dummy, ebx, ecx, edx, l2size, shift __maybe_unused; + + n = c->extended_cpuid_level; + +@@ -877,7 +877,9 @@ void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) + l2size = ecx >> 16; + + #ifdef CONFIG_X86_64 ++ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; + c->x86_tlbsize += ((ebx >> 16) & 0xfff) + (ebx & 0xfff); ++ c->x86_tlbsize <<= shift; + #else + /* do processor-specific cache resizing */ + if (this_cpu->legacy_cache_size) diff --git a/pkgbuilds/linux-omarchy-bore/0220-x86-amd-zen5-tlb-sizes.patch.sig b/pkgbuilds/linux-omarchy-bore/0220-x86-amd-zen5-tlb-sizes.patch.sig new file mode 100644 index 0000000..33230cf Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0220-x86-amd-zen5-tlb-sizes.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/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch b/pkgbuilds/linux-omarchy-bore/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch new file mode 100644 index 0000000..6977a84 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch @@ -0,0 +1,12 @@ +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 +@@ -1343,7 +1343,7 @@ ttm_bo_swapout_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) + + if (ttm_tt_is_populated(tt)) { + ret = ttm_tt_swapout(bdev, tt, swapout_walk->gfp_flags); +- if (!ret) { ++ if (ret > 0) { + spin_lock(&bdev->lru_lock); + ttm_resource_del_bulk_move_unevictable(bo->resource, bo); + ttm_resource_move_to_lru_tail(bo->resource); diff --git a/pkgbuilds/linux-omarchy-bore/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch.sig b/pkgbuilds/linux-omarchy-bore/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch.sig new file mode 100644 index 0000000..bd99af4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.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..d9c5b41 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/PKGBUILD @@ -0,0 +1,747 @@ +# Maintainer: The Omarchy Authors +# Adopted from the Arch Linux linux package. + +pkgbase=linux-omarchy-bore +pkgver=7.2.5 +pkgrel=6 +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} + 0011-kbuild-optimize-for-performance-o3.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} + 0220-x86-amd-zen5-tlb-sizes.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} + 0361-drm-ttm-swapped-out-resource-leaves-bulk-move.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' + 'cc2a66a097b5567e80c59b7d6c492fef5e667fc71a0390908712c69512c4136dbaf3d150b62d04eff960c796f7a94e6d0915d3b92cb336318b49f5f516a3bba6' + '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' + 'e59c4da548b2ea4fd6144886e3e2e765b3e73972121e9e73dbc05c066834b2b35e6ef4c17617e77efc90260566bf7c0910d2138d8d5c1c3b6094eeac9de64cfe' + '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' + '15855e9c28cb0abaab7ab606904dfc84d426ccab44edae59f0e1f092202b80525929f8d394d04ca1513d1d5dfc2cfc108a66a9c1cb42b9966c6417562ef20689' + '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=('f69a306213a769710e3429817239746406e115af29f34f37514119eb49f973c86953379b68d3df89700bef7f86e3ec43eb91d5f5821f2a8eae7f28dba589d265') + +# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc +sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' + 'SKIP' + '95f3e9209629044028373af987dc0e270a5a15acb8c8e49c5f05057220c75fe2' + 'SKIP' + '6cf74fc3f28a751354187b6cf610c3e43cf9088b5f730b68dbb575828c3a7478' + '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' + '718b40e15350049e03c0ed281b9257fca5432b8dec2e0e145e8b26507ee8892d' + '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' + '503eba8d7a80b978fccdaac798f57cbb6cd2b2b04fd04c46399daa004b6646dc' + '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/0011-kbuild-optimize-for-performance-o3.patch b/pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.patch new file mode 100644 index 0000000..9506ee1 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.patch @@ -0,0 +1,31 @@ +diff --git a/Makefile b/Makefile +--- a/Makefile ++++ b/Makefile +@@ -935,6 +935,9 @@ KBUILD_RUSTFLAGS += -Copt-level=2 + else ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE + KBUILD_CFLAGS += -Os + KBUILD_RUSTFLAGS += -Copt-level=s ++else ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3 ++KBUILD_CFLAGS += -O3 ++KBUILD_RUSTFLAGS += -Copt-level=3 + endif + + # Always set `debug-assertions` and `overflow-checks` because their default +diff --git a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -1622,6 +1622,14 @@ config CC_OPTIMIZE_FOR_SIZE + Choosing this option will pass "-Os" to your compiler resulting + in a smaller kernel. + ++config CC_OPTIMIZE_FOR_PERFORMANCE_O3 ++ bool "Optimize harder for performance (-O3)" ++ help ++ Build with the "-O3" compiler flag: more inlining, loop ++ unrolling and vectorization than -O2, at the cost of a larger ++ kernel image and larger modules. Rust code is built at ++ opt-level 3. ++ + endchoice + + config HAVE_LD_DEAD_CODE_DATA_ELIMINATION diff --git a/pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.patch.sig b/pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.patch.sig new file mode 100644 index 0000000..90d5219 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.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/0220-x86-amd-zen5-tlb-sizes.patch b/pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.patch new file mode 100644 index 0000000..1c5c5b3 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.patch @@ -0,0 +1,95 @@ +diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h +--- a/arch/x86/include/asm/cpufeatures.h ++++ b/arch/x86/include/asm/cpufeatures.h +@@ -471,6 +471,8 @@ + #define X86_FEATURE_AUTOIBRS (20*32+ 8) /* Automatic IBRS */ + #define X86_FEATURE_NO_SMM_CTL_MSR (20*32+ 9) /* SMM_CTL MSR is not present */ + ++#define X86_FEATURE_L2_TLB_SIZE_X32 (20*32+14) /* L2 TLB sizes are encoded as multiples of 32 */ ++ + #define X86_FEATURE_GP_ON_USER_CPUID (20*32+17) /* User CPUID faulting */ + + #define X86_FEATURE_PREFETCHI (20*32+20) /* Prefetch Data/Instruction to Cache Level */ +diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c +--- a/arch/x86/kernel/cpu/amd.c ++++ b/arch/x86/kernel/cpu/amd.c +@@ -1190,7 +1190,7 @@ static unsigned int amd_size_cache(struct cpuinfo_x86 *c, unsigned int size) + + static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + { +- u32 ebx, eax, ecx, edx; ++ u32 ebx, eax, ecx, edx, shift, tmp; + u16 mask = 0xfff; + + if (c->x86 < 0xf) +@@ -1199,10 +1199,12 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + if (c->extended_cpuid_level < 0x80000006) + return; + ++ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; ++ + cpuid(0x80000006, &eax, &ebx, &ecx, &edx); + +- tlb_lld_4k = (ebx >> 16) & mask; +- tlb_lli_4k = ebx & mask; ++ tlb_lld_4k = ((ebx >> 16) & mask) << shift; ++ tlb_lli_4k = (ebx & mask) << shift; + + /* + * K8 doesn't have 2M/4M entries in the L2 TLB so read out the L1 TLB +@@ -1214,16 +1216,18 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + } + + /* Handle DTLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ +- if (!((eax >> 16) & mask)) ++ tmp = ((eax >> 16) & mask) << shift; ++ if (!tmp) + tlb_lld_2m = (cpuid_eax(0x80000005) >> 16) & 0xff; + else +- tlb_lld_2m = (eax >> 16) & mask; ++ tlb_lld_2m = tmp; + + /* a 4M entry uses two 2M entries */ + tlb_lld_4m = tlb_lld_2m >> 1; + + /* Handle ITLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ +- if (!(eax & mask)) { ++ tmp = (eax & mask) << shift; ++ if (!tmp) { + /* Erratum 658 */ + if (c->x86 == 0x15 && c->x86_model <= 0x1f) { + tlb_lli_2m = 1024; +@@ -1231,8 +1235,9 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + cpuid(0x80000005, &eax, &ebx, &ecx, &edx); + tlb_lli_2m = eax & 0xff; + } +- } else +- tlb_lli_2m = eax & mask; ++ } else { ++ tlb_lli_2m = tmp; ++ } + + tlb_lli_4m = tlb_lli_2m >> 1; + +diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c +--- a/arch/x86/kernel/cpu/common.c ++++ b/arch/x86/kernel/cpu/common.c +@@ -857,7 +857,7 @@ static void get_model_name(struct cpuinfo_x86 *c) + + void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) + { +- unsigned int n, dummy, ebx, ecx, edx, l2size; ++ unsigned int n, dummy, ebx, ecx, edx, l2size, shift __maybe_unused; + + n = c->extended_cpuid_level; + +@@ -877,7 +877,9 @@ void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) + l2size = ecx >> 16; + + #ifdef CONFIG_X86_64 ++ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; + c->x86_tlbsize += ((ebx >> 16) & 0xfff) + (ebx & 0xfff); ++ c->x86_tlbsize <<= shift; + #else + /* do processor-specific cache resizing */ + if (this_cpu->legacy_cache_size) diff --git a/pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.patch.sig b/pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.patch.sig new file mode 100644 index 0000000..aa440e0 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.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/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch b/pkgbuilds/linux-omarchy/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch new file mode 100644 index 0000000..6977a84 --- /dev/null +++ b/pkgbuilds/linux-omarchy/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch @@ -0,0 +1,12 @@ +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 +@@ -1343,7 +1343,7 @@ ttm_bo_swapout_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) + + if (ttm_tt_is_populated(tt)) { + ret = ttm_tt_swapout(bdev, tt, swapout_walk->gfp_flags); +- if (!ret) { ++ if (ret > 0) { + spin_lock(&bdev->lru_lock); + ttm_resource_del_bulk_move_unevictable(bo->resource, bo); + ttm_resource_move_to_lru_tail(bo->resource); diff --git a/pkgbuilds/linux-omarchy/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch.sig b/pkgbuilds/linux-omarchy/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch.sig new file mode 100644 index 0000000..8ca1431 Binary files /dev/null and b/pkgbuilds/linux-omarchy/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.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..cc70049 --- /dev/null +++ b/pkgbuilds/linux-omarchy/PKGBUILD @@ -0,0 +1,741 @@ +# Maintainer: The Omarchy Authors +# Adopted from the Arch Linux linux package. + +pkgbase=linux-omarchy +pkgver=7.2.5 +pkgrel=6 +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} + 0011-kbuild-optimize-for-performance-o3.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} + 0220-x86-amd-zen5-tlb-sizes.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} + 0361-drm-ttm-swapped-out-resource-leaves-bulk-move.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' + 'cc2a66a097b5567e80c59b7d6c492fef5e667fc71a0390908712c69512c4136dbaf3d150b62d04eff960c796f7a94e6d0915d3b92cb336318b49f5f516a3bba6' + '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' + 'e59c4da548b2ea4fd6144886e3e2e765b3e73972121e9e73dbc05c066834b2b35e6ef4c17617e77efc90260566bf7c0910d2138d8d5c1c3b6094eeac9de64cfe' + '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' + '15855e9c28cb0abaab7ab606904dfc84d426ccab44edae59f0e1f092202b80525929f8d394d04ca1513d1d5dfc2cfc108a66a9c1cb42b9966c6417562ef20689' + '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=('5cbc3cba5e3abe4c77a54aeb2015b04e39dc80351accaa7c268da3e67577c0ad47109b8693bf2dee8d5366d0a0df3e6439c32d18fcf5ea07345bfcf517e5036d') + +# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc +sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' + 'SKIP' + '95f3e9209629044028373af987dc0e270a5a15acb8c8e49c5f05057220c75fe2' + 'SKIP' + '6cf74fc3f28a751354187b6cf610c3e43cf9088b5f730b68dbb575828c3a7478' + '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' + '718b40e15350049e03c0ed281b9257fca5432b8dec2e0e145e8b26507ee8892d' + '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' + '503eba8d7a80b978fccdaac798f57cbb6cd2b2b04fd04c46399daa004b6646dc' + '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 f130710..dc1d90e 100644 --- a/pkgbuilds/lmstudio-bin/PKGBUILD +++ b/pkgbuilds/lmstudio-bin/PKGBUILD @@ -1,8 +1,9 @@ # Maintainer: noureddinex pkgname=lmstudio-bin -pkgver=0.4.24 +pkgver=0.4.25 pkgrel=1 -_pkgver=${pkgver}-${pkgrel} +_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,9 +16,7 @@ conflicts=(lmstudio) source=("https://installers.lmstudio.ai/linux/x64/${_pkgver}/LM-Studio-${_pkgver}-x64.AppImage" "lmstudio.png" "lmstudio.desktop") -sha256sums=('17cb8ac6374f9182fc127efae20680265e3c4c17d96eadf147eb5fe6111a9353' - '9f791789c959a11316328692807737a5f1bc1c170ae99ec04c56bfd8ee8263e5' - '635dec12f3e3a57136b9e6fd7c2839ed6da7287fa55b482d64debf6eacf36baa') +sha256sums=('eca467446c833824697e8befab300fe5269fdf984e3ee4385fcbad8502f07c53' '9f791789c959a11316328692807737a5f1bc1c170ae99ec04c56bfd8ee8263e5' '635dec12f3e3a57136b9e6fd7c2839ed6da7287fa55b482d64debf6eacf36baa') prepare() { chmod +x "${srcdir}/${source[0]##*/}" diff --git a/pkgbuilds/localsend-bin/.omarchy/package.json b/pkgbuilds/localsend-bin/.omarchy/package.json index 07b779c..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": "75f9faba7aedbfb0a1c74b5c32a5f250d22a0bec" + "upstream": { + "watch": { + "github": "localsend/localsend", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "localsend-bin", + "commit": "8dc54e7bdcbf24653b9247ec8af07aa012713b1c" + } } 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/m1n1-aurora/.omarchy/package.json b/pkgbuilds/m1n1-aurora/.omarchy/package.json new file mode 100644 index 0000000..dd11ed8 --- /dev/null +++ b/pkgbuilds/m1n1-aurora/.omarchy/package.json @@ -0,0 +1,5 @@ +{ + "source": "local", + "channels": ["edge"], + "sync": false +} diff --git a/pkgbuilds/m1n1-aurora/PKGBUILD b/pkgbuilds/m1n1-aurora/PKGBUILD new file mode 100644 index 0000000..b115039 --- /dev/null +++ b/pkgbuilds/m1n1-aurora/PKGBUILD @@ -0,0 +1,54 @@ +# m1n1 for linux-aurora. Aurora's device trees name the USB4 aliases +# usb4-N-acio, usb4-N-nhi and usb4-N-pcie-adapter; m1n1 v1.6.1 only looks up +# the usb4_N_* names, so without the alias fallback the USB4 tunables are +# never applied. The Asahi m1n1 stays with linux-asahi. +pkgname=m1n1-aurora +pkgver=1.6.1.aurora1 +pkgrel=3 +pkgdesc='Omarchy Aurora m1n1 with compatible USB4 tunable aliases' +arch=('aarch64') +url='https://github.com/aurora-silicon/m1n1' +license=('MIT' 'CC0-1.0' 'OFL-1.1-RFN' 'Zlib' 'BSD-2-Clause OR GPL-2.0-or-later' 'BSD-3-Clause OR GPL-2.0-or-later' 'MIT OR Apache-2.0') +provides=("m1n1=$pkgver") +conflicts=('m1n1') +makedepends=('clang' 'lld' 'llvm' 'rustup' 'make' 'patch') +options=('!strip' '!debug') +_commit=06a4601a351ebfd1abb6abba9a44c34e40d94776 +source=("m1n1-${_commit}.tar.gz::https://github.com/aurora-silicon/m1n1/archive/${_commit}.tar.gz" + aurora-cio-aliases.patch + bootlogo_48.bin + bootlogo_128.bin + bootlogo_256.bin +) +sha256sums=('9d6972d3edaff856e985f2b1feb64fcf9b42fc5eecda7be64a242b49bbbe636d' + '4345a4e431642c4316595bdf023bc67d5c72e0374f3c8d59be81318710138f32' + '6668050653645711ad7523fe2abeb4cbe85a92c875f3eec754b6db23cea2191c' + 'b19cb017645c7a9068ea0be9b6bb394131d7ca11b28093cf6caa1ffca74b0a4e' + '9c659b392aacfa31c62c638003d2257abfdbe17919b5c90cde4c4e62f87addab') + +prepare() { + cd "m1n1-$_commit" + patch -p1 < "$srcdir/aurora-cio-aliases.patch" + for size in 48 128 256; do + cp "$srcdir/bootlogo_$size.bin" "data/bootlogo_$size.bin" + done + rustup toolchain install 1.89.0 --profile minimal --target aarch64-unknown-none-softfloat +} + +build() { + cd "m1n1-$_commit" + RUSTUP_TOOLCHAIN=1.89.0 make RELEASE=1 USE_CLANG=1 ARCH=aarch64-linux-gnu \ + M1N1_VERSION_TAG=v1.6.1-omarchy.aurora1 +} + +package() { + # asahi-scripts (update-m1n1 and its pacman hook) only exists in the + # asahi-alarm repository, which the builder does not configure. Declared + # here so makepkg does not try to install it at build time. + depends=('asahi-scripts>=20260127.1') + + cd "m1n1-$_commit" + install -Dm644 build/m1n1.bin "$pkgdir/usr/lib/asahi-boot/m1n1.bin" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -m644 3rdparty_licenses/* "$pkgdir/usr/share/licenses/$pkgname/" +} diff --git a/pkgbuilds/m1n1-aurora/README.md b/pkgbuilds/m1n1-aurora/README.md new file mode 100644 index 0000000..58cf613 --- /dev/null +++ b/pkgbuilds/m1n1-aurora/README.md @@ -0,0 +1,13 @@ +# m1n1-aurora + +m1n1 v1.6.1 from aurora-silicon/m1n1 (06a4601a351ebfd1abb6abba9a44c34e40d94776) with RC, NHI and PCIe adapter alias fallbacks for the USB4 tunables, plus the Omarchy boot logos. Aurora's device trees name those aliases `usb4-N-acio`, `usb4-N-nhi` and `usb4-N-pcie-adapter`; stock m1n1 only looks up the `usb4_N_*` names. Latest Aurora m1n1 main is not qualified. + +## Pairing with linux-aurora + +- It provides `m1n1=1.6.1.aurora1`, which satisfies linux-aurora's `m1n1>=1.6.1`, and conflicts with asahi-alarm's `m1n1`. pacman prefers a package literally named `m1n1` over a provider, so an Aurora transaction names `m1n1-aurora` explicitly and accepts its conflict with `m1n1` (`--ask 4` under `--noconfirm`). +- It owns `/usr/lib/asahi-boot/m1n1.bin`. asahi-scripts' `95-m1n1-install.hook` runs `update-m1n1` when that file, `/usr/lib/modules/*/dtbs/*` or U-Boot changes. `update-m1n1` writes m1n1, the device trees and gzipped U-Boot into `m1n1/boot.bin` on the system ESP. asahi-alarm's default takes the device trees from the newest `/lib/modules/*-ARCH/dtbs`, which is where linux-aurora installs its Apple DTBs. `/etc/default/update-m1n1` overrides are left alone. +- Raising linux-aurora's `_m1n1_version` above 1.6.1 needs an m1n1-aurora whose provided version satisfies it, released first. + +## Qualification + +Each bump is cold-boot qualified on the M1 Pro and M2 Max: package installation, the hook rebuilding `boot.bin`, the logo, physical boot and two external displays. A successful build is not hardware acceptance. diff --git a/pkgbuilds/m1n1-aurora/aurora-cio-aliases.patch b/pkgbuilds/m1n1-aurora/aurora-cio-aliases.patch new file mode 100644 index 0000000..13ead3b --- /dev/null +++ b/pkgbuilds/m1n1-aurora/aurora-cio-aliases.patch @@ -0,0 +1,28 @@ +diff --git a/src/kboot.c b/src/kboot.c +index a5bd4f1..1089666 100644 +--- a/src/kboot.c ++++ b/src/kboot.c +@@ -1249,17 +1249,23 @@ static int dt_set_acio_tunables(void) + + memset(fdt_alias, 0, sizeof(fdt_alias)); + snprintf(fdt_alias, sizeof(fdt_alias), "usb4_%d_rc", i); ++ if (!fdt_get_alias(dt, fdt_alias)) ++ snprintf(fdt_alias, sizeof(fdt_alias), "usb4-%d-acio", i); + dt_copy_acio_tunables(adt_path, fdt_alias, usb4_rc_tunables, + sizeof(usb4_rc_tunables) / sizeof(*usb4_rc_tunables)); + + memset(fdt_alias, 0, sizeof(fdt_alias)); + snprintf(fdt_alias, sizeof(fdt_alias), "usb4_%d_pcie_adapter", i); ++ if (!fdt_get_alias(dt, fdt_alias)) ++ snprintf(fdt_alias, sizeof(fdt_alias), "usb4-%d-pcie-adapter", i); + dt_copy_acio_tunables(adt_path, fdt_alias, usb4_pcie_adapter_tunables, + sizeof(usb4_pcie_adapter_tunables) / + sizeof(*usb4_pcie_adapter_tunables)); + + memset(fdt_alias, 0, sizeof(fdt_alias)); + snprintf(fdt_alias, sizeof(fdt_alias), "usb4_%d_nhi", i); ++ if (!fdt_get_alias(dt, fdt_alias)) ++ snprintf(fdt_alias, sizeof(fdt_alias), "usb4-%d-nhi", i); + dt_copy_acio_tunables(adt_path, fdt_alias, usb4_nhi_tunables, + sizeof(usb4_nhi_tunables) / sizeof(*usb4_nhi_tunables)); + dt_copy_usb4_drom(adt_path, fdt_alias); diff --git a/pkgbuilds/m1n1-aurora/bootlogo_128.bin b/pkgbuilds/m1n1-aurora/bootlogo_128.bin new file mode 100644 index 0000000..8616272 Binary files /dev/null and b/pkgbuilds/m1n1-aurora/bootlogo_128.bin differ diff --git a/pkgbuilds/m1n1-aurora/bootlogo_256.bin b/pkgbuilds/m1n1-aurora/bootlogo_256.bin new file mode 100644 index 0000000..72508c6 --- /dev/null +++ b/pkgbuilds/m1n1-aurora/bootlogo_256.bin @@ -0,0 +1 @@ +žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj����������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�����������������������������������������������������������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�������������������������������������������������������������������������������������������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj������������������žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj�žฮj� \ No newline at end of file diff --git a/pkgbuilds/m1n1-aurora/bootlogo_48.bin b/pkgbuilds/m1n1-aurora/bootlogo_48.bin new file mode 100644 index 0000000..5051bb9 Binary files /dev/null and b/pkgbuilds/m1n1-aurora/bootlogo_48.bin differ 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 a72a6ff..68cbe05 100644 --- a/pkgbuilds/mise-bin/PKGBUILD +++ b/pkgbuilds/mise-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Jeff Dickey pkgname=mise-bin -pkgver=2026.9.4 +pkgver=2026.9.12 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=('77f312d9cb4696f5ba547af4296f50de86b2d280d73f98eb4d4ae71cda559607') -sha256sums_aarch64=('ed4a996247a6f1fa088c8f6ae3c64c255a463347e2c6d074155e7147c454b0e3') +sha256sums_x86_64=('30c79a0a24d8f0ad80e6c9b11ec54816be2a9b77e7eeae32c1267a5b9d34d3d7') +sha256sums_aarch64=('7bc2a5558b787a33f22e4b5955cfec58871ad3723658418ad3d2cdf5a0e693b9') package() { install -Dm755 "${srcdir}/mise/bin/mise" "${pkgdir}/usr/bin/mise" diff --git a/pkgbuilds/monologue/.omarchy/package.json b/pkgbuilds/monologue/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/monologue/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/monologue/PKGBUILD b/pkgbuilds/monologue/PKGBUILD new file mode 100644 index 0000000..9b9e14e --- /dev/null +++ b/pkgbuilds/monologue/PKGBUILD @@ -0,0 +1,40 @@ +# Maintainer: David Heinemeier Hansson + +pkgname=monologue +pkgver=0.1.0 +pkgrel=3 +pkgdesc='A simple, theme-synced webcam recorder for Omarchy' +arch=('x86_64' 'aarch64') +url='https://github.com/omacom/monologue' +license=('MIT') +install='monologue.install' +options=('!debug') +depends=( + 'ffmpeg' + 'hicolor-icon-theme' + 'libpulse' + 'qt6-base' + 'qt6-declarative' + 'qt6-multimedia>=6.8' + 'xdg-desktop-portal' +) +makedepends=('gcc' 'make' 'pkgconf') +optdepends=('omacut: trim recordings directly from Monologue') +# Pin the published source until a tagged release is available. +_commit=23e0844f60feef2f9d2cf2c9d89f13eb0bf5adef +source=("$pkgname-$_commit.tar.gz::$url/archive/$_commit.tar.gz") +sha256sums=('1a04b9e47b29846e9135d110978f7f35c0274f7361729f0b6ac03796499c0ad6') + +build() { + cd "$srcdir/$pkgname-$_commit" + ./bin/build +} + +package() { + cd "$srcdir/$pkgname-$_commit" + + install -Dm755 build/monologue "$pkgdir/usr/bin/monologue" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 pkgbuild/monologue.svg "$pkgdir/usr/share/icons/hicolor/scalable/apps/monologue.svg" + install -Dm644 pkgbuild/monologue.desktop "$pkgdir/usr/share/applications/monologue.desktop" +} diff --git a/pkgbuilds/monologue/monologue.install b/pkgbuilds/monologue/monologue.install new file mode 100644 index 0000000..5357602 --- /dev/null +++ b/pkgbuilds/monologue/monologue.install @@ -0,0 +1,12 @@ +post_install() { + command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q + command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q -t -f usr/share/icons/hicolor +} + +post_upgrade() { + post_install +} + +post_remove() { + post_install +} 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 8038e15..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": "8d4c074f4b0a373c04c6f41baba83d3bd7d6d97b" + "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/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/nvidia-utils/.omarchy/package.json b/pkgbuilds/nvidia-utils/.omarchy/package.json new file mode 100644 index 0000000..7ddd330 --- /dev/null +++ b/pkgbuilds/nvidia-utils/.omarchy/package.json @@ -0,0 +1,6 @@ +{ + "source": "local", + "channels": [ + "edge" + ] +} diff --git a/pkgbuilds/nvidia-utils/LICENSE b/pkgbuilds/nvidia-utils/LICENSE new file mode 100644 index 0000000..b87c5e4 --- /dev/null +++ b/pkgbuilds/nvidia-utils/LICENSE @@ -0,0 +1,12 @@ +Copyright Arch Linux Contributors + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED โ€œAS ISโ€ AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/pkgbuilds/nvidia-utils/PKGBUILD b/pkgbuilds/nvidia-utils/PKGBUILD new file mode 100644 index 0000000..105281e --- /dev/null +++ b/pkgbuilds/nvidia-utils/PKGBUILD @@ -0,0 +1,240 @@ +# Maintainer: Sven-Hendrik Haase +# Maintainer: Peter Jung +# Contributor: James Rayner +# Contributor: Vasiliy Stelmachenok +# Contributor: Thomas Baechler + +pkgbase=nvidia-utils +pkgname=('nvidia-utils') +pkgver=615.71.09 +pkgrel=1.1 +arch=('aarch64') +url="https://www.nvidia.com/" +license=('LicenseRef-NVIDIA-Driver-License-Agreement') +options=('!strip') +source_aarch64=("https://download.nvidia.com/XFree86/Linux-aarch64/${pkgver}/NVIDIA-Linux-aarch64-${pkgver}.run") +source=('nvidia-drm-outputclass.conf' + 'nvidia-utils.sysusers' + 'nvidia.rules' + 'systemd-homed-override.conf' + 'systemd-suspend-override.conf' + 'nvidia-utils.conf') +sha512sums=('de7116c09f282a27920a1382df84aa86f559e537664bb30689605177ce37dc5067748acf9afd66a3269a6e323461356592fdfc624c86523bf105ff8fe47d3770' + '1bcf2c6ee71686c0d32625e746ec8c0f7cf42fc63c76c3076ff2526b2661e8b9e9f76eaa2c4b213c7cc437a6f06006cc07672c4974d7f4515b2de2fd7c47a891' + '7f1457dc454144fdece5abf795744c4c948a13feb8d49c20e2a1b8b8973e86f980233b521485d73eb039c396688f287a3a5b3de8cb1fb20ed70cc62e4ba91250' + 'a0183adce78e40853edf7e6b73867e7a8ea5dabac8e8164e42781f64d5232fbe869f850ab0697c3718ebced5cde760d0e807c05da50a982071dfe1157c31d6b8' + '55def6319f6abb1a4ccd28a89cd60f1933d155c10ba775b8dfa60a2dc5696b4b472c14b252dc0891f956e70264be87c3d5d4271e929a4fc4b1a68a6902814cee' + 'a380e5faeb19293c90f613cd92bcd1cef7597ee52f79f03ffdffe5d37d2badc05b6bdb4c26a9d610868ae4c16eafd56e7d16f769e849dc0335d0d248c6235fe9') +sha512sums_aarch64=('316f90d5e0ba74db3a79a91464955240aa2c14e986653067fe902c132c771898d2866afc4e6498069b33665596e17b1b5282980936e1e6e31cabb53787e70196') + +_pkg=NVIDIA-Linux-${CARCH}-${pkgver} + +create_links() { + # create soname links + find "$pkgdir" -type f -name '*.so*' ! -path '*xorg/*' -print0 | while read -d $'\0' _lib; do + _soname=$(dirname "${_lib}")/$(readelf -d "${_lib}" | grep -Po 'SONAME.*: \[\K[^]]*' || true) + _base=$(echo ${_soname} | sed -r 's/(.*)\.so.*/\1.so/') + [[ -e "${_soname}" ]] || ln -s $(basename "${_lib}") "${_soname}" + [[ -e "${_base}" ]] || ln -s $(basename "${_soname}") "${_base}" + done +} + +prepare() { + sh ${_pkg}.run --extract-only + cd ${_pkg} + bsdtar -xf nvidia-persistenced-init.tar.bz2 + +} + +package_nvidia-utils() { + pkgdesc="NVIDIA drivers utilities" + depends=('libglvnd' 'egl-wayland' 'egl-wayland2' 'egl-gbm' 'egl-x11') + optdepends=('nvidia-settings: configuration tool' + 'xorg-server: Xorg support' + 'xorg-server-devel: nvidia-xconfig' + 'opencl-nvidia: OpenCL support') + conflicts=('nvidia-libgl') + provides=('vulkan-driver' 'opengl-driver' 'nvidia-libgl') + replaces=('nvidia-libgl') + install="${pkgname}.install" + + cd "${_pkg}" + + # Check http://us.download.nvidia.com/XFree86/Linux-x86_64/${pkgver}/README/installedcomponents.html + # for hints on what needs to be installed where. + + # X driver + install -Dm755 nvidia_drv.so "${pkgdir}/usr/lib/xorg/modules/drivers/nvidia_drv.so" + + # Wayland/GBM + mkdir -p "${pkgdir}/usr/lib/gbm" + ln -sr "${pkgdir}/usr/lib/libnvidia-allocator.so.${pkgver}" "${pkgdir}/usr/lib/gbm/nvidia-drm_gbm.so" + + # firmware + install -Dm644 -t "${pkgdir}/usr/lib/firmware/nvidia/${pkgver}/" firmware/*.bin + + # GLX extension module for X + install -Dm755 "libglxserver_nvidia.so.${pkgver}" "${pkgdir}/usr/lib/nvidia/xorg/libglxserver_nvidia.so.${pkgver}" + # Ensure that X finds glx + ln -s "libglxserver_nvidia.so.${pkgver}" "${pkgdir}/usr/lib/nvidia/xorg/libglxserver_nvidia.so.1" + ln -s "libglxserver_nvidia.so.${pkgver}" "${pkgdir}/usr/lib/nvidia/xorg/libglxserver_nvidia.so" + + install -Dm755 "libGLX_nvidia.so.${pkgver}" "${pkgdir}/usr/lib/libGLX_nvidia.so.${pkgver}" + + # OpenGL libraries + install -Dm755 "libEGL_nvidia.so.${pkgver}" "${pkgdir}/usr/lib/libEGL_nvidia.so.${pkgver}" + install -Dm755 "libGLESv1_CM_nvidia.so.${pkgver}" "${pkgdir}/usr/lib/libGLESv1_CM_nvidia.so.${pkgver}" + install -Dm755 "libGLESv2_nvidia.so.${pkgver}" "${pkgdir}/usr/lib/libGLESv2_nvidia.so.${pkgver}" + install -Dm644 "10_nvidia.json" "${pkgdir}/usr/share/glvnd/egl_vendor.d/10_nvidia.json" + + # OpenGL core library + install -Dm755 "libnvidia-glcore.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-glcore.so.${pkgver}" + install -Dm755 "libnvidia-eglcore.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-eglcore.so.${pkgver}" + install -Dm755 "libnvidia-glsi.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-glsi.so.${pkgver}" + if [[ $CARCH == aarch64 ]]; then + # Required by the ARM EGL/GLX libraries. + install -Dm755 "libnvidia-rmapi-tegra.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-rmapi-tegra.so.${pkgver}" + fi + + # misc + install -Dm755 "libnvidia-api.so.1" "${pkgdir}/usr/lib/libnvidia-api.so.1" + install -Dm755 "libnvidia-fbc.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-fbc.so.${pkgver}" + install -Dm755 "libnvidia-encode.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-encode.so.${pkgver}" + install -Dm755 "libnvidia-cfg.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-cfg.so.${pkgver}" + install -Dm755 "libnvidia-ml.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-ml.so.${pkgver}" + install -Dm755 "libnvidia-fmdrv.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-fmdrv.so.${pkgver}" + install -Dm755 "libnvidia-imex.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-imex.so.${pkgver}" + install -Dm755 "libnvidia-glvkspirv.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-glvkspirv.so.${pkgver}" + install -Dm755 "libnvidia-allocator.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-allocator.so.${pkgver}" + install -Dm755 "libnvidia-gpucomp.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-gpucomp.so.${pkgver}" + + # Vulkan ICD + install -Dm644 "nvidia_icd.json" "${pkgdir}/usr/share/vulkan/icd.d/nvidia_icd.json" + install -Dm644 "nvidia_layers.json" "${pkgdir}/usr/share/vulkan/implicit_layer.d/nvidia_layers.json" + + if [[ $CARCH = x86_64 ]]; then + # VulkanSC + install -D -m755 nvidia-pcc -t "${pkgdir}/usr/bin" + install -D -m755 "libnvidia-vksc-core.so.${pkgver}" -t "${pkgdir}/usr/lib" + install -D -m644 nvidia_icd_vksc.json -t "${pkgdir}/usr/share/vulkansc/icd.d" + fi + + # VDPAU + install -Dm755 "libvdpau_nvidia.so.${pkgver}" "${pkgdir}/usr/lib/vdpau/libvdpau_nvidia.so.${pkgver}" + + # nvidia-tls library + install -Dm755 "libnvidia-tls.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-tls.so.${pkgver}" + + # CUDA + install -Dm755 "libcuda.so.${pkgver}" "${pkgdir}/usr/lib/libcuda.so.${pkgver}" + install -Dm755 "libnvcuvid.so.${pkgver}" "${pkgdir}/usr/lib/libnvcuvid.so.${pkgver}" + install -Dm755 "libcudadebugger.so.${pkgver}" "${pkgdir}/usr/lib/libcudadebugger.so.${pkgver}" + + # NVVM Compiler library loaded by the CUDA driver to do JIT link-time-optimization + install -Dm644 "libnvidia-nvvm.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-nvvm.so.${pkgver}" + install -Dm755 "libnvidia-nvvm70.so.4" "${pkgdir}/usr/lib/libnvidia-nvvm70.so.4" + + # PTX JIT Compiler (Parallel Thread Execution (PTX) is a pseudo-assembly language for CUDA) + install -Dm755 "libnvidia-ptxjitcompiler.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-ptxjitcompiler.so.${pkgver}" + + # raytracing + install -Dm755 "nvoptix.bin" "${pkgdir}/usr/share/nvidia/nvoptix.bin" + install -Dm755 "libnvoptix.so.${pkgver}" "${pkgdir}/usr/lib/libnvoptix.so.${pkgver}" + install -Dm755 "libnvidia-rtcore.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-rtcore.so.${pkgver}" + + # NGX + install -Dm755 nvidia-ngx-updater "${pkgdir}/usr/bin/nvidia-ngx-updater" + install -Dm755 "libnvidia-ngx.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-ngx.so.${pkgver}" + if [[ $CARCH = x86_64 ]]; then + install -Dm755 _nvngx.dll "${pkgdir}/usr/lib/nvidia/wine/_nvngx.dll" + install -Dm755 nvngx.dll "${pkgdir}/usr/lib/nvidia/wine/nvngx.dll" + install -Dm755 nvngx_dlssg.dll "${pkgdir}/usr/lib/nvidia/wine/nvngx_dlssg.dll" + fi + + # Optical flow + install -Dm755 "libnvidia-opticalflow.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-opticalflow.so.${pkgver}" + + if [[ $CARCH = x86_64 ]]; then + # Cryptography library wrapper + install -Dm755 "libnvidia-pkcs11.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-pkcs11.so.${pkgver}" + install -Dm755 "libnvidia-pkcs11-openssl3.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-pkcs11-openssl3.so.${pkgver}" + fi + + # Sandboxhelper + install -Dm755 "libnvidia-sandboxutils.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-sandboxutils.so.${pkgver}" + + # Present Helper + install -Dm755 "libnvidia-present.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-present.so.${pkgver}" + + # https://github.com/microsoft/TileIR + install -Dm755 "libnvidia-tileiras.so.${pkgver}" "${pkgdir}/usr/lib/libnvidia-tileiras.so.${pkgver}" + + # Debug + install -Dm755 nvidia-debugdump "${pkgdir}/usr/bin/nvidia-debugdump" + + # nvidia-xconfig + install -Dm755 nvidia-xconfig "${pkgdir}/usr/bin/nvidia-xconfig" + install -Dm644 nvidia-xconfig.1.gz "${pkgdir}/usr/share/man/man1/nvidia-xconfig.1.gz" + + # nvidia-bug-report + install -Dm755 nvidia-bug-report.sh "${pkgdir}/usr/bin/nvidia-bug-report.sh" + + # nvidia-smi + install -Dm755 nvidia-smi "${pkgdir}/usr/bin/nvidia-smi" + install -Dm644 nvidia-smi.1.gz "${pkgdir}/usr/share/man/man1/nvidia-smi.1.gz" + + # nvidia-cuda-mps + install -Dm755 nvidia-cuda-mps-server "${pkgdir}/usr/bin/nvidia-cuda-mps-server" + install -Dm755 nvidia-cuda-mps-control "${pkgdir}/usr/bin/nvidia-cuda-mps-control" + install -Dm644 nvidia-cuda-mps-control.1.gz "${pkgdir}/usr/share/man/man1/nvidia-cuda-mps-control.1.gz" + + # nvidia-modprobe + # This should be removed if nvidia fixed their uvm module! + install -Dm4755 nvidia-modprobe "${pkgdir}/usr/bin/nvidia-modprobe" + install -Dm644 nvidia-modprobe.1.gz "${pkgdir}/usr/share/man/man1/nvidia-modprobe.1.gz" + + # nvidia-persistenced + install -Dm755 nvidia-persistenced "${pkgdir}/usr/bin/nvidia-persistenced" + install -Dm644 nvidia-persistenced.1.gz "${pkgdir}/usr/share/man/man1/nvidia-persistenced.1.gz" + install -Dm644 nvidia-persistenced-init/systemd/nvidia-persistenced.service.template "${pkgdir}/usr/lib/systemd/system/nvidia-persistenced.service" + sed -i 's/__USER__/nvidia-persistenced/' "${pkgdir}/usr/lib/systemd/system/nvidia-persistenced.service" + + # application profiles + install -Dm644 "nvidia-application-profiles-${pkgver}-rc" "${pkgdir}/usr/share/nvidia/nvidia-application-profiles-${pkgver}-rc" + install -Dm644 "nvidia-application-profiles-${pkgver}-key-documentation" "${pkgdir}/usr/share/nvidia/nvidia-application-profiles-${pkgver}-key-documentation" + + install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/nvidia-utils/LICENSE" + install -Dm644 README.txt "${pkgdir}/usr/share/doc/nvidia/README" + install -Dm644 NVIDIA_Changelog "${pkgdir}/usr/share/doc/nvidia/NVIDIA_Changelog" + cp -r html "${pkgdir}/usr/share/doc/nvidia/" + ln -s nvidia "${pkgdir}/usr/share/doc/nvidia-utils" + + # new power management support + install -Dm644 systemd/system/*.service -t "${pkgdir}/usr/lib/systemd/system" + install -Dm755 systemd/system-sleep/nvidia "${pkgdir}/usr/lib/systemd/system-sleep/nvidia" + install -Dm755 systemd/nvidia-sleep.sh "${pkgdir}/usr/bin/nvidia-sleep.sh" + install -Dm755 nvidia-powerd "${pkgdir}/usr/bin/nvidia-powerd" + install -Dm644 dlsnetparams.csv "${pkgdir}/usr/share/nvidia/nvidia-powerd/dlsnetparams.csv" + install -Dm644 nvidia-dbus.conf "${pkgdir}/usr/share/dbus-1/system.d/nvidia-dbus.conf" + install -Dm644 "${srcdir}/systemd-homed-override.conf" "${pkgdir}/usr/lib/systemd/system/systemd-homed.service.d/10-nvidia-no-freeze-session.conf" + install -Dm644 "${srcdir}/systemd-suspend-override.conf" "${pkgdir}/usr/lib/systemd/system/systemd-suspend.service.d/10-nvidia-no-freeze-session.conf" + install -Dm644 "${srcdir}/systemd-suspend-override.conf" "${pkgdir}/usr/lib/systemd/system/systemd-suspend-then-hibernate.service.d/10-nvidia-no-freeze-session.conf" + install -Dm644 "${srcdir}/systemd-suspend-override.conf" "${pkgdir}/usr/lib/systemd/system/systemd-hibernate.service.d/10-nvidia-no-freeze-session.conf" + install -Dm644 "${srcdir}/systemd-suspend-override.conf" "${pkgdir}/usr/lib/systemd/system/systemd-hybrid-sleep.service.d/10-nvidia-no-freeze-session.conf" + + # distro specific files must be installed in /usr/share/X11/xorg.conf.d + install -Dm644 "${srcdir}/nvidia-drm-outputclass.conf" "${pkgdir}/usr/share/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf" + + install -Dm644 "${srcdir}/nvidia-utils.sysusers" "${pkgdir}/usr/lib/sysusers.d/$pkgname.conf" + + install -Dm644 "${srcdir}/nvidia.rules" "$pkgdir"/usr/lib/udev/rules.d/60-nvidia.rules + + # Enable kernel suspend notifiers for open modules and override TemporaryFilePath + # from default /tmp to /var/tmp + install -Dm644 "${srcdir}/nvidia-utils.conf" "${pkgdir}/usr/lib/modprobe.d/nvidia-utils.conf" + + # Lists NVIDIA driver files for container runtimes like nvidia-container-toolkit + install -Dm644 sandboxutils-filelist.json "${pkgdir}/usr/share/nvidia/files.d/sandboxutils-filelist.json" + + create_links +} diff --git a/pkgbuilds/nvidia-utils/README.package.md b/pkgbuilds/nvidia-utils/README.package.md new file mode 100644 index 0000000..791d59e --- /dev/null +++ b/pkgbuilds/nvidia-utils/README.package.md @@ -0,0 +1,36 @@ +# Temporary NVIDIA ARM packaging correction + +Carries Arch Linux's `nvidia-utils` recipe at +[f9ae10b379f8b1d0832ec92bca1c12072aa123e9](https://gitlab.archlinux.org/archlinux/packaging/packages/nvidia-utils/-/commit/f9ae10b379f8b1d0832ec92bca1c12072aa123e9), +restricted to aarch64 and the nvidia-utils output. Upstream contributor credits, +packaging license and support files are retained. The OpenCL/DKMS split outputs +and their unused kernel source preparation are omitted. + +NVIDIA 615.71.09's ARM EGL/GLX libraries require +`libnvidia-rmapi-tegra.so.615.71.09`. NVIDIA includes it in its archive, but the +Arch recipe omits it. Adding that library and its generated symlink fixes +Hyprland's EGL initialization failure on DGX Spark. No rendering overrides are +installed. The correction passed package installation, normal desktop login +and reboot on a Spark running kernel 7.2.6 with NVIDIA 615.71.09. + +This recipe builds only for edge, using release 1.1 to sit above Arch's broken +release 1 and below a prospective fixed release 2. There is deliberately no +automatic NVIDIA version watcher: driver userspace must remain compatible with +the kernel package supplied by Arch Linux ARM. Check both packages before each +release; a stale overlay must not hold back a driver transition. + +## Delivery and removal + +Publishing requires an aarch64 edge build. Consumers must put `[omarchy]` +before `[extra]` in pacman.conf for unqualified installation and updates to +select this package. A higher pkgrel alone does not overcome repository order. +The tested Spark has that ordering; this PR does not change shared runtime or +installer configuration. Deployment must verify that ordering on the intended +ARM installation/update paths. This is not a claim of fresh-ISO validation. + +The upstream Arch submission is pending account approval. Once Arch Linux ARM +ships the missing library, validate that package on the Spark, remove this +recipe AND the published overlay package/database entry, and verify that normal +updates select the fixed Arch package. A higher Arch version alone does not +bypass an earlier repository's stale package. Do not use an epoch or rename the +package to prevent that transition. diff --git a/pkgbuilds/nvidia-utils/nvidia-drm-outputclass.conf b/pkgbuilds/nvidia-utils/nvidia-drm-outputclass.conf new file mode 100644 index 0000000..9c36f59 --- /dev/null +++ b/pkgbuilds/nvidia-utils/nvidia-drm-outputclass.conf @@ -0,0 +1,8 @@ +Section "OutputClass" + Identifier "nvidia" + MatchDriver "nvidia-drm" + Driver "nvidia" + Option "AllowEmptyInitialConfiguration" + ModulePath "/usr/lib/nvidia/xorg" + ModulePath "/usr/lib/xorg/modules" +EndSection diff --git a/pkgbuilds/nvidia-utils/nvidia-utils.conf b/pkgbuilds/nvidia-utils/nvidia-utils.conf new file mode 100644 index 0000000..580794a --- /dev/null +++ b/pkgbuilds/nvidia-utils/nvidia-utils.conf @@ -0,0 +1,15 @@ +# Blacklist nouveau and nova +blacklist nouveau +blacklist nova_core +blacklist nova_drm + +# Make sure that all modules are loaded after the main one. +softdep nvidia post: nvidia-uvm nvidia-drm + +# https://download.nvidia.com/XFree86/Linux-x86_64/595.45.04/README/powermanagement.html +# Enable Suspend Notifiers for faster and more modern suspend +options nvidia NVreg_UseKernelSuspendNotifiers=1 +# +# The destination should not be using tmpfs, so we prefer +# /var/tmp instead of /tmp +options nvidia NVreg_TemporaryFilePath=/var/tmp diff --git a/pkgbuilds/nvidia-utils/nvidia-utils.install b/pkgbuilds/nvidia-utils/nvidia-utils.install new file mode 100644 index 0000000..e2bb8f8 --- /dev/null +++ b/pkgbuilds/nvidia-utils/nvidia-utils.install @@ -0,0 +1,21 @@ +post_upgrade() { + # With 595+ open kernel modules, video memory preservation is handled by + # kernel suspend notifiers, making the nvidia suspend/hibernate services unnecessary. + # Disable them for users upgrading from older versions. + if (( $(vercmp $2 595.58.03-1) < 0)); then + for service in nvidia-resume nvidia-hibernate nvidia-suspend nvidia-suspend-then-hibernate; do + if systemctl is-enabled --quiet $service 2>/dev/null; then + echo "Disabling $service (no longer needed with open kernel modules)..." + systemctl disable $service + fi + done + fi +} + +pre_remove() { + for service in nvidia-resume nvidia-hibernate nvidia-suspend nvidia-suspend-then-hibernate; do + if systemctl is-enabled --quiet $service 2>/dev/null; then + systemctl disable $service + fi + done +} diff --git a/pkgbuilds/nvidia-utils/nvidia-utils.sysusers b/pkgbuilds/nvidia-utils/nvidia-utils.sysusers new file mode 100644 index 0000000..0166d15 --- /dev/null +++ b/pkgbuilds/nvidia-utils/nvidia-utils.sysusers @@ -0,0 +1 @@ +u! nvidia-persistenced 143 'NVIDIA Persistence Daemon' diff --git a/pkgbuilds/nvidia-utils/nvidia.rules b/pkgbuilds/nvidia-utils/nvidia.rules new file mode 100644 index 0000000..0ae41b2 --- /dev/null +++ b/pkgbuilds/nvidia-utils/nvidia.rules @@ -0,0 +1,7 @@ +# Device nodes are created by nvidia-modprobe, which is called by the nvidia DDX. +# In case the DDX is not started, the device nodes are never created, so call +# nvidia-modprobe in the udev rules to cover the Wayland/EGLStream and compute +# case without a started display. In the case where vfio-pci is used +# nvidia-modprobe should not be invoked. +ACTION=="add", SUBSYSTEM=="module", KERNEL=="nvidia_drm", TEST!="/dev/nvidia-uvm", \ + RUN+="/usr/bin/nvidia-modprobe -c0 -u" diff --git a/pkgbuilds/nvidia-utils/systemd-homed-override.conf b/pkgbuilds/nvidia-utils/systemd-homed-override.conf new file mode 100644 index 0000000..605d113 --- /dev/null +++ b/pkgbuilds/nvidia-utils/systemd-homed-override.conf @@ -0,0 +1,2 @@ +[Service] +Environment="SYSTEMD_HOME_LOCK_FREEZE_SESSION=false" diff --git a/pkgbuilds/nvidia-utils/systemd-suspend-override.conf b/pkgbuilds/nvidia-utils/systemd-suspend-override.conf new file mode 100644 index 0000000..2a45482 --- /dev/null +++ b/pkgbuilds/nvidia-utils/systemd-suspend-override.conf @@ -0,0 +1,2 @@ +[Service] +Environment="SYSTEMD_SLEEP_FREEZE_USER_SESSIONS=false" 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/obsidian/.omarchy/package.json b/pkgbuilds/obsidian/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/obsidian/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/obsidian/.omarchy/upstream.sh b/pkgbuilds/obsidian/.omarchy/upstream.sh new file mode 100644 index 0000000..e2b9028 --- /dev/null +++ b/pkgbuilds/obsidian/.omarchy/upstream.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -euo pipefail +python3 "$(dirname "$0")/../upstream.py" diff --git a/pkgbuilds/obsidian/PKGBUILD b/pkgbuilds/obsidian/PKGBUILD new file mode 100644 index 0000000..25a0ea5 --- /dev/null +++ b/pkgbuilds/obsidian/PKGBUILD @@ -0,0 +1,103 @@ +# Maintainer: Shun Li +# Extracted from omacom/omarchy-pkgs PR #240, revision dcef132b2df11bb762e233ea8c6af3110f079b67. + +pkgname=obsidian +pkgver=1.13.7 +pkgrel=2 +pkgdesc='Knowledge base on top of a local folder of plain-text Markdown files' +arch=('aarch64') +url='https://obsidian.md/' +license=('custom') +depends=( + 'alsa-lib' + 'at-spi2-core' + 'cairo' + 'dbus' + 'expat' + 'fontconfig' + 'gcc-libs' + 'glib2' + 'glibc' + 'gtk3' + 'hicolor-icon-theme' + 'libcups' + 'libnotify' + 'libsecret' + 'libx11' + 'libxcb' + 'libxcomposite' + 'libxdamage' + 'libxext' + 'libxfixes' + 'libxkbcommon' + 'libxrandr' + 'mesa' + 'nspr' + 'nss' + 'pango' + 'systemd-libs' + 'xdg-utils' +) +provides=('obsidian') +conflicts=('obsidian-appimage' 'obsidian-bin') +options=('!debug' '!strip') +checkdepends=('python') +source=('obsidian.desktop' 'upstream.py' 'test-upstream.py') +source_aarch64=( + "obsidian-${pkgver}-arm64.tar.gz::https://github.com/obsidianmd/obsidian-releases/releases/download/v${pkgver}/obsidian-${pkgver}-arm64.tar.gz" +) +sha256sums=('42ecb332c900a01b2a6db0a70b4cc8c0553e863aee72894b23ed20a6289b131c' + 'd2933bdc97d9ba55986d710114512e0eb105e1cc549cef4aa7416cb333e0a9b8' + '7213e4e691c53e184b0f037478ac6758fe44adad7a984fc1daef7fd17c31d5d8') +sha256sums_aarch64=('98aac34d1f132a35cf506fc3fa196d595dcdeefdebd44b0cc5faaa7a1a210de2') + +check() { + python test-upstream.py +} + +package() { + local source_dir="$srcdir/obsidian-${pkgver}-arm64" + local install_dir="$pkgdir/opt/obsidian" + + install -d "$install_dir" + cp -a --no-preserve=ownership "$source_dir/." "$install_dir/" + + # The official ARM64 archive currently carries two x86_64-only addons. + # Remove only an unusable x86_64 payload; retain it automatically if the + # vendor starts shipping an AArch64 build in a later release. + local addon machine + for addon in \ + resources/app.asar.unpacked/node_modules/btime/binding.node \ + resources/app.asar.unpacked/node_modules/get-fonts/binding.node; do + [[ -f "$install_dir/$addon" ]] || continue + machine=$(readelf -h "$install_dir/$addon" | + sed -n 's/^[[:space:]]*Machine:[[:space:]]*//p') + case "$machine" in + AArch64) ;; + 'Advanced Micro Devices X86-64') rm -f "$install_dir/$addon" ;; + *) error "Unexpected Obsidian addon architecture: $addon ($machine)"; return 1 ;; + esac + done + + find "$install_dir" -type d -exec chmod 755 {} + + find "$install_dir" -type f -exec chmod 644 {} + + chmod 755 \ + "$install_dir/chrome_crashpad_handler" \ + "$install_dir/obsidian" \ + "$install_dir/obsidian-cli" + # Chromium sandboxes through unprivileged user namespaces, which Arch-family + # kernels allow; a setuid-root helper is not needed. + chmod 755 "$install_dir/chrome-sandbox" + + install -d "$pkgdir/usr/bin" + ln -s /opt/obsidian/obsidian "$pkgdir/usr/bin/obsidian" + ln -s /opt/obsidian/obsidian-cli "$pkgdir/usr/bin/obsidian-cli" + install -Dm644 "$srcdir/obsidian.desktop" \ + "$pkgdir/usr/share/applications/obsidian.desktop" + install -Dm644 "$install_dir/resources/icon.png" \ + "$pkgdir/usr/share/icons/hicolor/512x512/apps/obsidian.png" + install -Dm644 "$install_dir/LICENSE.electron.txt" \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSE.electron.txt" + install -Dm644 "$install_dir/LICENSES.chromium.html" \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSES.chromium.html" +} diff --git a/pkgbuilds/obsidian/README.md b/pkgbuilds/obsidian/README.md new file mode 100644 index 0000000..ca6564e --- /dev/null +++ b/pkgbuilds/obsidian/README.md @@ -0,0 +1,11 @@ +# Obsidian on ARM + +This is the canonical `obsidian` package, extracted from omacom/omarchy-pkgs #240 at `dcef132b2df11bb762e233ea8c6af3110f079b67`. It repackages the official ARM64 desktop tarball and preserves the original contributor attribution and Electron/Chromium license files. + +The package owns the installed application bundle and Electron runtime. `chrome-sandbox` is installed without setuid: Chromium's sandbox uses unprivileged user namespaces, which Arch-family kernels allow. A system that disables them must re-enable them rather than launch Obsidian with `--no-sandbox`. Obsidian's user-controlled in-app updater may separately update application code; no updater-disabling patch is applied. See https://obsidian.md/help/updates. + +The package-local upstream hook selects stable desktop releases containing the exact ARM64 tarball, ignoring mobile-only tags. Missing SHA256 metadata on the selected desktop release fails rather than silently selecting an older installer. The offline tests in `test-upstream.py` run in the package's `check()`; run them directly with `python pkgbuilds/obsidian/test-upstream.py`. Upstream's scheduled sync opens or updates its normal review PR; maintainers need GitHub PR notifications enabled and should monitor failed sync runs too. + +This package conflicts with `obsidian-appimage` and `obsidian-bin`. It does not automatically remove them or migrate users. Adoption in Omarchy Mac requires a separate change to the existing ARM preinstall/removal mappings. Do not modify vaults or user configuration as part of the package transaction. + +Before this draft is ready, validate clean ARM installation, desktop/CLI paths, native payload architecture and opening/editing a disposable vault in a disposable GUI session. Existing AppImage operation does not validate this tarball package. The two named vendor x86-only addons are removed; any newly introduced foreign executable requires review. diff --git a/pkgbuilds/obsidian/obsidian.desktop b/pkgbuilds/obsidian/obsidian.desktop new file mode 100644 index 0000000..da3429e --- /dev/null +++ b/pkgbuilds/obsidian/obsidian.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Obsidian +Comment=Knowledge base on top of a local folder of Markdown files +Exec=obsidian %U +Terminal=false +Type=Application +Icon=obsidian +StartupWMClass=obsidian +MimeType=x-scheme-handler/obsidian; +Categories=Office; diff --git a/pkgbuilds/obsidian/test-upstream.py b/pkgbuilds/obsidian/test-upstream.py new file mode 100644 index 0000000..762d68e --- /dev/null +++ b/pkgbuilds/obsidian/test-upstream.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 +import importlib.util +from pathlib import Path +import unittest + +path = Path(__file__).with_name('upstream.py') +spec = importlib.util.spec_from_file_location('obsidian_upstream', path) +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) + + +def release(version, **extra): + return dict(tag_name='v' + version, draft=False, prerelease=False, + published_at='2026-09-23T00:00:00Z', assets=[{ + 'name': f'obsidian-{version}-arm64.tar.gz', + 'digest': 'sha256:' + 'a' * 64, + 'browser_download_url': f'https://github.com/obsidianmd/obsidian-releases/releases/download/v{version}/obsidian-{version}-arm64.tar.gz' + }], **extra) + + +class DesktopReleaseTests(unittest.TestCase): + def test_mobile_only_and_preview_ignored(self): + mobile = release('2.0.0'); mobile['assets'] = [{'name': 'Obsidian.apk'}] + preview = release('3.0.0'); preview['prerelease'] = True + draft = release('4.0.0'); draft['draft'] = True + self.assertEqual(module.select([mobile, preview, draft, release('1.13.7')])['pkgver'], '1.13.7') + + def test_numeric_version_order(self): + self.assertEqual(module.select([release('1.9.0'), release('1.13.7')])['pkgver'], '1.13.7') + + def test_bad_newest_digest_fails_without_falling_back(self): + newest = release('2.0.0'); newest['assets'][0]['digest'] = None + with self.assertRaises(ValueError): + module.select([release('1.13.7'), newest]) + + def test_changed_url_rejected(self): + row = release('1.13.7'); row['assets'][0]['browser_download_url'] = 'https://example.org/foreign' + with self.assertRaises(ValueError): module.select([row]) + + def test_missing_or_duplicate_installer_rejected(self): + with self.assertRaises(ValueError): module.select([]) + row = release('1.13.7'); row['assets'] *= 2 + with self.assertRaises(ValueError): module.select([row]) + + +if __name__ == '__main__': + unittest.main() diff --git a/pkgbuilds/obsidian/upstream.py b/pkgbuilds/obsidian/upstream.py new file mode 100644 index 0000000..3e93edf --- /dev/null +++ b/pkgbuilds/obsidian/upstream.py @@ -0,0 +1,45 @@ +#!/usr/bin/python3 +"""Report the newest stable ARM desktop installer, ignoring mobile-only tags.""" +import json +import re +import subprocess + + +def select(releases): + if not isinstance(releases, list): + raise ValueError('Expected a GitHub release list') + candidates = [] + for release in releases: + if release.get('draft') or release.get('prerelease'): + continue + match = re.fullmatch(r'v([0-9]+(?:\.[0-9]+)*)', release.get('tag_name', '')) + if not match: + continue + version = match[1] + assets = [a for a in release.get('assets', []) + if a.get('name') == f'obsidian-{version}-arm64.tar.gz'] + if not assets: + continue + if len(assets) != 1: + raise ValueError('Duplicate desktop installer') + candidates.append((tuple(map(int, version.split('.'))), version, release, assets[0])) + if not candidates: + raise ValueError('No stable ARM desktop installer found') + _, version, release, asset = max(candidates, key=lambda row: row[0]) + digest = asset.get('digest') or '' + if not re.fullmatch(r'sha256:[0-9a-f]{64}', digest): + raise ValueError('Selected desktop installer lacks its SHA256 digest') + expected = f'https://github.com/obsidianmd/obsidian-releases/releases/download/v{version}/obsidian-{version}-arm64.tar.gz' + if asset.get('browser_download_url') != expected: + raise ValueError('Unexpected desktop installer URL') + return {'pkgver': version, 'published_at': release['published_at'], + 'sha256sums': {'aarch64': [digest[7:]]}} + + +if __name__ == '__main__': + response = subprocess.check_output([ + 'curl', '--proto', '=https', '--proto-redir', '=https', '-fsSL', + '--retry', '3', '--max-time', '60', + 'https://api.github.com/repos/obsidianmd/obsidian-releases/releases?per_page=100' + ], text=True) + print(json.dumps(select(json.loads(response)))) diff --git a/pkgbuilds/omakade/PKGBUILD b/pkgbuilds/omakade/PKGBUILD index 44bb5b7..75a909f 100644 --- a/pkgbuilds/omakade/PKGBUILD +++ b/pkgbuilds/omakade/PKGBUILD @@ -1,5 +1,5 @@ pkgname=omakade -pkgver=1.8.0 +pkgver=1.10.0 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=('d994ef5e6a537dfad9f2b3fef64ffcac014e36c46fbfafa7b25d1155b6cbca32') +sha256sums=('01a4c1aa35c51aba54f57fd0d4eab4a00a14ca2bd4d1b6163264b8ba6fed5b55') build() { cmake -S "$pkgname-$pkgver" -B build -G Ninja \ diff --git a/pkgbuilds/omarchy-billboard-generator/.omarchy/package.json b/pkgbuilds/omarchy-billboard-generator/.omarchy/package.json new file mode 100644 index 0000000..d9c0d05 --- /dev/null +++ b/pkgbuilds/omarchy-billboard-generator/.omarchy/package.json @@ -0,0 +1,12 @@ +{ + "source": "local", + "release_ring": "fast", + "min_release_age": "24h", + "upstream": { + "github": "llstrk/omarchy-billboard-generator", + "checksums": "SHA256SUMS", + "assets": { + "any": "omarchy-billboard-generator.tar.gz" + } + } +} diff --git a/pkgbuilds/omarchy-billboard-generator/PKGBUILD b/pkgbuilds/omarchy-billboard-generator/PKGBUILD new file mode 100644 index 0000000..b714a61 --- /dev/null +++ b/pkgbuilds/omarchy-billboard-generator/PKGBUILD @@ -0,0 +1,82 @@ +# Maintainer: David Heinemeier Hansson + +# Upstream ships a release archive plus a SHA256SUMS manifest, which is what +# the upstream feed in .omarchy/package.json follows. Chromium and ffmpeg are +# runtime dependencies found on PATH, never downloaded. + +pkgname=omarchy-billboard-generator +pkgver=0.2.0 +pkgrel=1 +pkgdesc='Desktop app and CLI that renders animated OMARCHY domain videos' +arch=('any') +url='https://github.com/llstrk/omarchy-billboard-generator' +# MIT covers the project's own code and Apache-2.0 and 0BSD the vendored npm +# dependencies. The Omarchy wordmark, website palettes, taglines and the ttfx +# animation runtime are Omarchy's own material carried over from omarchy-site, +# and PROVENANCE.md says which file is which. +license=('MIT' 'Apache-2.0' '0BSD' 'OFL-1.1' 'LicenseRef-Omarchy') +depends=('nodejs>=22' 'chromium' 'ffmpeg' 'xdg-utils') +makedepends=('npm') +source=("${pkgname}-${pkgver}.tar.gz::${url}/releases/download/v${pkgver}/${pkgname}.tar.gz") +sha256sums=('f499dfc1201ed3d6b8fba271496fcd4c8fc6301427f4f2fc53aab8d9c3f64f67') +# Pure JavaScript and WebAssembly; nothing here is an ELF to strip. +options=('!strip' '!debug') + +_appdir="/usr/lib/${pkgname}" + +build() { + cd "${srcdir}/${pkgname}" + # The same install the upstream release installer performs: locked production + # dependencies, no lifecycle scripts. + npm ci --omit=dev --ignore-scripts --cache "${srcdir}/npm-cache" +} + +check() { + cd "${srcdir}/${pkgname}" + # The catalogs prefer a synced snapshot in the user data directory, so point + # them at an empty one: the build must read the bundled data, not whatever + # the builder's home happens to hold. + export BILLBOARD_DATA_DIR="${srcdir}/data-home" BILLBOARD_CACHE_DIR="${srcdir}/cache-home" + # Upstream's own startup check, plus the catalogs that load the bundled data. + node bin/omarchy-billboard --help >/dev/null + node bin/omarchy-billboard --list-themes >/dev/null + node bin/omarchy-billboard --list-languages >/dev/null + node bin/omarchy-billboard --list-animations >/dev/null + node bin/omarchy-billboard-app --help >/dev/null +} + +package() { + cd "${srcdir}/${pkgname}" + + install -dm755 "${pkgdir}${_appdir}" + # Only what runs: no tests, release scripts, CI, or the curl|bash installer. + cp -a bin src app web assets data examples node_modules package.json "${pkgdir}${_appdir}/" + + # The entry points resolve their sources through the real path of the + # script, so a symlink is enough and keeps the launcher's node on PATH. + install -dm755 "${pkgdir}/usr/bin" + ln -s "${_appdir}/bin/omarchy-billboard" "${pkgdir}/usr/bin/omarchy-billboard" + ln -s "${_appdir}/bin/omarchy-billboard-app" "${pkgdir}/usr/bin/omarchy-billboard-app" + + # Written here rather than shipped as a second source: sync-upstream rewrites + # sha256sums wholesale from the release manifest, so a local file's checksum + # would not survive the first version bump. The WM class is what Chromium + # derives for this app-mode window, so the launcher's icon follows it. + install -dm755 "${pkgdir}/usr/share/applications" + cat > "${pkgdir}/usr/share/applications/${pkgname}.desktop" <<'DESKTOP' +[Desktop Entry] +Version=1.0 +Type=Application +Name=Omarchy Billboard Generator +Comment=Create animated Omarchy domain videos locally +Exec=omarchy-billboard-app +Icon=omarchy-billboard-generator +Terminal=false +Categories=AudioVideo;Video; +StartupWMClass=chrome-127.0.0.1__omarchy-billboard-Default +DESKTOP + install -Dm644 app/icon.svg "${pkgdir}/usr/share/icons/hicolor/scalable/apps/${pkgname}.svg" + + install -Dm644 -t "${pkgdir}/usr/share/doc/${pkgname}" README.md THEMES.md PROVENANCE.md + install -Dm644 -t "${pkgdir}/usr/share/licenses/${pkgname}" LICENSE PROVENANCE.md assets/fonts/*-OFL.txt +} 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-mac-boot/.omarchy/package.json b/pkgbuilds/omarchy-mac-boot/.omarchy/package.json new file mode 100644 index 0000000..9adf372 --- /dev/null +++ b/pkgbuilds/omarchy-mac-boot/.omarchy/package.json @@ -0,0 +1 @@ +{ "source": "local", "channels": ["edge"] } diff --git a/pkgbuilds/omarchy-mac-boot/PKGBUILD b/pkgbuilds/omarchy-mac-boot/PKGBUILD new file mode 100644 index 0000000..184a6b9 --- /dev/null +++ b/pkgbuilds/omarchy-mac-boot/PKGBUILD @@ -0,0 +1,62 @@ +# Maintainer: Marcelo Alcantara +# Recipe from maralcbr/omarchy-pkgs asahi-quattro pkgbuilds/omarchy-mac-boot +# (20260921-10 at 0a55baeddecf59687c32b4bd4e1dee743605c183); the payload now +# builds from packages/omarchy-mac/boot in omacom/omarchy-mac. + +pkgname=omarchy-mac-boot +# pkgver is the UTC commit date of _commit, so it sorts above the fork's +# 20260921-N. Reset pkgrel to 1 when pkgver changes; bump it to re-pin or +# rebuild on the same day. +pkgver=20260925 +pkgrel=3 +pkgdesc='Apple Silicon boot support for Omarchy: initramfs, in-place encryption, first boot and Limine activation' +arch=('aarch64') +groups=('omarchy-platform-apple-silicon') +url='https://github.com/omacom/omarchy-mac' +license=('MIT') +makedepends=('git') +provides=('omarchy-apple-boot' 'omarchy-first-boot') +conflicts=('omarchy-apple-boot' 'omarchy-first-boot') +replaces=('omarchy-apple-boot' 'omarchy-first-boot') +install=omarchy-mac-boot.install +# An exact omarchy-mac commit, never a branch. +_commit=f7ea7ecc804a133e1afe571b708458a6d71db77d +source=("omarchy-mac::git+https://github.com/omacom/omarchy-mac.git#commit=${_commit}") +sha256sums=('b14b862e8f22cb7db49afcf7e380bc92d532967cc0edef72adfda54a1eb5605b') + +prepare() { + # Tests and staging must not be able to read the surrounding desktop source. + rm -rf "$srcdir/boot" + cp -a "$srcdir/omarchy-mac/packages/omarchy-mac/boot" "$srcdir/boot" + [[ $(git -C "$srcdir/omarchy-mac" rev-parse HEAD) == "$_commit" ]] + [[ $(TZ=UTC0 git -C "$srcdir/omarchy-mac" show -s --format=%cd --date=format-local:%Y%m%d HEAD) == "$pkgver" ]] +} + +check() { + "$srcdir/boot/test/all" +} + +package() { + # Runtime-only, so the builder stages and tests the payload without the + # desktop or a boot chain. 1.39.0-2 is the first limine-mkinitcpio-hook that + # leaves a Mac's /boot to mkinitcpio until Limine is activated. + depends=('omarchy' 'limine' 'limine-mkinitcpio-hook>=1.39.0-2' 'limine-snapper-sync' 'asahi-scripts' + 'bash' 'btrfs-progs' 'coreutils' 'cpio' 'cryptsetup' 'diffutils' 'gawk' 'gnupg' 'grep' 'gum' + 'gzip' 'kbd' 'mkinitcpio' 'sed' 'systemd' 'util-linux') + + "$srcdir/boot/install" "$pkgdir" + # Once the source drops its own Plymouth fragment (omacom/omarchy-mac#544), + # the Apple drop-ins build on omarchy-settings' HOOKS baseline + # (/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf, omacom/omarchy-mac#536), and + # an older settings package leaves Plymouth out of the Mac initramfs. A + # settings version cannot say so (quattro candidates sort below stock + # releases, and omarchy pins its settings exactly), so the settings packages + # that ship the baseline provide it by name. + [[ -e $pkgdir/etc/mkinitcpio.conf.d/93-omarchy-mac-plymouth.conf ]] || + depends+=('omarchy-mkinitcpio-hooks-baseline') + printf '%s\n' "$_commit" | install -Dm644 /dev/stdin "$pkgdir/usr/share/omarchy-mac/boot-source-revision" + + # Administrator-editable: every /etc path and the initramfs helpers an image + # may rewrite. + mapfile -t backup < <(cd "$pkgdir" && find etc usr/lib/omarchy/initcpio -type f | sort) +} diff --git a/pkgbuilds/omarchy-mac-boot/README.md b/pkgbuilds/omarchy-mac-boot/README.md new file mode 100644 index 0000000..83076db --- /dev/null +++ b/pkgbuilds/omarchy-mac-boot/README.md @@ -0,0 +1,36 @@ +# omarchy-mac-boot + +Apple Silicon boot support for Omarchy: the Mac mkinitcpio drop-ins and initcpio hooks, in-place LUKS conversion in the initramfs, vendor firmware in early boot, first boot of a Mac image, the Limine activation gate and the boot check. The source is `packages/omarchy-mac/boot/` in omacom/omarchy-mac, with its own tests. The recipe pins an exact omarchy-mac commit, copies that directory away from the surrounding desktop tree in `prepare()`, runs its `test/all` in `check()` and stages the package with its `install` script. The recipe itself holds only metadata, `backup=` and the pacman scriptlet. + +It follows the fork recipe in maralcbr/omarchy-pkgs (`asahi-quattro`, `pkgbuilds/omarchy-mac-boot` at 20260921-10), which carried the payload as files in the recipe. + +## Scope + +The package is aarch64-only and published to edge only. It widens to rc and stable only after M1 and M2 cold-boot qualification. It carries the Apple platform tag, `groups=('omarchy-platform-apple-silicon')`, which omarchy-settings' pacman platform guard uses to keep it off other machines once that guard ships (omacom/omarchy-mac#539, `docs/platform-guard.md`). Its only provides are the retired Apple-only names, and nothing generic depends on it or on them, so nothing generic can pull it onto non-Apple aarch64 machines. Its one Apple-only dependency, `asahi-scripts`, comes from the asahi-alarm repository that only the Apple profile configures. + +It requires `limine-mkinitcpio-hook` 1.39.0-2 or newer: that is the first build whose hooks leave a Mac's `/boot` to mkinitcpio until Limine is activated. With an older hook, Limine's kernel hook replaces mkinitcpio's by name, and this package's `limine-ready` gate stops it on a Mac that still boots GRUB, so a kernel update would never reach `/boot`. + +## HOOKS baseline + +From the source that drops the boot package's own Plymouth fragment (omacom/omarchy-mac#544), the Apple drop-ins build on omarchy-settings' HOOKS baseline, `/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf` (omacom/omarchy-mac#536). With an older settings package the Mac initramfs loses Plymouth at the passphrase prompt. A settings version cannot express this: quattro candidate builds sort below stock releases, and `omarchy` pins its settings version exactly. So when the staged payload has no `93-omarchy-mac-plymouth.conf`, `package()` adds a dependency on the name `omarchy-mkinitcpio-hooks-baseline`. Every settings package that ships the baseline on aarch64 (`omarchy-settings`, `omarchy-settings-dev`, candidate builds) must `provide` that name. Until one does, such a build of this package cannot be installed: it fails outright rather than silently booting without Plymouth. + +## Publish order + +A new pin publishes on merge, so check what the pinned source needs first: + +- **Settings baseline.** A pin that includes omacom/omarchy-mac#544 (no `93-omarchy-mac-plymouth.conf`) needs omarchy-settings with the HOOKS baseline (omacom/omarchy-mac#542) published on aarch64, and providing `omarchy-mkinitcpio-hooks-baseline`. Publish that first; otherwise this build cannot be installed. +- **Update verification.** A pin that includes omacom/omarchy-mac#543 (`/usr/lib/omarchy/mac-boot/update-verify`) must publish before any runtime that carries #543. Otherwise that runtime blocks every update on Macs whose `omarchy-mac-boot` predates it. + +## Transition + +- It provides, conflicts with and replaces `omarchy-apple-boot` and `omarchy-first-boot`. The scriptlet moves a pending `omarchy-first-boot` marker to `omarchy-mac-first-boot`, drops the replaced unit's dangling enable link and points at a customised `90-omarchy-asahi.conf.pacsave`. +- `pkgver` is the UTC commit date of the pin, so any pin from `20260925` on upgrades the fork's `20260921-10` on mx-mac Macs. The files the fork shipped that the source no longer does (the image finalize tools, the upstream ARM repository key and the GRUB snapshot-menu hook) are removed by that upgrade. +- `backup=` covers every `/etc` path and `/usr/lib/omarchy/initcpio`. It includes `/etc/default/update-m1n1`, which pins update-m1n1's device-tree order to the C locale. On a Mac that already has its own unowned copy, pacman keeps it and installs the shipped one as `.pacnew`. + +## Updates + +Updates are reviewed pins, never a branch: + +1. Set `_commit` to the full omarchy-mac SHA and `pkgver` to its UTC commit date (`TZ=UTC0 git show -s --format=%cd --date=format-local:%Y%m%d `); `prepare()` checks both. +2. Reset `pkgrel` to 1 when `pkgver` changes; bump it for a second pin on the same date or a rebuild. +3. Refresh `sha256sums` with `makepkg -g`. diff --git a/pkgbuilds/omarchy-mac-boot/omarchy-mac-boot.install b/pkgbuilds/omarchy-mac-boot/omarchy-mac-boot.install new file mode 100644 index 0000000..762cddf --- /dev/null +++ b/pkgbuilds/omarchy-mac-boot/omarchy-mac-boot.install @@ -0,0 +1,48 @@ +_omarchy_mac_boot_asahi_pacsave_notice() { + local pacsave=/etc/mkinitcpio.conf.d/90-omarchy-asahi.conf.pacsave + local shipped=/etc/mkinitcpio.conf.d/90-omarchy-mac.conf + + [[ -f $pacsave && -f $shipped ]] || return 0 + cmp -s "$pacsave" "$shipped" && return 0 + + cat < omarchy-mac-boot: a customized 90-omarchy-asahi.conf was preserved as: + $pacsave + The active mkinitcpio drop-in is now: + $shipped + Merge any remaining local changes into the new file. +EOF +} + +_omarchy_mac_boot_migrate_legacy_first_boot() { + local legacy=/var/lib/omarchy/first-boot/pending + local successor=/var/lib/omarchy/mac-first-boot/pending + + [[ -e $legacy ]] || return 0 + install -Dm644 /dev/null "$successor" + rm -f "$legacy" + systemctl --no-reload enable omarchy-mac-first-boot.service 2>/dev/null || { + install -d /etc/systemd/system/multi-user.target.wants + ln -sfr /usr/lib/systemd/system/omarchy-mac-first-boot.service \ + /etc/systemd/system/multi-user.target.wants/omarchy-mac-first-boot.service + } +} + +post_install() { + _omarchy_mac_boot_asahi_pacsave_notice + _omarchy_mac_boot_migrate_legacy_first_boot + _omarchy_mac_boot_drop_legacy_wants +} + +_omarchy_mac_boot_drop_legacy_wants() { + # The replaced omarchy-first-boot left its preset-enabled link behind. + local link=/etc/systemd/system/multi-user.target.wants/omarchy-first-boot.service + [[ -L $link && ! -e $link ]] && rm -f "$link" + return 0 +} + +post_upgrade() { + _omarchy_mac_boot_asahi_pacsave_notice + _omarchy_mac_boot_migrate_legacy_first_boot + _omarchy_mac_boot_drop_legacy_wants +} diff --git a/pkgbuilds/omarchy-mac/.omarchy/package.json b/pkgbuilds/omarchy-mac/.omarchy/package.json new file mode 100644 index 0000000..9adf372 --- /dev/null +++ b/pkgbuilds/omarchy-mac/.omarchy/package.json @@ -0,0 +1 @@ +{ "source": "local", "channels": ["edge"] } diff --git a/pkgbuilds/omarchy-mac/PKGBUILD b/pkgbuilds/omarchy-mac/PKGBUILD new file mode 100644 index 0000000..ec9b5e4 --- /dev/null +++ b/pkgbuilds/omarchy-mac/PKGBUILD @@ -0,0 +1,42 @@ +# Maintainer: Scott Jones +# Ported from omarchy-mac/omarchy-pkgs-aarch64 pkgbuilds/omarchy-mac at 02ed7b250f7edcf7c5f748acaf7b21ef0a764e9d. + +pkgname=omarchy-mac +# pkgver matches packages/omarchy-mac/version at _commit. Bump pkgrel to re-pin +# or rebuild the same add-on version; reset it to 1 when pkgver increases. +pkgver=0.1.0 +pkgrel=5 +pkgdesc='Apple Silicon configuration and support services for Omarchy' +arch=('aarch64') +url='https://github.com/omacom/omarchy-mac' +license=('MIT') +makedepends=('git' 'findutils') +checkdepends=('diffutils' 'python' 'systemd') +# An exact quattro-upstream commit, never a branch. +_commit=b4a79d83d1144c4de90b29a2d8b4090090d7a9d8 +source=("omarchy-mac::git+https://github.com/omacom/omarchy-mac.git#commit=${_commit}") +sha256sums=('93676ce29791a29efe3b098fd8d129fa2248cd32d04253d9520a5b4c0697be6f') + +prepare() { + # Tests and staging must not be able to read the surrounding desktop source. + rm -rf "$srcdir/addon" + cp -a "$srcdir/omarchy-mac/packages/omarchy-mac" "$srcdir/addon" + [[ $(<"$srcdir/addon/version") == "$pkgver" ]] + [[ $(git -C "$srcdir/omarchy-mac" rev-parse HEAD) == "$_commit" ]] +} + +check() { + "$srcdir/addon/test/all" +} + +package() { + # Runtime-only: the builder does not need the Omarchy desktop to stage or + # test the add-on. + depends=('omarchy' 'bash' 'coreutils' 'diffutils' 'grep' 'sed' 'gawk' 'systemd' 'pciutils' 'kmod' + 'networkmanager' 'iwd' 'python' 'pipewire' 'pipewire-pulse' 'libpulse' 'wireplumber') + + "$srcdir/addon/install" "$pkgdir" + install -Dm644 "$srcdir/addon/README.md" "$pkgdir/usr/share/doc/$pkgname/README.md" + install -Dm644 "$srcdir/addon/ORIGINS.md" "$pkgdir/usr/share/doc/$pkgname/ORIGINS.md" + printf '%s\n' "$_commit" >"$pkgdir/usr/share/omarchy-mac/source-revision" +} diff --git a/pkgbuilds/omarchy-mac/README.md b/pkgbuilds/omarchy-mac/README.md new file mode 100644 index 0000000..49aa1e6 --- /dev/null +++ b/pkgbuilds/omarchy-mac/README.md @@ -0,0 +1,23 @@ +# omarchy-mac + +Apple Silicon runtime support for Omarchy: Wi-Fi resume recovery, the headset microphone mapping, the notch modprobe default and the NetworkManager Wi-Fi backend. The source is `packages/omarchy-mac/` in omacom/omarchy-mac, with its own version and tests. The recipe pins an exact `quattro-upstream` commit and packages only that directory; `prepare()` copies it away from the surrounding desktop tree before `check()` runs its tests. + +Ported from Scott Jones's recipe in omarchy-mac/omarchy-pkgs-aarch64 (`pkgbuilds/omarchy-mac` at `02ed7b250f7edcf7c5f748acaf7b21ef0a764e9d`). It supersedes the unpublished `omarchy-settings-asahi` recipe. + +## Scope + +The package is aarch64-only and published to edge only. It widens to rc and stable only after M1 and M2 cold-boot qualification. It depends on the generic `omarchy` package and has no `provides`, so nothing generic can pull it onto non-Apple aarch64 machines. It ships no kernel, boot, installer or trust configuration; those belong to `omarchy-mac-boot`. + +Installing the package enables no services, but its vendor configuration applies on the next service start or module load: the iwd Wi-Fi backend for NetworkManager, the `appledrm` notch option and the WirePlumber headset microphone policy. `omarchy-mac-setup-system` and `omarchy-mac-setup-user` enable the Wi-Fi resume and microphone units, and they exit unless `omarchy-hw-apple-silicon` reports Apple Silicon. Stock Omarchy does not yet ship that detector or call these entrypoints, so on a stock install the units stay disabled until the platform detector and profile work lands. + +Runtime dependencies are declared in `package()`, so the builder stages and tests the add-on without installing the desktop. + +## Updates + +Updates are reviewed pins, never a branch. To release a change to the add-on: + +1. Set `_commit` to the full `quattro-upstream` SHA and `pkgver` to `packages/omarchy-mac/version` at that commit. +2. Reset `pkgrel` to 1 when `pkgver` increases; bump it to re-pin or rebuild the same version. +3. Refresh `sha256sums` with `makepkg -g`. + +Desktop-only commits on `quattro-upstream` do not need a new pin. `0.1.0-5` sorts above the `0.1.0-4.` candidates the collaboration builder produced for test images. diff --git a/pkgbuilds/omarchy-meeting-recorder-bin/.omarchy/package.json b/pkgbuilds/omarchy-meeting-recorder-bin/.omarchy/package.json new file mode 100644 index 0000000..743b87f --- /dev/null +++ b/pkgbuilds/omarchy-meeting-recorder-bin/.omarchy/package.json @@ -0,0 +1,11 @@ +{ + "source": "local", + "min_release_age": "24h", + "upstream": { + "github": "jankeesvw/omarchy-meeting-recorder", + "digests": true, + "assets": { + "x86_64": "omarchy-meeting-recorder-{pkgver}-x86_64-linux.tar.gz" + } + } +} diff --git a/pkgbuilds/omarchy-meeting-recorder-bin/PKGBUILD b/pkgbuilds/omarchy-meeting-recorder-bin/PKGBUILD new file mode 100644 index 0000000..d19ecb9 --- /dev/null +++ b/pkgbuilds/omarchy-meeting-recorder-bin/PKGBUILD @@ -0,0 +1,31 @@ +# Maintainer: Jankees van Woezik + +pkgname=omarchy-meeting-recorder-bin +_name=omarchy-meeting-recorder +pkgver=1.0.2 +pkgrel=1 +pkgdesc="Meeting Recorder for Omarchy: record the mic and the computer audio, transcribed on your own machine with speakers, chapters and a player" +arch=('x86_64') +url="https://github.com/jankeesvw/omarchy-meeting-recorder" +license=('MIT') +depends=('gtk4' 'libadwaita' 'glib2' 'pango' 'cairo' 'graphene' 'libgcc' 'libstdc++' 'glibc' 'libpulse' 'ffmpeg') +optdepends=('quickshell: the bar widget, part of every Omarchy install' + 'perl-file-mimeinfo: open .meeting-recorder files with xdg-open, not only from Nautilus') +provides=("$_name") +conflicts=("$_name") +install="$_name.install" +options=('!debug') +source_x86_64=("$url/releases/download/v$pkgver/$_name-$pkgver-x86_64-linux.tar.gz") +sha256sums_x86_64=('78746ecc90366f12f308801637b98fc5e5b844bb28ba413e50ed3efe5c24079f') + +package() { + cd "$_name-$pkgver" + install -Dm755 "$_name" "$pkgdir/usr/bin/$_name" + install -Dm644 "data/$_name.desktop" "$pkgdir/usr/share/applications/$_name.desktop" + install -Dm644 "data/$_name.xml" "$pkgdir/usr/share/mime/packages/$_name.xml" + # The bar widget, to link into ~/.config/omarchy/plugins (see the install message). + install -Dm644 plugin/manifest.json "$pkgdir/usr/share/$_name/plugin/manifest.json" + install -Dm644 plugin/Widget.qml "$pkgdir/usr/share/$_name/plugin/Widget.qml" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" +} diff --git a/pkgbuilds/omarchy-meeting-recorder-bin/omarchy-meeting-recorder.install b/pkgbuilds/omarchy-meeting-recorder-bin/omarchy-meeting-recorder.install new file mode 100644 index 0000000..1121fc4 --- /dev/null +++ b/pkgbuilds/omarchy-meeting-recorder-bin/omarchy-meeting-recorder.install @@ -0,0 +1,22 @@ +post_install() { + cat <<'MSG' +Meeting Recorder is installed; open it from the launcher. + +The first time you open it, it offers to put its widget in the bar +(a live waveform while you record). + +Optional, per user: + + A floating window, in ~/.config/hypr (Omarchy Quattro, Lua): + o.window("^com\\.jankeesvw\\.OmarchyMeetingRecorder$", { float = true }) + o.window("^com\\.jankeesvw\\.OmarchyMeetingRecorder$", { size = { 480, 700 } }) + o.window("^com\\.jankeesvw\\.OmarchyMeetingRecorder$", { center = true }) + +The speech model (about 1.6 GB) is downloaded on first use, or taken from +voxtype when it already has it. +MSG +} + +post_upgrade() { + : +} diff --git a/pkgbuilds/omarchy-nvim/PKGBUILD b/pkgbuilds/omarchy-nvim/PKGBUILD index e08f8e5..fb6d754 100644 --- a/pkgbuilds/omarchy-nvim/PKGBUILD +++ b/pkgbuilds/omarchy-nvim/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Ryan Hughes pkgname=omarchy-nvim -pkgver=2026.8.13 +pkgver=2026.9.21 pkgrel=2 pkgdesc="Pre-built LazyVim configuration with cached plugins" arch=('any') diff --git a/pkgbuilds/omarchy-nvim/lua/plugins/all-themes.lua b/pkgbuilds/omarchy-nvim/lua/plugins/all-themes.lua index 3bbf7a4..0897b96 100644 --- a/pkgbuilds/omarchy-nvim/lua/plugins/all-themes.lua +++ b/pkgbuilds/omarchy-nvim/lua/plugins/all-themes.lua @@ -77,7 +77,7 @@ return { priority = 1000, }, { - "gthelding/monokai-pro.nvim", + "loctvl842/monokai-pro.nvim", lazy = true, priority = 1000, }, diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 6c7f8d9..4023b89 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -65,6 +65,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 0d063f3..5eb5ca8 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=1 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 @@ -74,6 +74,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' ) @@ -124,7 +125,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-steam-fex/.omarchy/package.json b/pkgbuilds/omarchy-steam-fex/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/omarchy-steam-fex/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/omarchy-steam-fex/LICENSE b/pkgbuilds/omarchy-steam-fex/LICENSE new file mode 100644 index 0000000..f12cfa7 --- /dev/null +++ b/pkgbuilds/omarchy-steam-fex/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pkgbuilds/omarchy-steam-fex/PKGBUILD b/pkgbuilds/omarchy-steam-fex/PKGBUILD new file mode 100644 index 0000000..02d2e9d --- /dev/null +++ b/pkgbuilds/omarchy-steam-fex/PKGBUILD @@ -0,0 +1,25 @@ +pkgname=omarchy-steam-fex +pkgver=1.0.0 +pkgrel=3 +pkgdesc='Steam launcher with login workarounds for Apple Silicon using muvm and FEX' +arch=('aarch64') +url='https://github.com/omacom/omarchy-pkgs/tree/master/pkgbuilds/omarchy-steam-fex' +license=('MIT') +checkdepends=('python') +source=('omarchy-launch-steam' 'LICENSE' 'test-launcher.py') +sha256sums=('37ad2e8a863e90c2b3248f22d93b548c6070f396f631ad39cefc4745478515b4' + '717ba1949502290f8e47688ae2e323acd06c8ca47aec9f7596b15f678c1af4a2' + 'fbab0f88ecdf3238bfb95a1523eef7de2ded2928c91c90e4e06435696dd86cdb') + +check() { + python test-launcher.py +} + +package() { + # These are runtime-only dependencies; the Asahi stack is not needed to + # assemble or test the scripts in a standard Arch Linux ARM builder. + depends=('bash' 'coreutils' 'python' 'steam' 'muvm' 'FEX-Emu') + + install -Dm755 omarchy-launch-steam "$pkgdir/usr/bin/omarchy-launch-steam" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} diff --git a/pkgbuilds/omarchy-steam-fex/README.md b/pkgbuilds/omarchy-steam-fex/README.md new file mode 100644 index 0000000..9760b17 --- /dev/null +++ b/pkgbuilds/omarchy-steam-fex/README.md @@ -0,0 +1,17 @@ +# Steam launcher for Apple Silicon + +`omarchy-steam-fex` provides `omarchy-launch-steam` for the Asahi Linux `steam`, `muvm`, and `FEX-Emu` stack. Those runtime packages come from `asahi-alarm`; `FEX-Emu` provides `FEXBash`. The package is restricted to aarch64 and assumes that stack's `~/.local/share/fex-steam/steam-launcher/bin_steam.sh` layout. + +The launcher runs Steam through `muvm` and `FEXBash` with the CEF occlusion workaround. Once Steam's client files are present, it also disables bootstrap verification and repair and patches the Steam UI network initialization block that can leave login waiting indefinitely. Initial bootstrap keeps the normal bootstrap flags. If the FEX launcher is unavailable, it falls back to `steam`. + +`omarchy-launch-steam --prepare` creates the current user's desktop override with `Exec=omarchy-launch-steam %U` if it is missing, and applies the same UI patch. Existing overrides and symlinks are preserved on preparation and launch. If an existing override uses a different command, edit its `Exec` entry to use `omarchy-launch-steam %U` when you want this launcher. Each matching chunk is backed up as `.omarchy-bak` before its first patch; existing backups are preserved. The regex matches the original network initialization block, so a patched block is not changed again. An unrelated connected-state assignment elsewhere in the chunk does not suppress the fix. The regex depends on Valve's client code and may need updating when that code changes. + +Only the launcher and license are installed. Steam's system desktop entry and downloaded client files remain outside this package's ownership; preparation runs as the desktop user, never in a package installation hook. A downstream Omarchy package that already owns the launcher must release that path before this package is installed, or in the same upgrade transaction. + +Runtime dependencies are declared in `package()` so assembling the scripts does not require the Asahi stack in the build container. They remain required dependencies in the resulting package. `check()` runs the offline launcher tests using temporary homes and mocked Steam/muvm commands. To run them directly: + +```sh +python pkgbuilds/omarchy-steam-fex/test-launcher.py +``` + +The launcher was extracted from [omarchy-mx-mac commit 5e8e1188](https://github.com/scottjones/omarchy-mx-mac/commit/5e8e1188e39fae70cf4f7bda1a1d85d66eccae51), credited to Scott Jones, dl-alexandre, and Santeri Hernejรคrvi. The extraction replaces `omarchy-cmd-present` with `command -v`, removing the dependency on Omarchy itself. Subsequent fixes preserve user desktop overrides and avoid false positives when checking whether the network block needs patching. diff --git a/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam b/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam new file mode 100755 index 0000000..c46fdf7 --- /dev/null +++ b/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam @@ -0,0 +1,113 @@ +#!/bin/bash + +# omarchy:summary=Launch Steam, applying Asahi muvm login workarounds on Apple Silicon +# omarchy:group=launch +# omarchy:args=[--prepare|steam-args...] + +set -euo pipefail + +steam_root="$HOME/.local/share/Steam" + +write_steam_desktop() { + local app_dir="$HOME/.local/share/applications" + # This is a user-owned override. Create it once without replacing existing + # customizations, including a symlink managed by the user. + [[ -e $app_dir/steam.desktop || -L $app_dir/steam.desktop ]] && return 0 + mkdir -p "$app_dir" + cat >"$app_dir/steam.desktop" <<'EOF' +[Desktop Entry] +Name=Steam +Comment=Application for managing and playing games on Steam +Exec=omarchy-launch-steam %U +Icon=steam +Terminal=false +Type=Application +Categories=Network;FileTransfer;Game; +MimeType=x-scheme-handler/steam;x-scheme-handler/steamlink; +EOF +} + +steam_client_ready() { + [[ -d $steam_root/steamui ]] && compgen -G "$steam_root/*/steamui.so" >/dev/null +} + +launch_fex_steam() { + local launcher="$1" + shift + + exec muvm -- FEXBash -c 'exec "$@"' omarchy-steam "$launcher" "$@" +} + +patch_steam_ui() { + local steamui="$steam_root/steamui" + [[ -d $steamui ]] || return 0 + + python3 - "$steamui" <<'PY' +import pathlib +import re +import sys + +steamui = pathlib.Path(sys.argv[1]) +pattern = re.compile( + r"const t=\(0,(\w+)\.(\w+)\)\(\"System\.Network\.RegisterForDeviceChanges\"\);" + r"t&&SteamClient\.System\.Network\.RegisterForDeviceChanges\(this\.OnNetworkDevicesChanged\)," + r"\(0,\1\.\2\)\(\"System\.Network\.GetProxyInfo\"\)&&SteamClient\.System\.Network\.GetProxyInfo\(\)\.then\(e=>this\.m_proxyInfo=e\)," + r"\(0,\1\.\2\)\(\"System\.Network\.RegisterForConnectivityTestChanges\"\)&&SteamClient\.System\.Network\.RegisterForConnectivityTestChanges\(this\.OnConnectivityTestStateChanged\)," + r"t\|\|\(this\.m_bIsAwaitingInitialNetworkState=!1\)" +) +replacement = ( + r'const t=(0,\1.\2)("System.Network.RegisterForDeviceChanges")' + r'&&!!(SteamClient.System&&SteamClient.System.Network&&"function"==typeof SteamClient.System.Network.RegisterForDeviceChanges);' + r'try{t&&SteamClient.System.Network.RegisterForDeviceChanges(this.OnNetworkDevicesChanged)}catch(e){}' + r'try{(0,\1.\2)("System.Network.GetProxyInfo")&&SteamClient.System.Network.GetProxyInfo().then(e=>this.m_proxyInfo=e)}catch(e){}' + r'try{(0,\1.\2)("System.Network.RegisterForConnectivityTestChanges")&&SteamClient.System.Network.RegisterForConnectivityTestChanges(this.OnConnectivityTestStateChanged)}catch(e){}' + r'this.m_bIsAwaitingInitialNetworkState=!1,this.m_bIsConnectedToANetwork=!0' +) + +for path in sorted(steamui.glob("chunk~*.js")): + text = path.read_text(errors="replace") + # The exact original block no longer matches after replacement. A generic + # connected-state assignment elsewhere in the chunk is not a patch marker. + updated, count = pattern.subn(replacement, text, count=1) + if count != 1: + continue + backup = path.with_suffix(path.suffix + ".omarchy-bak") + if not backup.exists(): + backup.write_text(text) + path.write_text(updated) +PY +} + +prepare_asahi() { + [[ $(uname -m) == aarch64 ]] || return 0 + write_steam_desktop + patch_steam_ui +} + +if [[ ${1:-} == --prepare ]]; then + prepare_asahi + exit 0 +fi + +if [[ $(uname -m) == aarch64 ]] && command -v muvm >/dev/null && command -v FEXBash >/dev/null; then + launcher="$HOME/.local/share/fex-steam/steam-launcher/bin_steam.sh" + + if [[ -f $launcher ]]; then + steam_args=(-cef-force-occlusion) + if steam_client_ready; then + prepare_asahi + steam_args+=( + -noverifyfiles + -nobootstrapupdate + -skipinitialbootstrap + -norepairfiles + ) + else + write_steam_desktop + fi + + launch_fex_steam "$launcher" "${steam_args[@]}" "$@" + fi +fi + +exec steam "$@" diff --git a/pkgbuilds/omarchy-steam-fex/test-launcher.py b/pkgbuilds/omarchy-steam-fex/test-launcher.py new file mode 100644 index 0000000..02e7c65 --- /dev/null +++ b/pkgbuilds/omarchy-steam-fex/test-launcher.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +"""Offline launcher tests: fake Steam/muvm/FEX, real Bash and Python, temporary HOME.""" + +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest + + +LAUNCHER = Path(os.environ.get( + 'STEAM_LAUNCHER_TEST_SCRIPT', + Path(__file__).with_name('omarchy-launch-steam'), +)) + +# A representative minified Steam network initialization block, including +# surrounding code that must survive the patch. Deliberately not a regex. +ORIGINAL = ( + 'before();const t=(0,Ab.cd)("System.Network.RegisterForDeviceChanges");' + 't&&SteamClient.System.Network.RegisterForDeviceChanges(this.OnNetworkDevicesChanged),' + '(0,Ab.cd)("System.Network.GetProxyInfo")&&SteamClient.System.Network.GetProxyInfo().then(e=>this.m_proxyInfo=e),' + '(0,Ab.cd)("System.Network.RegisterForConnectivityTestChanges")&&SteamClient.System.Network.RegisterForConnectivityTestChanges(this.OnConnectivityTestStateChanged),' + 't||(this.m_bIsAwaitingInitialNetworkState=!1);after();' +) +SKIP_BOOTSTRAP = ['-noverifyfiles', '-nobootstrapupdate', '-skipinitialbootstrap', '-norepairfiles'] + +MOCK = ''' +import json, os +from pathlib import Path +import shutil, sys +name = Path(sys.argv[0]).name +if name == 'uname': + print(os.environ.get('TEST_ARCH', 'aarch64')) + sys.exit(0) +with open(os.environ['TEST_CALLS'], 'a') as log: + log.write(json.dumps([name, *sys.argv[1:]]) + '\\n') +if name == 'muvm': + assert sys.argv[1:3] == ['--', 'FEXBash'] + os.execv(shutil.which('FEXBash'), sys.argv[2:]) +if name == 'FEXBash': + os.execv('/bin/bash', ['/bin/bash', *sys.argv[1:]]) +sys.exit(int(os.environ.get('TEST_EXIT', '0'))) +''' + + +class SteamLauncherTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix='steam-fex-test-') + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.user_home = self.root / 'home with spaces' + self.user_home.mkdir() + self.tools = self.root / 'tools' + self.tools.mkdir() + self.calls_path = self.root / 'calls.jsonl' + self.env = dict(os.environ, HOME=str(self.user_home), PATH=str(self.tools), + TEST_CALLS=str(self.calls_path), TEST_ARCH='aarch64', TEST_EXIT='0') + for name in ['mkdir', 'cat', 'python3']: + (self.tools / name).symlink_to(shutil.which(name)) + for name in ['uname', 'muvm', 'FEXBash', 'steam']: + self.mock(self.tools / name) + self.fex_launcher = self.user_home / '.local/share/fex-steam/steam-launcher/bin_steam.sh' + self.mock(self.fex_launcher) + self.steam_root = self.user_home / '.local/share/Steam' + self.ui = self.steam_root / 'steamui' + self.desktop = self.user_home / '.local/share/applications/steam.desktop' + + def mock(self, path): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(f'#!{sys.executable}\n' + MOCK) + path.chmod(0o755) + + def run_launcher(self, *args, expected=0): + result = subprocess.run(['/bin/bash', str(LAUNCHER), *args], env=self.env, + capture_output=True, text=True, timeout=10) + self.assertEqual(result.returncode, expected, result.stderr) + return [json.loads(line) for line in self.calls_path.read_text().splitlines()] if self.calls_path.exists() else [] + + def chunk(self, content=ORIGINAL, name='chunk~network.js'): + self.ui.mkdir(parents=True, exist_ok=True) + path = self.ui / name + path.write_text(content) + return path + + def client_ready(self): + self.ui.mkdir(parents=True, exist_ok=True) + binary = self.steam_root / 'ubuntu12_64/steamui.so' + binary.parent.mkdir(parents=True, exist_ok=True) + binary.touch() + + def assert_desktop(self): + text = self.desktop.read_text() + self.assertIn('\nExec=omarchy-launch-steam %U\n', text) + self.assertIn('x-scheme-handler/steam;x-scheme-handler/steamlink;', text) + + def assert_fex(self, calls, flags, user_args): + args = [str(self.fex_launcher), *flags, *user_args] + self.assertEqual(calls, [ + ['muvm', '--', 'FEXBash', '-c', 'exec "$@"', 'omarchy-steam', *args], + ['FEXBash', '-c', 'exec "$@"', 'omarchy-steam', *args], + ['bin_steam.sh', *flags, *user_args], + ]) + + def test_prepare_without_client_only_writes_user_desktop(self): + self.assertEqual(self.run_launcher('--prepare'), []) + self.assert_desktop() + self.assertFalse(self.steam_root.exists()) + + def test_prepare_patches_matching_chunks_and_preserves_original(self): + for identifier in ['Ab.cd', '_A2.x9']: + with self.subTest(identifier=identifier): + original = ORIGINAL.replace('Ab.cd', identifier) + path = self.chunk(original, f'chunk~{identifier}.js') + self.assertEqual(self.run_launcher('--prepare'), []) + patched = path.read_text() + self.assertTrue(patched.startswith('before();')) + self.assertTrue(patched.endswith(';after();')) + self.assertIn(f'const t=(0,{identifier})("System.Network.RegisterForDeviceChanges")&&!!', patched) + self.assertEqual(patched.count('catch(e){}'), 3) + self.assertIn('this.m_bIsAwaitingInitialNetworkState=!1,this.m_bIsConnectedToANetwork=!0', patched) + self.assertEqual(path.with_suffix('.js.omarchy-bak').read_text(), original) + self.assert_desktop() + + def test_repeated_prepare_is_idempotent_and_keeps_first_backup(self): + path = self.chunk() + self.run_launcher('--prepare') + patched, modified = path.read_bytes(), path.stat().st_mtime_ns + self.run_launcher('--prepare') + self.assertEqual(path.read_bytes(), patched) + self.assertEqual(path.stat().st_mtime_ns, modified) + path.write_text(ORIGINAL.replace('Ab.cd', 'New.api')) + self.run_launcher('--prepare') + self.assertIn('(0,New.api)', path.read_text()) + self.assertEqual(path.with_suffix('.js.omarchy-bak').read_text(), ORIGINAL) + + def test_unmatched_code_and_other_filenames_are_untouched(self): + for name, content in [('chunk~changed.js', 'otherNetworkCode();'), ('other.js', ORIGINAL)]: + path = self.chunk(content, name) + self.run_launcher('--prepare') + self.assertEqual(path.read_text(), content) + self.assertFalse(path.with_suffix('.js.omarchy-bak').exists()) + + def test_unrelated_connected_state_does_not_skip_original_block(self): + original = 'other.m_bIsConnectedToANetwork=!0;' + ORIGINAL + path = self.chunk(original) + self.run_launcher('--prepare') + self.assertIn('catch(e){}', path.read_text()) + self.assertTrue(path.read_text().startswith('other.m_bIsConnectedToANetwork=!0;')) + self.assertEqual(path.with_suffix('.js.omarchy-bak').read_text(), original) + patched, modified = path.read_bytes(), path.stat().st_mtime_ns + self.run_launcher('--prepare') + self.assertEqual(path.read_bytes(), patched) + self.assertEqual(path.stat().st_mtime_ns, modified) + + def test_existing_desktop_override_is_preserved_during_prepare_and_launch(self): + self.client_ready() + self.chunk() + self.desktop.parent.mkdir(parents=True) + customized = '[Desktop Entry]\nName=My Steam\nExec=my-steam-wrapper %U\nX-User-Setting=keep\n' + self.desktop.write_text(customized) + modified = self.desktop.stat().st_mtime_ns + for args in [('--prepare',), ()]: + self.run_launcher(*args) + self.assertEqual(self.desktop.read_text(), customized) + self.assertEqual(self.desktop.stat().st_mtime_ns, modified) + + def test_existing_desktop_symlink_is_preserved_even_with_missing_target(self): + self.desktop.parent.mkdir(parents=True) + target = self.user_home / 'custom-steam.desktop' + self.desktop.symlink_to(target) + self.run_launcher('--prepare') + self.assertTrue(self.desktop.is_symlink()) + self.assertFalse(target.exists()) + + def test_repeated_prepare_does_not_rewrite_created_desktop_override(self): + self.run_launcher('--prepare') + original, modified = self.desktop.read_bytes(), self.desktop.stat().st_mtime_ns + self.run_launcher('--prepare') + self.assertEqual(self.desktop.read_bytes(), original) + self.assertEqual(self.desktop.stat().st_mtime_ns, modified) + + def test_only_first_matching_block_is_patched(self): + path = self.chunk(ORIGINAL + ORIGINAL) + self.run_launcher('--prepare') + self.assertEqual(path.read_text().count('m_bIsConnectedToANetwork=!0'), 1) + self.assertIn(ORIGINAL, path.read_text()) + + def test_initial_launch_keeps_bootstrap_enabled_and_preserves_arguments(self): + args = ['steam://rungameid/123', 'argument with spaces', '$(touch unwanted)', ''] + calls = self.run_launcher(*args) + self.assert_fex(calls, ['-cef-force-occlusion'], args) + self.assert_desktop() + self.assertFalse(self.steam_root.exists()) + + def test_ui_directory_alone_does_not_disable_bootstrap(self): + path = self.chunk() + self.assert_fex(self.run_launcher(), ['-cef-force-occlusion'], []) + self.assertEqual(path.read_text(), ORIGINAL) + + def test_ready_launch_patches_and_disables_bootstrap(self): + path = self.chunk() + self.client_ready() + args = ['steam://open/main'] + self.assert_fex(self.run_launcher(*args), ['-cef-force-occlusion', *SKIP_BOOTSTRAP], args) + self.assertIn('m_bIsConnectedToANetwork=!0', path.read_text()) + self.assert_desktop() + + def test_missing_fex_components_fall_back_without_modifying_user_files(self): + for missing in [self.tools / 'muvm', self.tools / 'FEXBash', self.fex_launcher]: + with self.subTest(missing=missing.name): + missing.unlink() + self.env['TEST_EXIT'] = '23' + self.assertEqual(self.run_launcher('arg with spaces', expected=23), [['steam', 'arg with spaces']]) + self.assertFalse(self.desktop.exists()) + self.calls_path.unlink() + self.mock(missing) + + def test_x86_prepare_is_noop_and_launch_falls_back(self): + self.env['TEST_ARCH'] = 'x86_64' + path = self.chunk() + self.assertEqual(self.run_launcher('--prepare'), []) + self.assertFalse(self.desktop.exists()) + self.assertEqual(path.read_text(), ORIGINAL) + self.assertEqual(self.run_launcher('steam://open/main'), [['steam', 'steam://open/main']]) + + def test_fex_exit_status_is_preserved(self): + self.env['TEST_EXIT'] = '29' + self.assert_fex(self.run_launcher(expected=29), ['-cef-force-occlusion'], []) + + +if __name__ == '__main__': + unittest.main() diff --git a/pkgbuilds/omarchy-task-manager/.omarchy/package.json b/pkgbuilds/omarchy-task-manager/.omarchy/package.json new file mode 100644 index 0000000..df4a9fb --- /dev/null +++ b/pkgbuilds/omarchy-task-manager/.omarchy/package.json @@ -0,0 +1,11 @@ +{ + "source": "local", + "upstream": { + "watch": { + "github": "tcballard/omarchy-task-manager", + "pattern": "v(?P[0-9]+\\.[0-9]+\\.[0-9]+)", + "allow_prerelease": true + } + }, + "release_ring": "fast" +} diff --git a/pkgbuilds/omarchy-task-manager/PKGBUILD b/pkgbuilds/omarchy-task-manager/PKGBUILD new file mode 100644 index 0000000..959c830 --- /dev/null +++ b/pkgbuilds/omarchy-task-manager/PKGBUILD @@ -0,0 +1,33 @@ +# Canonical recipe. scripts/package-source.sh fills the release source checksum. +# Maintainer: Tom Ballard (tcballard) +pkgname=omarchy-task-manager +pkgver=0.1.1 +pkgrel=1 +url='https://github.com/tcballard/omarchy-task-manager' +pkgdesc='Floating native task manager for Omarchy' +arch=('x86_64') +license=('MIT') +depends=('qt6-base' 'qt6-declarative' 'qt6-wayland' 'qt6-svg' 'hicolor-icon-theme' 'gcc-libs' 'glibc' 'glib2' 'systemd' 'coreutils') +optdepends=('gdb: live process core dumps' 'nvidia-utils: NVIDIA device telemetry') +makedepends=('cmake' 'ninja' 'rust' 'cargo') +checkdepends=('python' 'desktop-file-utils') +source=("$url/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") +sha256sums=('172fcf67770d3a67783907d5451c8e5f2fb38992d4ee2ea8a397a59d15b4b1c0') + +build() { + cmake -S "$srcdir/$pkgname-$pkgver" -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib + cmake --build build +} + +check() { + cd "$srcdir/$pkgname-$pkgver" + cargo test --locked + ctest --test-dir "$srcdir/build" --output-on-failure + desktop-file-validate packaging/io.github.tcballard.TaskManager.desktop + python tests/protocol.py "$srcdir/build/omarchy-task-manager-core" +} + +package() { + DESTDIR="$pkgdir" cmake --install build +} 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/omasnap/PKGBUILD b/pkgbuilds/omasnap/PKGBUILD index 24b7e08..c5cf340 100644 --- a/pkgbuilds/omasnap/PKGBUILD +++ b/pkgbuilds/omasnap/PKGBUILD @@ -1,11 +1,11 @@ # Maintainer: Tobi Lรผtke pkgname=omasnap -pkgver=1.20.1 +pkgver=1.21.0 pkgrel=1 pkgdesc="Native Wayland screenshot and annotation overlay for Hyprland" arch=('x86_64' 'aarch64') -url="https://github.com/tobi/omasnap" +url="https://github.com/omacom/omasnap" license=('MIT' 'OFL-1.1') depends=( 'hyprland' @@ -25,7 +25,7 @@ makedepends=( options=('!debug') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('daf8fd17a81890661eebab791e6e78216c331e040043a1a6b72e638d3626b431') +sha256sums=('2f842edf67631825fa1e102876020040ea3e21341221a428b6aeee5580a9d928') build() { cmake -S "$pkgname-$pkgver" -B build -G Ninja \ @@ -35,8 +35,24 @@ build() { } check() { - QT_QPA_PLATFORM=offscreen \ - ./build/omasnap-smoke "$srcdir/omasnap-smoke-output" + # The smoke suite fsyncs its working documents under /tmp. On the CI + # droplets the build leaves about a gigabyte of dirty pages, and the + # flush that starts a few seconds into the suite makes those fsyncs stall + # long enough to overrun the suite's 5-second settle windows. Flush first. + local runtime_dir status started + started=$(date +%s%N); sync + echo "flushed dirty pages in $(( ($(date +%s%N) - started) / 1000000 )) ms" + runtime_dir=$(mktemp -d /dev/shm/omasnap-runtime.XXXXXX) + if QT_QPA_PLATFORM=offscreen QT_FORCE_STDERR_LOGGING=1 \ + XDG_RUNTIME_DIR="$runtime_dir" \ + ./build/omasnap-smoke "$srcdir/omasnap-smoke-output"; then + status=0 + else + status=$? + echo "omasnap-smoke exited with status $status" >&2 + fi + rm -r -- "$runtime_dir" + return "$status" } package() { diff --git a/pkgbuilds/omaspeak-bin/.omarchy/package.json b/pkgbuilds/omaspeak-bin/.omarchy/package.json new file mode 100644 index 0000000..db153c3 --- /dev/null +++ b/pkgbuilds/omaspeak-bin/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "release_ring": "fast" +} diff --git a/pkgbuilds/omaspeak-bin/PKGBUILD b/pkgbuilds/omaspeak-bin/PKGBUILD new file mode 100644 index 0000000..c60ac9e --- /dev/null +++ b/pkgbuilds/omaspeak-bin/PKGBUILD @@ -0,0 +1,84 @@ +# Maintainer: Jacob Vincent Mink + +pkgname=omaspeak-bin +_pkgname=${pkgname%-bin} +pkgver=0.0.3 +_upstream_ver=0.0.3 +pkgrel=4 +pkgdesc='Local-first text-to-speech application and daemon (pre-built binary)' +arch=('x86_64' 'aarch64') +url='https://github.com/jacob-vincent-mink/omaspeak' +license=('MIT' 'Apache-2.0' 'BSD-3-Clause') +depends=( + 'alsa-utils' + 'gcc-libs' + 'glibc' +) +optdepends=( + 'pipewire-audio: audio playback through pw-play' + 'openvino: Intel CPU acceleration runtime' + 'openvino-intel-gpu-plugin: Intel GPU device support for OpenVINO' + 'openvino-intel-npu-plugin: Intel NPU device support for OpenVINO' + 'cuda: NVIDIA CUDA libraries for an external CUDA runtime bundle' + 'cudnn: NVIDIA neural-network libraries for an external CUDA runtime bundle' +) +provides=("${_pkgname}=${pkgver}") +conflicts=("${_pkgname}") +install="${pkgname}.install" +options=('!strip' '!debug') + +source=('package-remove' 'remove-user-services.hook') +sha256sums=('f1b527448529b45fee2f96b4c0a19b11087377c0ac9f43842bab0d04fcfd3b9f' + '9f1a0c2f5031fcd5905de77643a8727b792e07c582c09c2ba179f0714f118b20') + +source_x86_64=("${_pkgname}-${_upstream_ver}-linux-x86_64.tar.xz::${url}/releases/download/v${_upstream_ver}/${_pkgname}-${_upstream_ver}-linux-x86_64.tar.xz") +source_aarch64=("${_pkgname}-${_upstream_ver}-linux-aarch64.tar.xz::${url}/releases/download/v${_upstream_ver}/${_pkgname}-${_upstream_ver}-linux-aarch64.tar.xz") + +sha256sums_x86_64=('c72428bf6989582b5aa802f39e9390e4cacf26f7fbfacf76645118286c7d1ab2') +sha256sums_aarch64=('88fc4ea8c275b9d5b9d41602d32dbca77ee30de0fc7dcbc06ad0e819fd41545a') + +package() { + install -Dm755 "${srcdir}/package-remove" "${pkgdir}/usr/lib/${_pkgname}/package-remove" + install -Dm644 "${srcdir}/remove-user-services.hook" \ + "${pkgdir}/usr/share/libalpm/hooks/30-${_pkgname}-remove-user-services.hook" + + local release_root="${srcdir}/${_pkgname}-${_upstream_ver}-linux-${CARCH}" + + install -Dm755 "${release_root}/${_pkgname}" "${pkgdir}/usr/bin/${_pkgname}" + + install -Dm755 \ + "${release_root}/lib/libaudiocpp.so.0.1.0" \ + "${pkgdir}/usr/lib/${_pkgname}/libaudiocpp.so.0.1.0" + ln -s libaudiocpp.so.0.1.0 \ + "${pkgdir}/usr/lib/${_pkgname}/libaudiocpp.so.0" + ln -s libaudiocpp.so.0 \ + "${pkgdir}/usr/lib/${_pkgname}/libaudiocpp.so" + + install -Dm644 \ + "${release_root}/packaging/systemd/${_pkgname}.service" \ + "${pkgdir}/usr/lib/systemd/user/${_pkgname}.service" + + local document + for document in \ + README.md \ + INSTALL.md \ + ACCELERATOR_SETUP.md \ + CHANGELOG.md \ + RELEASE_NOTES.md \ + DEMO.md \ + RUNTIME.md \ + config.example.toml; do + install -Dm644 \ + "${release_root}/${document}" \ + "${pkgdir}/usr/share/doc/${pkgname}/${document}" + done + cp -r \ + "${release_root}/assets" \ + "${release_root}/benchmarks" \ + "${pkgdir}/usr/share/doc/${pkgname}/" + + install -dm755 "${pkgdir}/usr/share/licenses/${pkgname}" + cp -r \ + "${release_root}/licenses/." \ + "${pkgdir}/usr/share/licenses/${pkgname}/" +} diff --git a/pkgbuilds/omaspeak-bin/omaspeak-bin.install b/pkgbuilds/omaspeak-bin/omaspeak-bin.install new file mode 100644 index 0000000..4a33226 --- /dev/null +++ b/pkgbuilds/omaspeak-bin/omaspeak-bin.install @@ -0,0 +1,11 @@ +# shellcheck shell=sh + +post_install() { + echo ':: Run omaspeak setup to configure a model and runtime.' + echo ':: The optional user service remains disabled; on-demand speech works without it.' + echo ':: Accelerator instructions: /usr/share/doc/omaspeak-bin/ACCELERATOR_SETUP.md' +} + +post_upgrade() { + echo ':: Run omaspeak setup check to verify the current configuration.' +} diff --git a/pkgbuilds/omaspeak-bin/package-remove b/pkgbuilds/omaspeak-bin/package-remove new file mode 100644 index 0000000..1b45b95 --- /dev/null +++ b/pkgbuilds/omaspeak-bin/package-remove @@ -0,0 +1,150 @@ +#!/bin/bash +# Invoked only by the package's Remove/PreTransaction ALPM hook. +set -eu + +# systemd's configuration parser throws the whitespace around an assignment away +# (parse_line() runs both halves through strstrip()), so a drop-in written as +# ExecStart = /home/alice/build/omawake daemon +# picks the executable just as surely as the unspaced form does. +readonly execstart_assignment='^[[:space:]]*ExecStart[[:space:]]*=' + +owned_unit() { + local commands + [[ -f $1 && ! -L $1 ]] || return 1 + # Only a unit in the very shape the application generates is ever deleted; + # anything else, however it is spaced, stays somebody's own file. + commands=$(grep '^ExecStart=' "$1") || return 1 + [[ $commands != *$'\n'* ]] || return 1 + grep -Eq "^ExecStart=\"?/usr/bin/$2\"?([[:space:]]|$)" <<< "$commands" +} + +# Decode systemd's shell_maybe_quote() output, including cescape_char() escapes. +# Never evaluate manager-controlled values as shell syntax. +unquote_manager_value() { + local text=$1 decoded= character octal + if [[ $text != '$'* ]]; then + unquoted_value=$text + return 0 + fi + [[ $text == \$\'*\' ]] || return 1 + text=${text:2:${#text}-3} + while [[ -n $text ]]; do + if [[ ${text:0:1} != \\ ]]; then + decoded+=${text:0:1} + text=${text:1} + continue + fi + case ${text:1:1} in + \\ | "'") decoded+=${text:1:1} ;; + a | b | f | n | r | t | v) + printf -v character '%b' "\\${text:1:1}" + decoded+=$character + ;; + [0-3]) + octal=${text:1:3} + [[ $octal =~ ^[0-3][0-7]{2}$ && $octal != 000 ]] || return 1 + printf -v character '%b' "\\0$octal" + decoded+=$character + text=${text:4} + continue + ;; + *) return 1 ;; + esac + text=${text:2} + done + unquoted_value=$decoded +} + +remove_for_user() { + local app=$1 user_home=$2 runtime=$3 config="$2/.config" unit="$1.service" + local online=false effective path target manager_environment load_state + if [[ -S $runtime/bus || -S $runtime/systemd/private ]]; then + online=true + # Do not evaluate shell syntax from a user manager's environment. + manager_environment=$(systemctl --user show-environment) || return 1 + while IFS= read -r line; do + case $line in + XDG_CONFIG_HOME=*) + if ! unquote_manager_value "${line#XDG_CONFIG_HOME=}"; then + echo ":: Cannot tell which directory $user_home's manager reads $unit from; removal aborted." >&2 + return 1 + fi + # An XDG_CONFIG_HOME that is not an absolute path is no setting at all: + # the manager itself falls back to the home's .config directory then. + if [[ $unquoted_value == /* ]]; then config=$unquoted_value; fi + ;; + esac + done <<< "$manager_environment" + effective=$(systemctl --user show "$unit" --property=ExecStart --value) || return 1 + if [[ -n $effective && $effective != *"path=/usr/bin/$app ;"* ]]; then + echo ":: Preserving $unit for $user_home: it uses another executable." + return 0 + fi + fi + path="$config/systemd/user/$unit" + if ! $online && grep -qs "$execstart_assignment" "$path.d/"*.conf; then + echo ":: Preserving offline service with an executable override: $path." + return 0 + fi + if [[ -e $path || -L $path ]]; then + if ! owned_unit "$path" "$app"; then + echo ":: Preserving custom or masked unit $path." + return 0 + fi + fi + if $online; then + # Stop first, and fail the package transaction if stopping fails. + load_state=$(systemctl --user show "$unit" --property=LoadState --value) || return 1 + if [[ $load_state != not-found ]]; then + systemctl --user stop "$unit" || return 1 + # A unit that had failed stays failed once it is stopped, which is the one + # state systemd will reset. For every other state it answers that the unit + # is not loaded and exits non-zero, so the reset is asked for only where it + # applies: a healthy unit leaving the transaction never aborts a removal. + if [[ $(systemctl --user show "$unit" --property=ActiveState --value) == failed ]]; then + systemctl --user reset-failed "$unit" || return 1 + fi + # Disabling also removes enablement links outside the normal target. + systemctl --user disable "$unit" || return 1 + fi + fi + # Offline users have no bus. Remove only exact enablement links for this unit. + # All filesystem operations run as the owning user, never as pacman's root. + for target in "$config/systemd/user/"*.wants/"$unit" "$config/systemd/user/"*.requires/"$unit"; do + [[ -L $target ]] || continue + case $(realpath -m -- "$target") in + "$path"|"/usr/lib/systemd/user/$unit") rm -- "$target" ;; + esac + done + if owned_unit "$path" "$app"; then rm -- "$path"; fi + if $online; then systemctl --user daemon-reload || return 1; fi +} + +if [[ ${1-} == --user ]]; then + shift + case ${1-} in omawake|omaspeak) ;; *) exit 2 ;; esac + [[ $# == 3 ]] || exit 2 + remove_for_user "$@" + exit +fi + +[[ $# == 1 ]] || exit 2 +case $1 in omawake|omaspeak) ;; *) exit 2 ;; esac +app=$1 +result=0 +# Include logged-out users as well as active/lingering user managers. +accounts=$(getent passwd) || exit 1 +while IFS=: read -r account _ user_id _ _ user_home _; do + [[ $user_home == /* ]] || continue + runtime="/run/user/$user_id" + if [[ ! -d $user_home/.config/systemd/user && ! -S $runtime/bus && ! -S $runtime/systemd/private ]]; then + continue + fi + if ! runuser -u "$account" -- env -u XDG_CONFIG_HOME \ + XDG_RUNTIME_DIR="$runtime" DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime/bus" \ + "$0" --user "$app" "$user_home" "$runtime"; then + echo ":: Could not clean up $app for $account; removal aborted. Stop/remove the user service and retry." >&2 + result=1 + fi +done <<< "$accounts" +exit "$result" diff --git a/pkgbuilds/omaspeak-bin/remove-user-services.hook b/pkgbuilds/omaspeak-bin/remove-user-services.hook new file mode 100644 index 0000000..f3eec8c --- /dev/null +++ b/pkgbuilds/omaspeak-bin/remove-user-services.hook @@ -0,0 +1,10 @@ +[Trigger] +Operation = Remove +Type = Package +Target = omaspeak-bin + +[Action] +Description = Stop and remove omaspeak user services before package removal +When = PreTransaction +Exec = /usr/lib/omaspeak/package-remove omaspeak +AbortOnFail diff --git a/pkgbuilds/omawake-bin/.omarchy/package.json b/pkgbuilds/omawake-bin/.omarchy/package.json new file mode 100644 index 0000000..db153c3 --- /dev/null +++ b/pkgbuilds/omawake-bin/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "release_ring": "fast" +} diff --git a/pkgbuilds/omawake-bin/PKGBUILD b/pkgbuilds/omawake-bin/PKGBUILD new file mode 100644 index 0000000..2ee1d54 --- /dev/null +++ b/pkgbuilds/omawake-bin/PKGBUILD @@ -0,0 +1,84 @@ +# Maintainer: Jacob Vincent Mink + +pkgname=omawake-bin +_pkgname=${pkgname%-bin} +pkgver=0.0.3 +_upstream_ver=0.0.3 +pkgrel=4 +pkgdesc='Configurable local wake-word daemon (pre-built binary)' +arch=('x86_64' 'aarch64') +url='https://github.com/jacob-vincent-mink/omawake' +license=('MIT' 'Apache-2.0' 'BSD-3-Clause') +depends=( + 'alsa-lib' + 'gcc-libs' + 'glibc' +) +optdepends=( + 'pipewire-audio: PipeWire audio support' + 'openvino: Intel runtime for an externally supplied OpenVINO provider bundle' + 'openvino-intel-gpu-plugin: Intel GPU device support for OpenVINO' + 'openvino-intel-npu-plugin: Intel NPU device support for OpenVINO' + 'cuda: NVIDIA CUDA libraries for an external CUDA runtime bundle' + 'cudnn: NVIDIA neural-network libraries for an external CUDA runtime bundle' +) +provides=("${_pkgname}=${pkgver}") +conflicts=("${_pkgname}") +install="${pkgname}.install" +options=('!strip' '!debug') + +source=('package-remove' 'remove-user-services.hook') +sha256sums=('f1b527448529b45fee2f96b4c0a19b11087377c0ac9f43842bab0d04fcfd3b9f' + 'a0bb2e9de807bdb2cc8d0076eac3c21555c910eb8baa06acfba18fea716b9014') + +source_x86_64=("${_pkgname}-${_upstream_ver}-linux-x86_64.tar.xz::${url}/releases/download/v${_upstream_ver}/${_pkgname}-${_upstream_ver}-linux-x86_64.tar.xz") +source_aarch64=("${_pkgname}-${_upstream_ver}-linux-aarch64.tar.xz::${url}/releases/download/v${_upstream_ver}/${_pkgname}-${_upstream_ver}-linux-aarch64.tar.xz") + +sha256sums_x86_64=('fa374341f60760b04c9a97d76f7ad2679463f5b2b673ee6d9c1ceee97d3f0669') +sha256sums_aarch64=('384eb11872c873a33332acf51f567dc4d4e327e57563b61056e4d0aa7fe470eb') + +package() { + install -Dm755 "${srcdir}/package-remove" "${pkgdir}/usr/lib/${_pkgname}/package-remove" + install -Dm644 "${srcdir}/remove-user-services.hook" \ + "${pkgdir}/usr/share/libalpm/hooks/30-${_pkgname}-remove-user-services.hook" + + local release_root="${srcdir}/${_pkgname}-${_upstream_ver}-linux-${CARCH}" + + install -Dm755 "${release_root}/${_pkgname}" "${pkgdir}/usr/bin/${_pkgname}" + + install -Dm755 \ + "${release_root}/lib/libaudiocpp.so.0.1.0" \ + "${pkgdir}/usr/lib/${_pkgname}/libaudiocpp.so.0.1.0" + ln -s libaudiocpp.so.0.1.0 \ + "${pkgdir}/usr/lib/${_pkgname}/libaudiocpp.so.0" + ln -s libaudiocpp.so.0 \ + "${pkgdir}/usr/lib/${_pkgname}/libaudiocpp.so" + + install -Dm644 \ + "${release_root}/packaging/systemd/${_pkgname}.service" \ + "${pkgdir}/usr/lib/systemd/user/${_pkgname}.service" + + local document + for document in \ + README.md \ + INSTALL.md \ + ACCELERATOR_SETUP.md \ + CHANGELOG.md \ + RELEASE_NOTES.md \ + DEMO.md \ + RUNTIME.md \ + config.example.toml; do + install -Dm644 \ + "${release_root}/${document}" \ + "${pkgdir}/usr/share/doc/${pkgname}/${document}" + done + cp -r \ + "${release_root}/assets" \ + "${release_root}/benchmarks" \ + "${pkgdir}/usr/share/doc/${pkgname}/" + + install -dm755 "${pkgdir}/usr/share/licenses/${pkgname}" + cp -r \ + "${release_root}/licenses/." \ + "${pkgdir}/usr/share/licenses/${pkgname}/" +} diff --git a/pkgbuilds/omawake-bin/omawake-bin.install b/pkgbuilds/omawake-bin/omawake-bin.install new file mode 100644 index 0000000..a5a993e --- /dev/null +++ b/pkgbuilds/omawake-bin/omawake-bin.install @@ -0,0 +1,11 @@ +# shellcheck shell=sh + +post_install() { + echo ':: Run omawake setup to configure a model and runtime.' + echo ':: The optional user service remains disabled; enable it only when desired.' + echo ':: Accelerator instructions: /usr/share/doc/omawake-bin/ACCELERATOR_SETUP.md' +} + +post_upgrade() { + echo ':: Run omawake setup check to verify the current configuration.' +} diff --git a/pkgbuilds/omawake-bin/package-remove b/pkgbuilds/omawake-bin/package-remove new file mode 100644 index 0000000..1b45b95 --- /dev/null +++ b/pkgbuilds/omawake-bin/package-remove @@ -0,0 +1,150 @@ +#!/bin/bash +# Invoked only by the package's Remove/PreTransaction ALPM hook. +set -eu + +# systemd's configuration parser throws the whitespace around an assignment away +# (parse_line() runs both halves through strstrip()), so a drop-in written as +# ExecStart = /home/alice/build/omawake daemon +# picks the executable just as surely as the unspaced form does. +readonly execstart_assignment='^[[:space:]]*ExecStart[[:space:]]*=' + +owned_unit() { + local commands + [[ -f $1 && ! -L $1 ]] || return 1 + # Only a unit in the very shape the application generates is ever deleted; + # anything else, however it is spaced, stays somebody's own file. + commands=$(grep '^ExecStart=' "$1") || return 1 + [[ $commands != *$'\n'* ]] || return 1 + grep -Eq "^ExecStart=\"?/usr/bin/$2\"?([[:space:]]|$)" <<< "$commands" +} + +# Decode systemd's shell_maybe_quote() output, including cescape_char() escapes. +# Never evaluate manager-controlled values as shell syntax. +unquote_manager_value() { + local text=$1 decoded= character octal + if [[ $text != '$'* ]]; then + unquoted_value=$text + return 0 + fi + [[ $text == \$\'*\' ]] || return 1 + text=${text:2:${#text}-3} + while [[ -n $text ]]; do + if [[ ${text:0:1} != \\ ]]; then + decoded+=${text:0:1} + text=${text:1} + continue + fi + case ${text:1:1} in + \\ | "'") decoded+=${text:1:1} ;; + a | b | f | n | r | t | v) + printf -v character '%b' "\\${text:1:1}" + decoded+=$character + ;; + [0-3]) + octal=${text:1:3} + [[ $octal =~ ^[0-3][0-7]{2}$ && $octal != 000 ]] || return 1 + printf -v character '%b' "\\0$octal" + decoded+=$character + text=${text:4} + continue + ;; + *) return 1 ;; + esac + text=${text:2} + done + unquoted_value=$decoded +} + +remove_for_user() { + local app=$1 user_home=$2 runtime=$3 config="$2/.config" unit="$1.service" + local online=false effective path target manager_environment load_state + if [[ -S $runtime/bus || -S $runtime/systemd/private ]]; then + online=true + # Do not evaluate shell syntax from a user manager's environment. + manager_environment=$(systemctl --user show-environment) || return 1 + while IFS= read -r line; do + case $line in + XDG_CONFIG_HOME=*) + if ! unquote_manager_value "${line#XDG_CONFIG_HOME=}"; then + echo ":: Cannot tell which directory $user_home's manager reads $unit from; removal aborted." >&2 + return 1 + fi + # An XDG_CONFIG_HOME that is not an absolute path is no setting at all: + # the manager itself falls back to the home's .config directory then. + if [[ $unquoted_value == /* ]]; then config=$unquoted_value; fi + ;; + esac + done <<< "$manager_environment" + effective=$(systemctl --user show "$unit" --property=ExecStart --value) || return 1 + if [[ -n $effective && $effective != *"path=/usr/bin/$app ;"* ]]; then + echo ":: Preserving $unit for $user_home: it uses another executable." + return 0 + fi + fi + path="$config/systemd/user/$unit" + if ! $online && grep -qs "$execstart_assignment" "$path.d/"*.conf; then + echo ":: Preserving offline service with an executable override: $path." + return 0 + fi + if [[ -e $path || -L $path ]]; then + if ! owned_unit "$path" "$app"; then + echo ":: Preserving custom or masked unit $path." + return 0 + fi + fi + if $online; then + # Stop first, and fail the package transaction if stopping fails. + load_state=$(systemctl --user show "$unit" --property=LoadState --value) || return 1 + if [[ $load_state != not-found ]]; then + systemctl --user stop "$unit" || return 1 + # A unit that had failed stays failed once it is stopped, which is the one + # state systemd will reset. For every other state it answers that the unit + # is not loaded and exits non-zero, so the reset is asked for only where it + # applies: a healthy unit leaving the transaction never aborts a removal. + if [[ $(systemctl --user show "$unit" --property=ActiveState --value) == failed ]]; then + systemctl --user reset-failed "$unit" || return 1 + fi + # Disabling also removes enablement links outside the normal target. + systemctl --user disable "$unit" || return 1 + fi + fi + # Offline users have no bus. Remove only exact enablement links for this unit. + # All filesystem operations run as the owning user, never as pacman's root. + for target in "$config/systemd/user/"*.wants/"$unit" "$config/systemd/user/"*.requires/"$unit"; do + [[ -L $target ]] || continue + case $(realpath -m -- "$target") in + "$path"|"/usr/lib/systemd/user/$unit") rm -- "$target" ;; + esac + done + if owned_unit "$path" "$app"; then rm -- "$path"; fi + if $online; then systemctl --user daemon-reload || return 1; fi +} + +if [[ ${1-} == --user ]]; then + shift + case ${1-} in omawake|omaspeak) ;; *) exit 2 ;; esac + [[ $# == 3 ]] || exit 2 + remove_for_user "$@" + exit +fi + +[[ $# == 1 ]] || exit 2 +case $1 in omawake|omaspeak) ;; *) exit 2 ;; esac +app=$1 +result=0 +# Include logged-out users as well as active/lingering user managers. +accounts=$(getent passwd) || exit 1 +while IFS=: read -r account _ user_id _ _ user_home _; do + [[ $user_home == /* ]] || continue + runtime="/run/user/$user_id" + if [[ ! -d $user_home/.config/systemd/user && ! -S $runtime/bus && ! -S $runtime/systemd/private ]]; then + continue + fi + if ! runuser -u "$account" -- env -u XDG_CONFIG_HOME \ + XDG_RUNTIME_DIR="$runtime" DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime/bus" \ + "$0" --user "$app" "$user_home" "$runtime"; then + echo ":: Could not clean up $app for $account; removal aborted. Stop/remove the user service and retry." >&2 + result=1 + fi +done <<< "$accounts" +exit "$result" diff --git a/pkgbuilds/omawake-bin/remove-user-services.hook b/pkgbuilds/omawake-bin/remove-user-services.hook new file mode 100644 index 0000000..4bac03f --- /dev/null +++ b/pkgbuilds/omawake-bin/remove-user-services.hook @@ -0,0 +1,10 @@ +[Trigger] +Operation = Remove +Type = Package +Target = omawake-bin + +[Action] +Description = Stop and remove omawake user services before package removal +When = PreTransaction +Exec = /usr/lib/omawake/package-remove omawake +AbortOnFail 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/once-bin/PKGBUILD b/pkgbuilds/once-bin/PKGBUILD index 7784085..8b0f952 100644 --- a/pkgbuilds/once-bin/PKGBUILD +++ b/pkgbuilds/once-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Kevin McConnell pkgname=once-bin -pkgver=0.3.2 +pkgver=0.3.3 pkgrel=1 pkgdesc='CLI/TUI for installing and managing self-hosted web applications' arch=('x86_64' 'aarch64') @@ -22,8 +22,8 @@ source=("MIT-LICENSE-${pkgver}::https://raw.githubusercontent.com/basecamp/once/ sha256sums=('fa0d1454375cbc7701bc13d916c3ae71e613b8ae718321641e678e09409393c4' 'aa314fe79677eb5f120fcc3d4c42007a93ff7b1ef917382c0c1fdca5633ad46a') -sha256sums_x86_64=('e1da40a0952879580e43623d6fd6002a391ee469b642c98ecddbe00374facbb6') -sha256sums_aarch64=('9bd644e1557521b0b8cab93ba3841747cbee0390aa3aa020bd91bfa66ac51dec') +sha256sums_x86_64=('aef855da263721c6c1072ff5ebc4c17a52af8c8e80c46c5a9dd458e7ca3a7f35') +sha256sums_aarch64=('97e32ba0fdac0ad5e6010851b306e3cb2616285a9eeb2e869ff7e71f4b442bbb') package() { install -Dm755 "once-${pkgver}-${CARCH}" "${pkgdir}/usr/bin/once" diff --git a/pkgbuilds/openai-codex-bin/.omarchy/package.json b/pkgbuilds/openai-codex-bin/.omarchy/package.json index 9a120f1..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": "f72ee6f702ebc825c1680241dd599fb890c5a66a" + "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 6cdbbb0..64e5687 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.154.0 +pkgver=0.155.1 pkgrel=1 pkgdesc="Arch Linux package for OpenAI's Codex CLI - Auto Updated" arch=('x86_64' 'aarch64') @@ -21,10 +21,8 @@ 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=('d7e18b2597ae8f242f5f31ee9e90deef48dbc9edd634d9868fb6435d08c07f02' - 'a68df7cca23c6da7cde175677df7de61c73a234add1333a1254b86d641af01f7') -sha256sums_aarch64=('583b48df32804213bdcd338c2e5adb06b34340821fa757a726cc0a524fa33c27' - '20aefa302c2022b496e32911bf954a5f76c7fd749c6bdb9fbd711e32b66dcbfa') +sha256sums_x86_64=('a0ef8b2debc3bf747e07b1a039354de31300ac0dcc2276498ba281470b5d9115' '9fd083743af55be818aceb351d371fb5136f5b6aa3938f167087373d27067b2d') +sha256sums_aarch64=('d6c7e62fbd688d52ee04f3929d0613705d32a920a42db7a139e366eaf1f4a2d7' '516f2ed76d4ae96c2074d3c08f4576ed1bdc5c3a97e26734d7319de8b6861683') 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 61ac091..8ca352d 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.908.40834 +pkgver=26.915.31945 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=('da37b8e7bcefaaea019c478cacbe6c73ee1ddd15e0e1ebb3c7ef0a42dd818ac2') -sha256sums_aarch64=('bae5c5ca585625a116a8877dedc455e4c27ca02063ea93dbd6a0506ed6a12d31') +sha256sums_x86_64=('d27a9c02919cfe484dcc5f34584b9ea9fd0d7a65c69dcc872b5bdcfa0efb5983') +sha256sums_aarch64=('b94c494b5f0fd7c720fa6fccd5ef609879affc62332ca930ed29b907d537bc6d') package() { cd "${srcdir}" diff --git a/pkgbuilds/openclaw/PKGBUILD b/pkgbuilds/openclaw/PKGBUILD index aece3b2..2cf1665 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.3 +pkgver=2026.9.5 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=('d1c63366833f8ae4a6ab4f3b60b1aa84ca82d03dba13d3d3eba989aa159e2449') +sha256sums=('1fb6ef4fae447af14f1e3b1028334f39146d181a66a4cce2848d4f741c636340') options=(!debug !strip) install=$pkgname.install noextract=($pkgname-$pkgver.tgz) diff --git a/pkgbuilds/openvino-genai/.omarchy/package.json b/pkgbuilds/openvino-genai/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/openvino-genai/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/openvino-genai/PKGBUILD b/pkgbuilds/openvino-genai/PKGBUILD new file mode 100644 index 0000000..41f52cd --- /dev/null +++ b/pkgbuilds/openvino-genai/PKGBUILD @@ -0,0 +1,95 @@ +# Maintainer: Spencer Bull + +pkgname=openvino-genai +pkgver=2026.3.1.0 +pkgrel=3 +pkgdesc="OpenVINO GenAI C and C++ runtime libraries" +arch=('x86_64') +url="https://github.com/openvinotoolkit/openvino.genai" +license=('Apache-2.0') +options=('!debug' '!lto') +depends=( + 'gcc-libs' + 'glibc' + 'onetbb' + 'openvino=2026.3.1' # Includes libopenvino_c.so. +) +makedepends=( + 'cmake' + 'git' + 'ninja' + 'python' +) +optdepends=( + 'openvino-intel-gpu-plugin: inference on Intel GPUs' + 'openvino-intel-npu-plugin: inference on Intel NPUs' +) + +_commit=56d9685302da2fc5cc7c9689cfab500fd0660a02 +source=( + "openvino.genai::git+$url.git#commit=$_commit" + 'gcc-16-char8_t.patch' + 'format-template-linkage.patch::https://github.com/openvinotoolkit/openvino.genai/commit/398fbc1450f7485368edf52dd82f45eba215d6c9.patch' +) +sha256sums=( + 'SKIP' + '6e685c1e45d4b2314fd55e2f791846cc9086413ba62a6bb5e31be5a5878a243c' + '8c2a3e4bf1d33e00b780da7bec2d5e40b6fd26a4e518359d6ff7c59ca7f649e3' +) + +prepare() { + cd openvino.genai + patch -Np1 -i "$srcdir/gcc-16-char8_t.patch" + # Backport upstream 398fbc14: GCC -O3 can otherwise leave format + # unresolved in libopenvino_genai.so. + patch -Np1 -i "$srcdir/format-template-linkage.patch" + git submodule update --init --recursive +} + +build() { + CFLAGS+=" -ffile-prefix-map=$srcdir=/usr/src/$pkgname" + CXXFLAGS+=" -ffile-prefix-map=$srcdir=/usr/src/$pkgname" + + cmake -S openvino.genai -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_SKIP_RPATH=ON \ + -DENABLE_JS=OFF \ + -DENABLE_MISAKI_CPP=OFF \ + -DENABLE_PYTHON=OFF \ + -DENABLE_SAMPLES=OFF \ + -DENABLE_TESTS=OFF \ + -DENABLE_TOOLS=OFF \ + -DENABLE_XGRAMMAR=OFF + + cmake --build build +} + +check() { + LD_LIBRARY_PATH="$srcdir/build/openvino_genai:$srcdir/build/src/c${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \ + python -c 'import ctypes; ctypes.CDLL("libopenvino_c.so"); ctypes.CDLL("libopenvino_genai_c.so")' +} + +package() { + install -d "$pkgdir/usr/lib" "$pkgdir/usr/share/licenses/$pkgname" + + cp -a build/openvino_genai/libopenvino_genai.so* "$pkgdir/usr/lib/" + cp -a build/openvino_genai/libopenvino_tokenizers.so* "$pkgdir/usr/lib/" + cp -a build/src/c/libopenvino_genai_c.so* "$pkgdir/usr/lib/" + + # Consumers such as omawake load the unversioned name dynamically. + # Reject a missing/empty library or a broken symlink before publishing. + if [[ ! -s "$pkgdir/usr/lib/libopenvino_genai_c.so" ]]; then + error 'Required C binding is missing or empty: libopenvino_genai_c.so' + return 1 + fi + + install -Dm644 openvino.genai/LICENSE \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 openvino.genai/third-party-programs.txt \ + "$pkgdir/usr/share/licenses/$pkgname/third-party-programs.txt" + install -Dm644 openvino.genai/thirdparty/openvino_tokenizers/LICENSE \ + "$pkgdir/usr/share/licenses/$pkgname/openvino-tokenizers-LICENSE" + install -Dm644 openvino.genai/thirdparty/openvino_tokenizers/third-party-programs.txt \ + "$pkgdir/usr/share/licenses/$pkgname/openvino-tokenizers-third-party-programs.txt" +} diff --git a/pkgbuilds/openvino-genai/gcc-16-char8_t.patch b/pkgbuilds/openvino-genai/gcc-16-char8_t.patch new file mode 100644 index 0000000..c94559d --- /dev/null +++ b/pkgbuilds/openvino-genai/gcc-16-char8_t.patch @@ -0,0 +1,7 @@ +diff --git a/src/cpp/src/whisper/word_level_timestamps.cpp b/src/cpp/src/whisper/word_level_timestamps.cpp +index 7b3da124..d1f9212f 100644 +--- a/src/cpp/src/whisper/word_level_timestamps.cpp ++++ b/src/cpp/src/whisper/word_level_timestamps.cpp +@@ -327 +327 @@ std::pair, std::vector>> split_toke +- const std::string replacement_char = u8"\uFFFD"; ++ const std::string replacement_char = "\xEF\xBF\xBD"; diff --git a/pkgbuilds/owe-lockfeed/.omarchy/package.json b/pkgbuilds/owe-lockfeed/.omarchy/package.json new file mode 100644 index 0000000..bc11813 --- /dev/null +++ b/pkgbuilds/owe-lockfeed/.omarchy/package.json @@ -0,0 +1,10 @@ +{ + "source": "local", + "release_ring": "fast", + "upstream": { + "watch": { + "github": "omacom/owe", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + } +} diff --git a/pkgbuilds/owe-lockfeed/PKGBUILD b/pkgbuilds/owe-lockfeed/PKGBUILD new file mode 100644 index 0000000..9df2a24 --- /dev/null +++ b/pkgbuilds/owe-lockfeed/PKGBUILD @@ -0,0 +1,28 @@ +# Maintainer: owe contributors +pkgname=owe-lockfeed +pkgver=0.2.6 +pkgrel=1 +pkgdesc="Lock screen video feed module for the OWE wallpaper engine" +arch=('x86_64' 'aarch64') +url="https://github.com/omacom/owe" +license=('MIT') +depends=('qt6-declarative') +makedepends=('cmake' 'qt6-declarative') +source=("$pkgname-$pkgver.tar.gz::https://github.com/omacom/owe/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('e5c10e60bdfaebed861a3b7515c691a5a78fc0fe3ab29c0e96d934eb1a957cf8') + +build() { + cmake -S "$srcdir/owe-$pkgver/qml-plugin" -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_INSTALL_LIBDIR=lib + cmake --build build +} + +check() { + ctest --test-dir build --output-on-failure +} + +package() { + DESTDIR="$pkgdir" cmake --install build +} diff --git a/pkgbuilds/owe/.omarchy/package.json b/pkgbuilds/owe/.omarchy/package.json new file mode 100644 index 0000000..4f7a928 --- /dev/null +++ b/pkgbuilds/owe/.omarchy/package.json @@ -0,0 +1,14 @@ +{ + "source": "local", + "release_ring": "fast", + "upstream": { + "watch": { + "github": "omacom/owe", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "origin": { + "aur": "owe", + "commit": "884100f82cf2940e4c1ef50dff39a99e37e9895e" + } +} diff --git a/pkgbuilds/owe/PKGBUILD b/pkgbuilds/owe/PKGBUILD new file mode 100644 index 0000000..8815f55 --- /dev/null +++ b/pkgbuilds/owe/PKGBUILD @@ -0,0 +1,34 @@ +# Maintainer: owe contributors +pkgname=owe +pkgver=0.2.6 +pkgrel=1 +pkgdesc="High-performance wallpaper engine for Omarchy (mp4, gif, stills)" +arch=('x86_64' 'aarch64') +url="https://github.com/omacom/owe" +license=('MIT') +depends=('mpv' 'ffmpeg' 'wayland' 'libglvnd' 'libepoxy' 'systemd-libs' 'socat') +makedepends=('meson' 'ninja' 'gcc' 'pkgconf' 'wayland-protocols') +checkdepends=('python') +optdepends=('intel-media-driver: VAAPI hardware decode on Intel GPUs' + 'libva-mesa-driver: VAAPI hardware decode on AMD GPUs') +source=("$pkgname-$pkgver.tar.gz::https://github.com/omacom/owe/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('e5c10e60bdfaebed861a3b7515c691a5a78fc0fe3ab29c0e96d934eb1a957cf8') + +build() { + meson setup build "$srcdir/owe-$pkgver" -Dbuildtype=release -Dprefix=/usr + ninja -C build +} + +check() { + meson test -C build +} + +package() { + DESTDIR="$pkgdir" ninja -C build install + install -d "$pkgdir/usr/lib/systemd/user" + sed 's|%h/.local/bin/owed|/usr/bin/owed|' "$srcdir/owe-$pkgver/systemd/owed.service" \ + >"$pkgdir/usr/lib/systemd/user/owed.service" + install -Dm755 "$srcdir/owe-$pkgver/hooks/owe-idle" "$pkgdir/usr/bin/owe-idle" + install -Dm644 "$srcdir/owe-$pkgver/hooks/theme-set.d/10-owe-sync" "$pkgdir/usr/share/owe/10-owe-sync" + install -Dm644 "$srcdir/owe-$pkgver/config/config.toml" "$pkgdir/usr/share/doc/$pkgname/config.toml.example" +} diff --git a/pkgbuilds/perplexity/PKGBUILD b/pkgbuilds/perplexity/PKGBUILD index d3be886..8174de1 100644 --- a/pkgbuilds/perplexity/PKGBUILD +++ b/pkgbuilds/perplexity/PKGBUILD @@ -5,7 +5,7 @@ # from that repository's package index. pkgname=perplexity -pkgver=26.9.4+build70439 +pkgver=26.9.4+build72244 pkgrel=1 pkgdesc="Official Perplexity desktop app" arch=('x86_64' 'aarch64') @@ -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=('23a21f50f2ad9c0bd38117d7a9a032bec80925f85a7706ebb95da3d1166c5914') -sha256sums_aarch64=('0b8b8a60d2f8e888e40895265b0ab681d7c2030e789690fceb4bc8dca83f27ba') +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/qemu-user-static/.omarchy/package.json b/pkgbuilds/qemu-user-static/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/qemu-user-static/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/qemu-user-static/PKGBUILD b/pkgbuilds/qemu-user-static/PKGBUILD new file mode 100644 index 0000000..8d983a8 --- /dev/null +++ b/pkgbuilds/qemu-user-static/PKGBUILD @@ -0,0 +1,73 @@ +# aarch64-only build: Arch Linux ARM builds qemu without the static user-mode +# binaries, so the Omarchy default set (qemu-user-static-binfmt, used for +# foreign-architecture containers) repackages Debian's statically linked +# qemu-user build. On x86_64 both packages come from the Arch extra repository. + +pkgbase=qemu-user-static +pkgname=('qemu-user-static' 'qemu-user-static-binfmt') +# One-time epoch moves from upstream-only to full Debian revision ordering. +# pkgver records Debian epoch.upstream.revision. Updates are reviewed pins: +# bump _debver, pkgver, _snapshot and the checksum together. +epoch=1 +pkgver=1.10.0.13+ds.0+deb13u1 +_debver=1:10.0.13+ds-0+deb13u1 +_snapshot=02b553a4532192bc999d15408d1e56d7b254906f +pkgrel=1 +pkgdesc="A generic and open source machine emulator and virtualizer - static user-mode emulation" +arch=('aarch64') +url="https://www.qemu.org" +license=('GPL-2.0-only' 'LGPL-2.1-only') +source=("qemu-binfmt-conf.sh") +# Debian removes superseded pool files. Pin the official archive snapshot so +# the same checksum-verified binary remains rebuildable. _snapshot is the +# content-addressed snapshot SHA1; SHA256 below remains the integrity check. +source_aarch64=("qemu-user_${pkgver}_arm64.deb::https://snapshot.debian.org/file/$_snapshot") +sha256sums=('5e12ef484b8364e1ba7bae4acda837d2e6320ffbeb7fab341034124553cdb137') +sha256sums_aarch64=('c73711af02b97cd5e2667e735d9c06890c57165517110ca4e646c95a7083158d') +noextract=("qemu-user_${pkgver}_arm64.deb") + +prepare() { + mkdir -p deb + bsdtar -C deb -xf "qemu-user_${pkgver}_arm64.deb" data.tar.xz + bsdtar -C deb -xf deb/data.tar.xz ./usr/bin ./usr/share/doc/qemu-user/copyright +} + +package_qemu-user-static() { + pkgdesc="A generic and open source machine emulator and virtualizer - static user-mode emulation" + depends=('glibc') + optdepends=('qemu-user-static-binfmt: binary format rules for foreign-architecture binaries') + provides=('qemu-user-static') + conflicts=('qemu-user-static-bin') + + install -dm755 "$pkgdir/usr/bin" + local binary + for binary in deb/usr/bin/qemu-*; do + install -Dm755 "$binary" "$pkgdir/usr/bin/$(basename "$binary")-static" + done + install -Dm644 deb/usr/share/doc/qemu-user/copyright "$pkgdir/usr/share/licenses/$pkgname/copyright" +} + +package_qemu-user-static-binfmt() { + pkgdesc="Binary format rules for the static QEMU user-mode emulators" + depends=('qemu-user-static') + provides=('qemu-user-binfmt-provider') + conflicts=('qemu-user-binfmt-provider' 'binfmt-qemu-static') + + install -dm755 "$pkgdir/usr/lib/binfmt.d" + # HOST_ARCH keeps the native architecture out of the rule set regardless + # of the build host, so the package is identical under native and QEMU builds. + # --ignore-family keeps 32-bit ARM: the script groups it with aarch64, but + # Apple Silicon cannot execute AArch32 natively. + HOST_ARCH=aarch64 sh "$srcdir/qemu-binfmt-conf.sh" \ + --systemd ALL \ + --exportdir "$pkgdir/usr/lib/binfmt.d/" \ + --qemu-path /usr/bin \ + --qemu-suffix -static \ + --persistent yes \ + --preserve-argv0 yes \ + --ignore-family yes + local conf + for conf in "$pkgdir"/usr/lib/binfmt.d/*.conf; do + mv "$conf" "${conf%.conf}-static.conf" + done +} diff --git a/pkgbuilds/qemu-user-static/README.md b/pkgbuilds/qemu-user-static/README.md new file mode 100644 index 0000000..f487ccd --- /dev/null +++ b/pkgbuilds/qemu-user-static/README.md @@ -0,0 +1,11 @@ +# ARM static QEMU + +This aarch64-only split recipe provides qemu-user-static and qemu-user-static-binfmt using Debianโ€™s static ARM64 binaries. It preserves Marceloโ€™s recipe, imported through omarchy-mac/omarchy-pkgs-aarch64#61, including licensing and binfmt behavior. It does not change x86 packages or the build workers. + +## Updates + +Updates are reviewed pins, not automatic. To move to a new Debian 13 revision, update `_debver`, `pkgver` (Debian epoch.upstream.revision), `_snapshot` (the snapshot.debian.org SHA1 of the ARM64 `qemu-user` archive) and `sha256sums_aarch64` together, and confirm both Debian ordering and `vercmp` advance. A one-time Arch epoch of 1 moves away from the previous upstream-only version. + +## binfmt rules + +The rules are generated with `--ignore-family yes`, so 32-bit ARM binaries are registered even though the script groups them with aarch64; Apple Silicon has no AArch32 execution. Native aarch64 stays excluded. Rules use the persistent and preserve-argv0 flags and never the credential flag. diff --git a/pkgbuilds/qemu-user-static/qemu-binfmt-conf.sh b/pkgbuilds/qemu-user-static/qemu-binfmt-conf.sh new file mode 100644 index 0000000..5fd462b --- /dev/null +++ b/pkgbuilds/qemu-user-static/qemu-binfmt-conf.sh @@ -0,0 +1,442 @@ +#!/bin/sh +# Enable automatic program execution by the kernel. + +qemu_target_list="i386 i486 alpha arm armeb sparc sparc32plus sparc64 \ +ppc ppc64 ppc64le m68k mips mipsel mipsn32 mipsn32el mips64 mips64el \ +sh4 sh4eb s390x aarch64 aarch64_be hppa riscv32 riscv64 xtensa xtensaeb \ +microblaze microblazeel or1k x86_64 hexagon loongarch64" + +i386_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00' +i386_mask='\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +i386_family=i386 + +i486_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00' +i486_mask='\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +i486_family=i386 + +x86_64_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00' +x86_64_mask='\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +x86_64_family=i386 + +alpha_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90' +alpha_mask='\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +alpha_family=alpha + +arm_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00' +arm_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +arm_family=arm + +armeb_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28' +armeb_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +armeb_family=armeb + +sparc_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02' +sparc_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +sparc_family=sparc + +sparc32plus_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12' +sparc32plus_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +sparc32plus_family=sparc + +sparc64_magic='\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2b' +sparc64_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +sparc64_family=sparc + +ppc_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14' +ppc_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +ppc_family=ppc + +ppc64_magic='\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15' +ppc64_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +ppc64_family=ppc + +ppc64le_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00' +ppc64le_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00' +ppc64le_family=ppcle + +m68k_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x04' +m68k_mask='\xff\xff\xff\xff\xff\xff\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +m68k_family=m68k + +# FIXME: We could use the other endianness on a MIPS host. + +mips_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +mips_mask='\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20' +mips_family=mips + +mipsel_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +mipsel_mask='\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00' +mipsel_family=mips + +mipsn32_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20' +mipsn32_mask='\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20' +mipsn32_family=mips + +mipsn32el_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00' +mipsn32el_mask='\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00' +mipsn32el_family=mips + +mips64_magic='\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08' +mips64_mask='\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +mips64_family=mips + +mips64el_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00' +mips64el_mask='\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +mips64el_family=mips + +sh4_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a\x00' +sh4_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +sh4_family=sh4 + +sh4eb_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a' +sh4eb_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +sh4eb_family=sh4 + +s390x_magic='\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x16' +s390x_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +s390x_family=s390x + +aarch64_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00' +aarch64_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +aarch64_family=arm + +aarch64_be_magic='\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7' +aarch64_be_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +aarch64_be_family=armeb + +hppa_magic='\x7f\x45\x4c\x46\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x0f' +hppa_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +hppa_family=hppa + +riscv32_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00' +riscv32_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +riscv32_family=riscv + +riscv64_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00' +riscv64_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +riscv64_family=riscv + +xtensa_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x5e\x00' +xtensa_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +xtensa_family=xtensa + +xtensaeb_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x5e' +xtensaeb_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +xtensaeb_family=xtensaeb + +microblaze_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xba\xab' +microblaze_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +microblaze_family=microblaze + +microblazeel_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xab\xba' +microblazeel_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +microblazeel_family=microblazeel + +or1k_magic='\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x5c' +or1k_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff' +or1k_family=or1k + +hexagon_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xa4\x00' +hexagon_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +hexagon_family=hexagon + +loongarch64_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x01' +loongarch64_mask='\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff' +loongarch64_family=loongarch + +# Converts the name of a host CPU architecture to the corresponding QEMU +# target. +# +# FIXME: This can probably be simplified a lot by dropping most entries. +# Remember that the script is only used on Linux, so we only need to +# handle the strings Linux uses to report the host CPU architecture. +qemu_normalize() { + cpu="$1" + case "$cpu" in + i[3-6]86) + echo "i386" + ;; + amd64) + echo "x86_64" + ;; + powerpc) + echo "ppc" + ;; + ppc64el) + echo "ppc64le" + ;; + armel|armhf|armv[4-9]*l) + echo "arm" + ;; + armv[4-9]*b) + echo "armeb" + ;; + arm64) + echo "aarch64" + ;; + *) + echo "$cpu" + ;; + esac +} + +usage() { + cat <&2 + exit 1 + fi +} + +qemu_check_bintfmt_misc() { + # load the binfmt_misc module + if [ ! -d /proc/sys/fs/binfmt_misc ]; then + if ! /sbin/modprobe binfmt_misc ; then + exit 1 + fi + fi + if [ ! -f /proc/sys/fs/binfmt_misc/register ]; then + if ! mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc ; then + exit 1 + fi + fi + + qemu_check_access /proc/sys/fs/binfmt_misc/register +} + +installed_dpkg() { + dpkg --status "$1" > /dev/null 2>&1 +} + +qemu_check_debian() { + if [ ! -e /etc/debian_version ] ; then + echo "WARNING: your system is not a Debian based distro" 1>&2 + elif ! installed_dpkg binfmt-support ; then + echo "WARNING: package binfmt-support is needed" 1>&2 + fi + qemu_check_access "$EXPORTDIR" +} + +qemu_check_systemd() { + if ! systemctl -q is-enabled systemd-binfmt.service ; then + echo "WARNING: systemd-binfmt.service is missing or disabled" 1>&2 + fi + qemu_check_access "$EXPORTDIR" +} + +qemu_generate_register() { + flags="" + if [ "$CREDENTIAL" = "yes" ] ; then + flags="OC" + fi + if [ "$PERSISTENT" = "yes" ] ; then + flags="${flags}F" + fi + if [ "$PRESERVE_ARG0" = "yes" ] ; then + flags="${flags}P" + fi + + echo ":qemu-$cpu:M::$magic:$mask:$qemu:$flags" +} + +qemu_register_interpreter() { + echo "Setting $qemu as binfmt interpreter for $cpu" + qemu_generate_register > /proc/sys/fs/binfmt_misc/register +} + +qemu_generate_systemd() { + echo "Setting $qemu as binfmt interpreter for $cpu for systemd-binfmt.service" + qemu_generate_register > "$EXPORTDIR/qemu-$cpu.conf" +} + +qemu_generate_debian() { + cat > "$EXPORTDIR/qemu-$cpu" <&2 + exit 1 + fi + + # register the interpreter for each cpu except for the native one + + for cpu in ${qemu_target_list} ; do + magic=$(eval echo \$${cpu}_magic) + mask=$(eval echo \$${cpu}_mask) + family=$(eval echo \$${cpu}_family) + + target="$cpu" + if [ "$cpu" = "i486" ] ; then + target="i386" + fi + + qemu="$QEMU_PATH/qemu-$target$QEMU_SUFFIX" + + if [ "$magic" = "" ] || [ "$mask" = "" ] || [ "$family" = "" ] ; then + echo "INTERNAL ERROR: unknown cpu $cpu" 1>&2 + continue + fi + + if [ "$host_family" = "$family" ] ; then + # When --ignore-family is used, we have to generate rules even + # for targets that are in the same family as the host CPU. The + # only exception is of course when the CPU types exactly match + if [ "$target" = "$host_cpu" ] || [ "$IGNORE_FAMILY" = "no" ] ; then + continue + fi + fi + + $BINFMT_SET + done +} + +CHECK=qemu_check_bintfmt_misc +BINFMT_SET=qemu_register_interpreter + +SYSTEMDDIR="/etc/binfmt.d" +DEBIANDIR="/usr/share/binfmts" + +QEMU_PATH=/usr/local/bin +CREDENTIAL=no +PERSISTENT=no +PRESERVE_ARG0=no +QEMU_SUFFIX="" +IGNORE_FAMILY=no + +_longopts="debian,systemd:,qemu-path:,qemu-suffix:,exportdir:,help,credential:,\ +persistent:,preserve-argv0:,ignore-family:" +options=$(getopt -o ds:Q:S:e:hc:p:g:F:i: -l ${_longopts} -- "$@") +eval set -- "$options" + +while true ; do + case "$1" in + -d|--debian) + CHECK=qemu_check_debian + BINFMT_SET=qemu_generate_debian + EXPORTDIR=${EXPORTDIR:-$DEBIANDIR} + ;; + -s|--systemd) + CHECK=qemu_check_systemd + BINFMT_SET=qemu_generate_systemd + EXPORTDIR=${EXPORTDIR:-$SYSTEMDDIR} + shift + # check given cpu is in the supported CPU list + if [ "$1" != "ALL" ] ; then + for cpu in ${qemu_target_list} ; do + if [ "$cpu" = "$1" ] ; then + break + fi + done + + if [ "$cpu" = "$1" ] ; then + qemu_target_list="$1" + else + echo "ERROR: unknown CPU \"$1\"" 1>&2 + usage + exit 1 + fi + fi + ;; + -Q|--qemu-path) + shift + QEMU_PATH="$1" + ;; + -F|--qemu-suffix) + shift + QEMU_SUFFIX="$1" + ;; + -e|--exportdir) + shift + EXPORTDIR="$1" + ;; + -h|--help) + usage + exit 1 + ;; + -c|--credential) + shift + CREDENTIAL="$1" + ;; + -p|--persistent) + shift + PERSISTENT="$1" + ;; + -g|--preserve-argv0) + shift + PRESERVE_ARG0="$1" + ;; + -i|--ignore-family) + shift + IGNORE_FAMILY="$1" + ;; + *) + break + ;; + esac + shift +done + +$CHECK +qemu_set_binfmts 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..e144cd5 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.14.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=('1e7f51ed0141f4573c65296f73d9e7005c897c1b2fb39085f3d6f7f95bbcefbf') +sha256sums_aarch64=('52c0d6810094183ea1dc2248a147e60afae98ce63f0b41975dafa7f63644ef20') 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 af0ea7f..4b6a14b 100644 --- a/pkgbuilds/strata/PKGBUILD +++ b/pkgbuilds/strata/PKGBUILD @@ -1,6 +1,6 @@ pkgname=strata -pkgver=0.15.0 -pkgrel=1 +pkgver=0.19.0 +pkgrel=2 pkgdesc='Fast, keyboard-first file manager for modern Linux desktops' arch=('x86_64' 'aarch64') url='https://github.com/lgse/strata' @@ -39,7 +39,7 @@ conflicts=('strata-git') options=('!debug' '!lto') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('be4d579a9378b8bf9ccc3904e7327ce756bed61d1e617c75b543e1e539611f0a') +sha256sums=('51701930728625ce1d6394949a4b6b2705f0999fd08be87f1a26d900209d62e0') prepare() { cd "$pkgname-$pkgver" @@ -69,7 +69,16 @@ build() { check() { cd "$pkgname-$pkgver" - export CARGO_TARGET_DIR=target + # Upstream's release profile is fat LTO with one codegen unit, which + # compiles the final crate on a single thread. That is the right trade + # for the binary users run, and build() keeps it. The test harness is a + # second compile of the same crate under the same profile, thrown away + # afterwards; build it in parallel, in its own target directory so the + # shipped binary is untouched. Measured on the x86_64 builder: 708 s + # serial, 223 s with thin LTO and 16 units. + export CARGO_TARGET_DIR=target-check + export CARGO_PROFILE_RELEASE_LTO=thin + export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16 export STRATA_BUILD_COMMIT STRATA_BUILD_COMMIT=$(<.build-commit) export STRATA_RELEASE_TAG="v$pkgver" @@ -84,8 +93,22 @@ check() { rm -rf -- "$search_tmp" (( test_status == 0 )) || return "$test_status" + # restart_waiter_* run `sh -c 'while kill -0 "$1"; do sleep; done'` with + # the pid u32::MAX. Arch's sh is bash, and bash's kill builtin in POSIX + # mode prints "not a pid or valid job spec" for that number but exits 0, + # so the loop never ends and the job hits its 180-minute timeout. + # Upstream CI runs on Ubuntu, where sh is dash and rejects the number. + # Skip until upstream waits on a real pid. + local skip=(--skip services::search::tests:: --skip ui::settings::tests::restart_waiter_) + # ownership_probe_errors_disable_in_place_updates points the pacman path + # at a directory and expects Command::output() to fail. aarch64 builds + # run under QEMU user-mode emulation, where glibc's posix_spawn cannot + # observe the child's failed execve (natively it returns EACCES); the + # spawn "succeeds" with exit 127, the probe reads that as "not owned", + # and the assertion fails. Passes natively. + [[ $CARCH == aarch64 ]] && skip+=(--skip services::update_install::tests::ownership_probe_errors_disable_in_place_updates) cargo test --frozen --release --all-targets --all-features \ - -- --test-threads=1 --skip services::search::tests:: + -- --test-threads=1 "${skip[@]}" } 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/sublime-text-4/PKGBUILD b/pkgbuilds/sublime-text-4/PKGBUILD index 7114b57..09cdc85 100644 --- a/pkgbuilds/sublime-text-4/PKGBUILD +++ b/pkgbuilds/sublime-text-4/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Manuel Hรผsers pkgname=sublime-text-4 -pkgver=4.4200 +pkgver=4.4213 pkgrel=1 pkgdesc='Sophisticated text editor for code, html and prose - stable build' arch=('x86_64' 'aarch64') @@ -16,8 +16,8 @@ source_x86_64=("${pkgname//-/_}_${pkgver/./_}_${pkgrel}_x64.tar.xz::https://down source_aarch64=("${pkgname//-/_}_${pkgver/./_}_${pkgrel}_arm64.tar.xz::https://download.sublimetext.com/sublime_text_build_${pkgver:2}_arm64.tar.xz") sha512sums=('ac56e9b7dddaebb3d222795cfc644109c93cc3f79695b8f9ee56022c74fe04a1134dd54cab07c74ff1f96b783cb3dbc026c16095552f1d2dd83115ea274dc2e9') -sha512sums_x86_64=('0a6ff4be7ae35ce80d568a2bf8dc5ed6fcf9f845517f7a1b8b24f180842f72ef16f9792e91fc70a277cfaf66bf1be482bb1328c98252d7c524d3412cfe9f22e3') -sha512sums_aarch64=('bb8f314e3c0ffff2536f91331fe43fe55d42fed27fca32bced5e779331296c7ee4b619dacc0193bc7f2cfa16a770177a81783ed959c786522e9688b028c7c221') +sha512sums_x86_64=('0d222ba954d7f6c5c7b03ce1eff3751e2d92b233058524208eb8e007c347b9a3628b9487e832c3c5599e7d2bcb940407466bd7b000a4e389f9ed916950bd049e') +sha512sums_aarch64=('e2ee9de786d1ca6ef28f6703626be226dc0b15f74a61ca4de58522fa7c9f295c8a38b052463d95878a8930366bf1f5d4740a876c7022e1655c4b906a0a83cef1') prepare() { sed -i -e "s|@ST_PATH@|/opt/sublime_text|g" "${pkgname}.sh" 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/.omarchy/upstream.sh b/pkgbuilds/t3code-bin/.omarchy/upstream.sh index 6bc010c..63c436d 100755 --- a/pkgbuilds/t3code-bin/.omarchy/upstream.sh +++ b/pkgbuilds/t3code-bin/.omarchy/upstream.sh @@ -1,8 +1,9 @@ #!/bin/bash # T3 Code publishes electron-builder's update feed beside every release, so the -# newest version costs one small request. The feed's checksum is a base64 +# newest version costs one small request. Each feed's checksum is a base64 # SHA-512 and makepkg wants hex SHA-256, so a release that is actually new still -# has to be downloaded once to hash -- hence the version check before the fetch. +# has to be downloaded once per architecture to hash -- hence the version check +# before the fetch. set -euo pipefail FEED_URL="https://github.com/pingdotgg/t3code/releases/latest/download/latest-linux.yml" @@ -23,7 +24,7 @@ if [[ -n "$current" ]] && [[ "$(vercmp "$version" "$current")" -le 0 ]]; then exit 0 fi -# The PKGBUILD builds one fixed asset name, so a feed naming anything else -- +# The PKGBUILD builds fixed asset names, so a feed naming anything else -- # a rename, or an arm64 build reaching the Linux feed first -- has to stop the # sync rather than pin that file's checksum to a URL nobody will fetch. expected="T3-Code-${version}-x86_64.AppImage" @@ -32,7 +33,18 @@ if [[ "$asset" != "$expected" ]]; then exit 1 fi -sha256=$(curl -fsSL "$RELEASE_URL/v${version}/${asset}" | sha256sum | cut -d' ' -f1) +# Pin the ARM feed to the same release, so a partially published release or a +# latest-release change cannot mix versions between architectures. +arm_feed=$(curl -fsSL "$RELEASE_URL/v${version}/latest-linux-arm64.yml") +arm_version=$(awk '/^version:/ { print $2; exit }' <<<"$arm_feed" | tr -d '"'\''') +arm_asset=$(awk '/^path:/ { print $2; exit }' <<<"$arm_feed" | tr -d '"'\''') +if [[ "$arm_version" != "$version" || "$arm_asset" != "T3-Code-${version}-arm64.AppImage" ]]; then + echo "Upstream ARM feed does not match T3-Code-${version}-arm64.AppImage" >&2 + exit 1 +fi -jq -n --arg pkgver "$version" --arg sha256 "$sha256" \ - '{pkgver: $pkgver, sha256sums: {x86_64: [$sha256]}}' +sha256=$(curl -fsSL "$RELEASE_URL/v${version}/${asset}" | sha256sum | cut -d' ' -f1) +arm_sha256=$(curl -fsSL "$RELEASE_URL/v${version}/${arm_asset}" | sha256sum | cut -d' ' -f1) + +jq -n --arg pkgver "$version" --arg sha256 "$sha256" --arg arm_sha256 "$arm_sha256" \ + '{pkgver: $pkgver, sha256sums: {x86_64: [$sha256], aarch64: [$arm_sha256]}}' diff --git a/pkgbuilds/t3code-bin/PKGBUILD b/pkgbuilds/t3code-bin/PKGBUILD index d848b90..235022d 100644 --- a/pkgbuilds/t3code-bin/PKGBUILD +++ b/pkgbuilds/t3code-bin/PKGBUILD @@ -1,18 +1,19 @@ # Maintainer: David Heinemeier Hansson -# T3 Code ships Linux as an AppImage and nothing else, so Omarchy unpacks it and +# T3 Code ships its Linux desktop as an AppImage, so Omarchy unpacks it and # keeps only the Electron tree. AppRun, the compatibility libraries bundled for # distributions that do not ship their own, and the AppImage's icon shims are all -# dead weight here. .omarchy/upstream.sh rewrites the version and checksum below -# from the release feed the app updates itself from. +# dead weight here. .omarchy/upstream.sh rewrites the version and checksums below +# from the release feeds the app updates itself from. pkgname=t3code-bin -pkgver=0.0.40 -pkgrel=1 +pkgver=0.0.42 +pkgrel=2 pkgdesc="Open-source control plane for coding agents" -arch=('x86_64') +arch=('x86_64' 'aarch64') url="https://t3.codes" license=('MIT') +makedepends=('7zip') depends=( 'alsa-lib' @@ -54,19 +55,28 @@ provides=("t3code=${pkgver}") conflicts=('t3code') options=('!debug' '!strip') -_appimage="T3-Code-${pkgver}-x86_64.AppImage" +_appimage_x86_64="T3-Code-${pkgver}-x86_64.AppImage" +_appimage_aarch64="T3-Code-${pkgver}-arm64.AppImage" source=('t3code-launcher.sh' 't3-launcher.sh' 'LICENSE') -source_x86_64=("${_appimage}::https://github.com/pingdotgg/t3code/releases/download/v${pkgver}/${_appimage}") -noextract=("${_appimage}") +source_x86_64=("https://github.com/pingdotgg/t3code/releases/download/v${pkgver}/${_appimage_x86_64}") +source_aarch64=("https://github.com/pingdotgg/t3code/releases/download/v${pkgver}/${_appimage_aarch64}") +noextract=("${_appimage_x86_64}" "${_appimage_aarch64}") sha256sums=('cb905ff341372ef2ef6e402cf485959f8bd1df8f0efebee4cda1afdd5e6abc0a' 'c5b3f2a9f0b14b12cfd973b79319f0f018b7ae49a1d43d8ca346100c3f7de28f' '935d8f2af0c703f9c39517ee57cc4930b19d02d533be930b63f0e82f93614b43') -sha256sums_x86_64=('8bf5fd44cb7fad0c43191d54fefdf974a8227d50505ecb8abcf76326209f264a') +sha256sums_x86_64=('8dc1fccdabc2ed3a59a3944cc772ef11931b9351401c0963ed305d5f96e3cdf4') +sha256sums_aarch64=('c256d872d358e9f2c91328b0154c6311fa2eb16386ef6f788fcda12d73cf2836') prepare() { - chmod +x "${srcdir}/${_appimage}" + local _appimage + case "$CARCH" in + x86_64) _appimage="${_appimage_x86_64}" ;; + aarch64) _appimage="${_appimage_aarch64}" ;; + esac rm -rf "${srcdir}/squashfs-root" - "${srcdir}/${_appimage}" --appimage-extract >/dev/null + # Extract without executing the runtime: AppImage's ELF magic does not match + # QEMU's binfmt registration when building ARM packages on an x86_64 host. + 7z x "${srcdir}/${_appimage}" -o"${srcdir}/squashfs-root" >/dev/null } package() { 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/ttfx/PKGBUILD b/pkgbuilds/ttfx/PKGBUILD index 4079045..1a386f7 100644 --- a/pkgbuilds/ttfx/PKGBUILD +++ b/pkgbuilds/ttfx/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: David Heinemeier Hansson pkgname=ttfx -pkgver=0.3.2 +pkgver=0.3.3 pkgrel=1 pkgdesc="Terminal text effects as a single static binary โ€” Rust port of terminaltexteffects" arch=('x86_64' 'aarch64') @@ -12,7 +12,7 @@ makedepends=('cargo') options=('!debug') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('d0c0df4867e7f03142fb7f77c66670d0e8da15534239c1a7abfd89f19dfc00f6') +sha256sums=('d040da0da2f4a952a367fa3d934ac25999265405f1d2a9f0475625e41211fa7d') prepare() { cd "$pkgname-$pkgver" 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/uboot-asahi/.omarchy/package.json b/pkgbuilds/uboot-asahi/.omarchy/package.json new file mode 100644 index 0000000..dd11ed8 --- /dev/null +++ b/pkgbuilds/uboot-asahi/.omarchy/package.json @@ -0,0 +1,5 @@ +{ + "source": "local", + "channels": ["edge"], + "sync": false +} diff --git a/pkgbuilds/uboot-asahi/Allwinner-fix-booting-on-a-number-of-devices.patch b/pkgbuilds/uboot-asahi/Allwinner-fix-booting-on-a-number-of-devices.patch new file mode 100644 index 0000000..2b08e13 --- /dev/null +++ b/pkgbuilds/uboot-asahi/Allwinner-fix-booting-on-a-number-of-devices.patch @@ -0,0 +1,27 @@ +From b8ea3cebde98866b7cbb4e8f9bb6dfdee529a549 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Wed, 27 Aug 2025 00:23:28 +0100 +Subject: [PATCH] Allwinner: fix booting on a number of devices + +Revert "sunxi: enable MMU_PGPROT proper page table protection" + +This reverts commit 16cfccda4dbf0b53473b8212897f5c63bfbeb6e1. +--- + arch/arm/Kconfig | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig +index 40368abc297..b8be270e4d7 100644 +--- a/arch/arm/Kconfig ++++ b/arch/arm/Kconfig +@@ -1207,7 +1207,6 @@ config ARCH_SUNXI + select DM_SPI_FLASH if SPI && MTD + select DM_KEYBOARD + select DM_SERIAL +- select MMU_PGPROT if ARM64 + select OF_BOARD_SETUP + select OF_CONTROL + select PINCTRL +-- +2.51.0 + diff --git a/pkgbuilds/uboot-asahi/Fix-NVMe-not-only-on-Raspberry-Pi-5.patch b/pkgbuilds/uboot-asahi/Fix-NVMe-not-only-on-Raspberry-Pi-5.patch new file mode 100644 index 0000000..62f87ed --- /dev/null +++ b/pkgbuilds/uboot-asahi/Fix-NVMe-not-only-on-Raspberry-Pi-5.patch @@ -0,0 +1,738 @@ +From patchwork Mon Jul 6 17:21:47 2026 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Torsten Duwe +X-Patchwork-Id: 2265604 +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de + (client-ip=85.214.62.61; helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) +Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4gvB431Qwzz1xqc + for ; Tue, 07 Jul 2026 03:21:55 +1000 (AEST) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id 179C084942; + Mon, 6 Jul 2026 19:21:52 +0200 (CEST) +Authentication-Results: phobos.denx.de; + dmarc=fail (p=none dis=none) header.from=lst.de +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Received: by phobos.denx.de (Postfix, from userid 109) + id 82ADE8490A; Mon, 6 Jul 2026 19:21:51 +0200 (CEST) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, + RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_PASS autolearn=ham + autolearn_force=no version=3.4.2 +Received: from verein.lst.de (verein.lst.de [213.95.11.211]) + (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id 9F24C846B4 + for ; Mon, 6 Jul 2026 19:21:49 +0200 (CEST) +Authentication-Results: phobos.denx.de; + dmarc=fail (p=none dis=none) header.from=lst.de +Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=duwe@lst.de +Received: by verein.lst.de (Postfix, from userid 2005) + id C934268B05; Mon, 6 Jul 2026 19:21:47 +0200 (CEST) +In-Reply-To: <20260706172028.C148E68BEB@verein.lst.de> +References: <20260706172028.C148E68BEB@verein.lst.de> +Subject: [PATCH v2 1/4] core: Skip parent device nodes without a DT reference + when looking for dma-ranges +To: Neil Armstrong , + Andrew Goodbody , Bin Meng , + Simon Glass +Cc: Tom Rini , Matthias Brugger , + Peter Robinson , + Mattijs Korpershoek , + Marek Vasut , + Heiko Schocher , Svyatoslav Ryhel , + "Lucien.Jheng" , Rasmus Villemoes , + Martin Schwan , + "Markus Schneider-Pargmann (TI.com)" , + Prashant Kamble , + Denis Mukhin , Sughosh Ganu , + u-boot@lists.denx.de +Message-Id: <20260706172147.C934268B05@verein.lst.de> +Date: Mon, 6 Jul 2026 19:21:47 +0200 (CEST) +From: duwe@lst.de (Torsten Duwe) +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.39 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean + +From: Torsten Duwe + +If a device node got created dynamically, there is no guarantee that the +parent node has an associated device tree node which could specify dma +constraints. Especially PCI(e) enumeration adds intermediate "bus nodes", +also dynamically. + +Try harder to find the correct configuration by walking up the tree until +a DT association is found. + +Suggested-by: Neil Armstrong +Signed-off-by: Torsten Duwe +Reviewed-by: Peter Robinson +Tested-by: Peter Robinson +--- + drivers/core/device.c | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/drivers/core/device.c b/drivers/core/device.c +index d365204ba11..b6d00cf4714 100644 +--- a/drivers/core/device.c ++++ b/drivers/core/device.c +@@ -459,7 +459,19 @@ static int device_get_dma_constraints(struct udevice *dev) + u64 size = 0; + int ret; + +- if (!CONFIG_IS_ENABLED(DM_DMA) || !parent || !dev_has_ofnode(parent)) ++ if (!CONFIG_IS_ENABLED(DM_DMA) || !parent) ++ return 0; ++ ++ /* Look for the first node in the parent chain */ ++ while (parent) { ++ if (dev_has_ofnode(parent)) ++ break; ++ ++ parent = dev_get_parent(parent); ++ } ++ ++ /* No parents have a node, bail out */ ++ if (!parent) + return 0; + + /* + +From patchwork Mon Jul 6 17:21:50 2026 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Torsten Duwe +X-Patchwork-Id: 2265605 +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de + (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) +Received: from phobos.denx.de (phobos.denx.de + [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange x25519) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4gvB481pfXz1xqc + for ; Tue, 07 Jul 2026 03:22:00 +1000 (AEST) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id 9A59684994; + Mon, 6 Jul 2026 19:21:55 +0200 (CEST) +Authentication-Results: phobos.denx.de; + dmarc=fail (p=none dis=none) header.from=lst.de +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Received: by phobos.denx.de (Postfix, from userid 109) + id EB7D384979; Mon, 6 Jul 2026 19:21:53 +0200 (CEST) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, + RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_PASS autolearn=ham + autolearn_force=no version=3.4.2 +Received: from verein.lst.de (verein.lst.de [213.95.11.211]) + (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id EE61C846B4 + for ; Mon, 6 Jul 2026 19:21:51 +0200 (CEST) +Authentication-Results: phobos.denx.de; + dmarc=fail (p=none dis=none) header.from=lst.de +Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=duwe@lst.de +Received: by verein.lst.de (Postfix, from userid 2005) + id 464E868BEB; Mon, 6 Jul 2026 19:21:50 +0200 (CEST) +In-Reply-To: <20260706172028.C148E68BEB@verein.lst.de> +References: <20260706172028.C148E68BEB@verein.lst.de> +Subject: [PATCH v2 2/4] test: Add a unit test in phys_to_bus for dynamically + created device nodes +To: Neil Armstrong , + Andrew Goodbody , Bin Meng , + Simon Glass +Cc: Tom Rini , Matthias Brugger , + Peter Robinson , + Mattijs Korpershoek , + Marek Vasut , + Heiko Schocher , Svyatoslav Ryhel , + "Lucien.Jheng" , Rasmus Villemoes , + Martin Schwan , + "Markus Schneider-Pargmann (TI.com)" , + Prashant Kamble , + Denis Mukhin , Sughosh Ganu , + u-boot@lists.denx.de +Message-Id: <20260706172150.464E868BEB@verein.lst.de> +Date: Mon, 6 Jul 2026 19:21:50 +0200 (CEST) +From: duwe@lst.de (Torsten Duwe) +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.39 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean + +From: Torsten Duwe + +In order to test whether device_get_dma_constraints() is now able to skip +device nodes without a DT association, temporarily remove those on 2 newly- +created nodes. + +Signed-off-by: Torsten Duwe +--- + +This test indeed makes a different whether the previous change is +applied or not, but *only* reliably for the non-flat-tree case (!?). +On the flat tree, it always fails after (reverse) applying it. + +Either the previous patch is incomplete, or, more likely, the test +assumptions here are not all valid. Suggestions welcome (especially +how to create device nodes from a device tree without a reference to +their origin ;-) + +--- + arch/sandbox/dts/test.dts | 9 +++++++++ + test/dm/phys2bus.c | 25 ++++++++++++++++++++++++- + 2 files changed, 33 insertions(+), 1 deletion(-) + +diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts +index 0887de4333b..1d92e85c739 100644 +--- a/arch/sandbox/dts/test.dts ++++ b/arch/sandbox/dts/test.dts +@@ -656,6 +656,15 @@ + subnode@0 { + compatible = "denx,u-boot-fdt-test"; + }; ++ bus@7 { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ compatible = "denx,u-boot-test-bus"; ++ ++ subnode@8 { ++ compatible = "denx,u-boot-fdt-test"; ++ }; ++ }; + }; + + mmio-bus@1 { +diff --git a/test/dm/phys2bus.c b/test/dm/phys2bus.c +index 67f33904943..858ed5410e2 100644 +--- a/test/dm/phys2bus.c ++++ b/test/dm/phys2bus.c +@@ -15,7 +15,8 @@ + + static int dm_test_phys_to_bus(struct unit_test_state *uts) + { +- struct udevice *dev; ++ struct udevice *dev, *pdev; ++ ofnode psave, nsave; + ofnode node; + + node = ofnode_path("/mmio-bus@0"); +@@ -31,6 +32,29 @@ static int dm_test_phys_to_bus(struct unit_test_state *uts) + ut_asserteq_addr((void*)0x1003ffffULL, (void*)dev_phys_to_bus(dev, 0x3ffff)); + ut_asserteq_addr((void*)0x3ffffULL, (void*)(ulong)dev_bus_to_phys(dev, 0x1003ffff)); + ++#if CONFIG_IS_ENABLED(OF_REAL) ++ /* dma-ranges are on the grandparent bus node, ++ * OF nodes neither here nor at parent ++ */ ++ node = ofnode_path("/mmio-bus@0/bus@7"); ++ ut_assert(ofnode_valid(node)); ++ ut_assertok(uclass_get_device_by_ofnode(UCLASS_TEST_BUS, node, &pdev)); ++ ++ /* pretend the devices were discovered dynamically and have no OF node */ ++ psave = pdev->node_; ++ pdev->node_.np = NULL; ++ node = ofnode_path("/mmio-bus@0/bus@7/subnode@8"); ++ ut_assert(ofnode_valid(node)); ++ nsave = dev->node_; ++ dev->node_.np = NULL; ++ ut_assertok(uclass_get_device_by_ofnode(UCLASS_TEST_FDT, node, &dev)); ++ ++ ut_asserteq_addr((void *)0x1003ffffULL, (void *)dev_phys_to_bus(dev, 0x3ffff)); ++ ut_asserteq_addr((void *)0x3ffffULL, (void *)(ulong)dev_bus_to_phys(dev, 0x1003ffff)); ++ ++ pdev->node_ = psave; ++ dev->node_ = nsave; ++#endif + return 0; + } + DM_TEST(dm_test_phys_to_bus, UTF_SCAN_PDATA | UTF_SCAN_FDT); + +From patchwork Mon Jul 6 17:21:52 2026 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Torsten Duwe +X-Patchwork-Id: 2265606 +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de + (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) +Received: from phobos.denx.de (phobos.denx.de + [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4gvB4H70Bdz1xqc + for ; Tue, 07 Jul 2026 03:22:07 +1000 (AEST) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id 00AF0849BA; + Mon, 6 Jul 2026 19:21:57 +0200 (CEST) +Authentication-Results: phobos.denx.de; + dmarc=fail (p=none dis=none) header.from=lst.de +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Received: by phobos.denx.de (Postfix, from userid 109) + id 25F8F849BA; Mon, 6 Jul 2026 19:21:56 +0200 (CEST) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, + RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_PASS autolearn=ham + autolearn_force=no version=3.4.2 +Received: from verein.lst.de (verein.lst.de [213.95.11.211]) + (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id 06F20846B4 + for ; Mon, 6 Jul 2026 19:21:54 +0200 (CEST) +Authentication-Results: phobos.denx.de; + dmarc=fail (p=none dis=none) header.from=lst.de +Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=duwe@lst.de +Received: by verein.lst.de (Postfix, from userid 2005) + id 5257168BFE; Mon, 6 Jul 2026 19:21:52 +0200 (CEST) +In-Reply-To: <20260706172028.C148E68BEB@verein.lst.de> +References: <20260706172028.C148E68BEB@verein.lst.de> +Subject: [PATCH v2 3/4] nvme: Fix missing address translation for PCIe inbound + access +To: Neil Armstrong , + Andrew Goodbody , Bin Meng , + Simon Glass +Cc: Tom Rini , Matthias Brugger , + Peter Robinson , + Mattijs Korpershoek , + Marek Vasut , + Heiko Schocher , Svyatoslav Ryhel , + "Lucien.Jheng" , Rasmus Villemoes , + Martin Schwan , + "Markus Schneider-Pargmann (TI.com)" , + Prashant Kamble , + Denis Mukhin , Sughosh Ganu , + u-boot@lists.denx.de +Message-Id: <20260706172152.5257168BFE@verein.lst.de> +Date: Mon, 6 Jul 2026 19:21:52 +0200 (CEST) +From: duwe@lst.de (Torsten Duwe) +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.39 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean + +From: Torsten Duwe + +U-Boot currently does not account for PCIe bridges with a non-zero +inbound access offset when talking NVMe, it only works on platforms +where this offset happens to be zero. + +This patch enhances the NVMe driver with the ability to also handle +these cases. It also removes the unused function nvme_get_features(). + +Signed-off-by: Torsten Duwe +Reviewed-by: Peter Robinson +Tested-by: Peter Robinson +--- +I tried to consistently use uintptr_t when doing arithmetic on pointers +and dma_addr_t to hold, well, DMA addresses ;) + +nvme_get_features mentions it's unused; so far it causes fruitless +maintenance work and causes dead extra bytes in das U-Boot binary. +It hasn't been neccessary for 9 years; one can revive it from git +once needed. + +--- + drivers/nvme/nvme.c | 71 +++++++++++++++++---------------------------- + drivers/nvme/nvme.h | 3 ++ + 2 files changed, 29 insertions(+), 45 deletions(-) + +diff --git a/drivers/nvme/nvme.c b/drivers/nvme/nvme.c +index 147a104149e..54284c0242b 100644 +--- a/drivers/nvme/nvme.c ++++ b/drivers/nvme/nvme.c +@@ -8,10 +8,10 @@ + #include + #include + #include +-#include + #include + #include + #include ++#include + #include + #include + #include +@@ -101,6 +101,8 @@ static int nvme_setup_prps(struct nvme_dev *dev, u64 *prp2, + printf("Error: malloc prp_pool fail\n"); + return -ENOMEM; + } ++ dev->prp_pool_dma = dev_phys_to_bus(dev->udev, ++ (uintptr_t)dev->prp_pool); + dev->prp_entry_num = num_pages * (prps_per_page - 1) + 1; + } + +@@ -108,8 +110,10 @@ static int nvme_setup_prps(struct nvme_dev *dev, u64 *prp2, + i = 0; + while (nprps) { + if ((i == (prps_per_page - 1)) && nprps > 1) { +- *(prp_pool + i) = cpu_to_le64((ulong)prp_pool + +- page_size); ++ dma_addr_t prp_pool_dma = ++ dev_phys_to_bus(dev->udev, (uintptr_t)prp_pool); ++ *(prp_pool + i) = cpu_to_le64(prp_pool_dma + ++ page_size); + i = 0; + prp_pool = (u64 *)((uintptr_t)prp_pool + page_size); + } +@@ -117,7 +121,7 @@ static int nvme_setup_prps(struct nvme_dev *dev, u64 *prp2, + dma_addr += page_size; + nprps--; + } +- *prp2 = (ulong)dev->prp_pool; ++ *prp2 = dev->prp_pool_dma; + + flush_dcache_range((ulong)dev->prp_pool, (ulong)dev->prp_pool + + num_pages * page_size); +@@ -258,11 +262,15 @@ static struct nvme_queue *nvme_alloc_queue(struct nvme_dev *dev, + nvmeq->cqes = (void *)memalign(4096, NVME_CQ_ALLOCATION); + if (!nvmeq->cqes) + goto free_nvmeq; ++ nvmeq->cq_dma_addr = dev_phys_to_bus(dev->udev, ++ (uintptr_t)nvmeq->cqes); + memset((void *)nvmeq->cqes, 0, NVME_CQ_SIZE(depth)); + + nvmeq->sq_cmds = (void *)memalign(4096, NVME_SQ_SIZE(depth)); + if (!nvmeq->sq_cmds) + goto free_queue; ++ nvmeq->sq_dma_addr = dev_phys_to_bus(dev->udev, ++ (uintptr_t)nvmeq->sq_cmds); + memset((void *)nvmeq->sq_cmds, 0, NVME_SQ_SIZE(depth)); + + nvmeq->dev = dev; +@@ -410,8 +416,8 @@ static int nvme_configure_admin_queue(struct nvme_dev *dev) + dev->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES; + + writel(aqa, &dev->bar->aqa); +- nvme_writeq((ulong)nvmeq->sq_cmds, &dev->bar->asq); +- nvme_writeq((ulong)nvmeq->cqes, &dev->bar->acq); ++ nvme_writeq(nvmeq->sq_dma_addr, &dev->bar->asq); ++ nvme_writeq(nvmeq->cq_dma_addr, &dev->bar->acq); + + result = nvme_enable_ctrl(dev); + if (result) +@@ -437,7 +443,7 @@ static int nvme_alloc_cq(struct nvme_dev *dev, u16 qid, + + memset(&c, 0, sizeof(c)); + c.create_cq.opcode = nvme_admin_create_cq; +- c.create_cq.prp1 = cpu_to_le64((ulong)nvmeq->cqes); ++ c.create_cq.prp1 = cpu_to_le64(nvmeq->cq_dma_addr); + c.create_cq.cqid = cpu_to_le16(qid); + c.create_cq.qsize = cpu_to_le16(nvmeq->q_depth - 1); + c.create_cq.cq_flags = cpu_to_le16(flags); +@@ -454,7 +460,7 @@ static int nvme_alloc_sq(struct nvme_dev *dev, u16 qid, + + memset(&c, 0, sizeof(c)); + c.create_sq.opcode = nvme_admin_create_sq; +- c.create_sq.prp1 = cpu_to_le64((ulong)nvmeq->sq_cmds); ++ c.create_sq.prp1 = cpu_to_le64(nvmeq->sq_dma_addr); + c.create_sq.sqid = cpu_to_le16(qid); + c.create_sq.qsize = cpu_to_le16(nvmeq->q_depth - 1); + c.create_sq.sq_flags = cpu_to_le16(flags); +@@ -464,13 +470,13 @@ static int nvme_alloc_sq(struct nvme_dev *dev, u16 qid, + } + + int nvme_identify(struct nvme_dev *dev, unsigned nsid, +- unsigned cns, dma_addr_t dma_addr) ++ unsigned cns, dma_addr_t cpu_addr) + { + struct nvme_command c; + u32 page_size = dev->page_size; ++ dma_addr_t dma_addr = dev_phys_to_bus(dev->udev, cpu_addr); + int offset = dma_addr & (page_size - 1); + int length = sizeof(struct nvme_id_ctrl); +- dma_addr_t orig_dma_addr = dma_addr; + int ret; + + memset(&c, 0, sizeof(c)); +@@ -488,49 +494,22 @@ int nvme_identify(struct nvme_dev *dev, unsigned nsid, + + c.identify.cns = cpu_to_le32(cns); + +- nvme_invalidate_cache_aligned((uintptr_t)orig_dma_addr, ++ nvme_invalidate_cache_aligned((uintptr_t)cpu_addr, + sizeof(struct nvme_id_ctrl)); + + ret = nvme_submit_admin_cmd(dev, &c, NULL); + if (!ret) +- nvme_invalidate_cache_aligned((uintptr_t)orig_dma_addr, ++ nvme_invalidate_cache_aligned((uintptr_t)cpu_addr, + sizeof(struct nvme_id_ctrl)); + + return ret; + } + +-int nvme_get_features(struct nvme_dev *dev, unsigned fid, unsigned nsid, +- dma_addr_t dma_addr, u32 *result) +-{ +- struct nvme_command c; +- int ret; +- +- memset(&c, 0, sizeof(c)); +- c.features.opcode = nvme_admin_get_features; +- c.features.nsid = cpu_to_le32(nsid); +- c.features.prp1 = cpu_to_le64(dma_addr); +- c.features.fid = cpu_to_le32(fid); +- +- ret = nvme_submit_admin_cmd(dev, &c, result); +- +- /* +- * TODO: Add some cache invalidation when a DMA buffer is involved +- * in the request, here and before the command gets submitted. The +- * buffer size varies by feature, also some features use a different +- * field in the command packet to hold the buffer address. +- * Section 5.21.1 (Set Features command) in the NVMe specification +- * details the buffer requirements for each feature. +- * +- * At the moment there is no user of this function. +- */ +- +- return ret; +-} +- + int nvme_set_features(struct nvme_dev *dev, unsigned fid, unsigned dword11, +- dma_addr_t dma_addr, u32 *result) ++ dma_addr_t cpu_addr, u32 *result) + { + struct nvme_command c; ++ dma_addr_t dma_addr = dev_phys_to_bus(dev->udev, cpu_addr); + + memset(&c, 0, sizeof(c)); + c.features.opcode = nvme_admin_set_features; +@@ -769,7 +748,7 @@ static ulong nvme_blk_rw(struct udevice *udev, lbaint_t blknr, + u64 prp2; + u64 total_len = blkcnt << desc->log2blksz; + u64 temp_len = total_len; +- uintptr_t temp_buffer = (uintptr_t)buffer; ++ uintptr_t dma_addr = dev_phys_to_bus(udev, (uintptr_t)buffer); + + u64 slba = blknr; + u16 lbas = 1 << (dev->max_transfer_shift - ns->lba_shift); +@@ -797,19 +776,19 @@ static ulong nvme_blk_rw(struct udevice *udev, lbaint_t blknr, + } + + if (nvme_setup_prps(dev, &prp2, +- lbas << ns->lba_shift, temp_buffer)) ++ lbas << ns->lba_shift, dma_addr)) + return -EIO; + c.rw.slba = cpu_to_le64(slba); + slba += lbas; + c.rw.length = cpu_to_le16(lbas - 1); +- c.rw.prp1 = cpu_to_le64(temp_buffer); ++ c.rw.prp1 = cpu_to_le64(dma_addr); + c.rw.prp2 = cpu_to_le64(prp2); + status = nvme_submit_sync_cmd(dev->queues[NVME_IO_Q], + &c, NULL, IO_TIMEOUT); + if (status) + break; + temp_len -= (u32)lbas << ns->lba_shift; +- temp_buffer += lbas << ns->lba_shift; ++ dma_addr += lbas << ns->lba_shift; + } + + if (read) +@@ -884,6 +863,8 @@ int nvme_init(struct udevice *udev) + printf("Error: %s: Out of memory!\n", udev->name); + goto free_queue; + } ++ ++ ndev->prp_pool_dma = dev_phys_to_bus(udev, (uintptr_t)ndev->prp_pool); + ndev->prp_entry_num = MAX_PRP_POOL >> 3; + + ret = nvme_setup_io_queues(ndev); +diff --git a/drivers/nvme/nvme.h b/drivers/nvme/nvme.h +index bc1d612dde4..7c6ed4d9b55 100644 +--- a/drivers/nvme/nvme.h ++++ b/drivers/nvme/nvme.h +@@ -619,6 +619,7 @@ struct nvme_dev { + u32 page_size; + u8 vwc; + u64 *prp_pool; ++ uintptr_t prp_pool_dma; + u32 prp_entry_num; + u32 nn; + }; +@@ -637,7 +638,9 @@ enum nvme_queue_id { + struct nvme_queue { + struct nvme_dev *dev; + struct nvme_command *sq_cmds; ++ dma_addr_t sq_dma_addr; + struct nvme_completion *cqes; ++ dma_addr_t cq_dma_addr; + u32 __iomem *q_db; + u16 q_depth; + s16 cq_vector; + +From patchwork Mon Jul 6 17:21:54 2026 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Torsten Duwe +X-Patchwork-Id: 2265607 +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de + (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) +Received: from phobos.denx.de (phobos.denx.de + [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange x25519) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4gvB4R378Lz1xqc + for ; Tue, 07 Jul 2026 03:22:15 +1000 (AEST) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id 3FDAE8490A; + Mon, 6 Jul 2026 19:21:59 +0200 (CEST) +Authentication-Results: phobos.denx.de; + dmarc=fail (p=none dis=none) header.from=lst.de +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Received: by phobos.denx.de (Postfix, from userid 109) + id 17F418490A; Mon, 6 Jul 2026 19:21:58 +0200 (CEST) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, + RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_PASS autolearn=ham + autolearn_force=no version=3.4.2 +Received: from verein.lst.de (verein.lst.de [213.95.11.211]) + (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id 1D593849A4 + for ; Mon, 6 Jul 2026 19:21:56 +0200 (CEST) +Authentication-Results: phobos.denx.de; + dmarc=fail (p=none dis=none) header.from=lst.de +Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=duwe@lst.de +Received: by verein.lst.de (Postfix, from userid 2005) + id B506B68CFE; Mon, 6 Jul 2026 19:21:54 +0200 (CEST) +In-Reply-To: <20260706172028.C148E68BEB@verein.lst.de> +References: <20260706172028.C148E68BEB@verein.lst.de> +Subject: [PATCH v2 4/4] configs: enable NVMe +To: Neil Armstrong , + Andrew Goodbody , Bin Meng , + Simon Glass +Cc: Tom Rini , Matthias Brugger , + Peter Robinson , + Mattijs Korpershoek , + Marek Vasut , + Heiko Schocher , Svyatoslav Ryhel , + "Lucien.Jheng" , Rasmus Villemoes , + Martin Schwan , + "Markus Schneider-Pargmann (TI.com)" , + Prashant Kamble , + Denis Mukhin , Sughosh Ganu , + u-boot@lists.denx.de +Message-Id: <20260706172154.B506B68CFE@verein.lst.de> +Date: Mon, 6 Jul 2026 19:21:54 +0200 (CEST) +From: duwe@lst.de (Torsten Duwe) +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.39 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean + +From: Torsten Duwe + +Enable NVMe in the Raspberry Pi 64-Bit default config and scan for devices. + +Signed-off-by: Torsten Duwe +Reviewed-by: Peter Robinson +Tested-by: Peter Robinson +--- + configs/rpi_arm64_defconfig | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/configs/rpi_arm64_defconfig b/configs/rpi_arm64_defconfig +index cdcf05ea6db..fb0fdce1d7f 100644 +--- a/configs/rpi_arm64_defconfig ++++ b/configs/rpi_arm64_defconfig +@@ -16,7 +16,7 @@ CONFIG_BOOTSTD_DEFAULTS=y + CONFIG_OF_BOARD_SETUP=y + CONFIG_FDT_SIMPLEFB=y + CONFIG_USE_PREBOOT=y +-CONFIG_PREBOOT="pci enum; usb start;" ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_SYS_PBSIZE=1049 + # CONFIG_DISPLAY_CPUINFO is not set + # CONFIG_DISPLAY_BOARDINFO is not set +@@ -44,6 +44,7 @@ CONFIG_MMC_SDHCI_SDMA=y + CONFIG_MMC_SDHCI_BCM2835=y + CONFIG_MMC_SDHCI_BCMSTB=y + CONFIG_BCMGENET=y ++CONFIG_NVME_PCI=y + CONFIG_PCI_BRCMSTB=y + CONFIG_PINCTRL=y + # CONFIG_PINCTRL_GENERIC is not set diff --git a/pkgbuilds/uboot-asahi/JetsonTX2-Fix-upstream-device-tree-naming.patch b/pkgbuilds/uboot-asahi/JetsonTX2-Fix-upstream-device-tree-naming.patch new file mode 100644 index 0000000..14242ce --- /dev/null +++ b/pkgbuilds/uboot-asahi/JetsonTX2-Fix-upstream-device-tree-naming.patch @@ -0,0 +1,50 @@ +From 4ee5ba36570d94ce27e4023f15b8b3a6b8b9daf9 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Mon, 13 Oct 2025 21:41:13 +0100 +Subject: [PATCH] config: JetsonTX2: Fix upstream device tree naming + +The upstream naming for the Jetson TX2 is p2771-0000 so fix +the device tree name so the firmware will find the right +name on disk. + +Signed-off-by: Peter Robinson +--- + arch/arm/dts/Makefile | 2 +- + .../{tegra186-p2771-0000-500.dts => tegra186-p2771-0000.dts} | 0 + configs/p2771-0000-500_defconfig | 2 +- + 3 files changed, 2 insertions(+), 2 deletions(-) + rename arch/arm/dts/{tegra186-p2771-0000-500.dts => tegra186-p2771-0000.dts} (100%) + +diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile +index 7c8cf3a5a1d..86a7b4fda35 100644 +--- a/arch/arm/dts/Makefile ++++ b/arch/arm/dts/Makefile +@@ -134,7 +134,7 @@ dtb-$(CONFIG_ARCH_TEGRA) += \ + tegra124-venice2.dtb \ + tegra124-xiaomi-mocha.dtb \ + tegra186-p2771-0000-000.dtb \ +- tegra186-p2771-0000-500.dtb \ ++ tegra186-p2771-0000.dtb \ + tegra210-p2371-0000.dtb \ + tegra210-p2371-2180.dtb \ + tegra210-p2571.dtb \ +diff --git a/arch/arm/dts/tegra186-p2771-0000-500.dts b/arch/arm/dts/tegra186-p2771-0000.dts +similarity index 100% +rename from arch/arm/dts/tegra186-p2771-0000-500.dts +rename to arch/arm/dts/tegra186-p2771-0000.dts +diff --git a/configs/p2771-0000-500_defconfig b/configs/p2771-0000-500_defconfig +index 42043277e2d..e1e5c47ddfa 100644 +--- a/configs/p2771-0000-500_defconfig ++++ b/configs/p2771-0000-500_defconfig +@@ -7,7 +7,7 @@ CONFIG_TEXT_BASE=0x80080000 + CONFIG_NR_DRAM_BANKS=1026 + CONFIG_ENV_SIZE=0x2000 + CONFIG_ENV_OFFSET=0xFFFFE000 +-CONFIG_DEFAULT_DEVICE_TREE="tegra186-p2771-0000-500" ++CONFIG_DEFAULT_DEVICE_TREE="tegra186-p2771-0000" + CONFIG_SYS_BOOTM_LEN=0x800000 + CONFIG_SYS_LOAD_ADDR=0x80080000 + CONFIG_TEGRA186=y +-- +2.51.0 + diff --git a/pkgbuilds/uboot-asahi/PKGBUILD b/pkgbuilds/uboot-asahi/PKGBUILD new file mode 100644 index 0000000..769a083 --- /dev/null +++ b/pkgbuilds/uboot-asahi/PKGBUILD @@ -0,0 +1,164 @@ +# Omarchy's uboot-asahi: asahi-alarm's uboot-asahi (asahi-alarm/PKGBUILDs) +# with a silent console, no banner, no logo and no boot delay, so the Mac +# boots straight to the boot menu and the splash without U-Boot's text. +# +# Same name as asahi-alarm's, and nothing generic depends on it: it is only +# installed when named, or as an upgrade of an installed uboot-asahi. +# pacman takes a package from the first repository that carries its name: +# with [omarchy] ahead of [asahi-alarm] this build is installed and kept even +# after asahi-alarm moves on; with [asahi-alarm] ahead it is installed only as +# omarchy/uboot-asahi, kept over their build at the same pkgver, and replaced +# by a newer one. Keep pkgver and the patch set in step with asahi-alarm's and +# pkgrel above theirs. No epoch, which would outrank every future asahi-alarm +# release. + +pkgname=uboot-asahi +_ver=2026.07 +_asahirel=2 + +pkgver=${_ver}.asahi${_asahirel} +pkgrel=4 +pkgdesc='U-Boot for Apple Silicon Macs (Omarchy build: silent console)' +_commit_id=asahi-v${_ver}-${_asahirel} +_srcname=u-boot-${_commit_id} +arch=('aarch64') +url='http://asahilinux.org' +license=('MIT' 'GPL2') +makedepends=( bc imagemagick ) +source=( + "u-boot-${_commit_id}.tar.gz::https://github.com/AsahiLinux/u-boot/archive/${_commit_id}.tar.gz" + 'disable-VBE-by-default.patch' + 'enable-bootmenu-by-default.patch' + 'uefi-distro-load-FDT-from-any-partition-on-boot-device.patch' + 'uefi-Add-all-options-for-EFI-System-Partitions.patch' + 'uefi-initial-find_fdt_location-for-finding-the-DT-on-disk.patch' + 'uefi-enable-SetVariableRT-with-volotile-storage.patch' + 'uefi-enable-https-boot-by-default.patch' + 'pylibfdt-Replace-removed-SWIG-Python-2-compatibility-macros.patch' + 'USB-PD-TCPM-improvements.patch' + 'rockchip-Enable-preboot-start-for-pci-usb.patch' + 'rockchip-rk3568-nanopi-r5-Drop-duplicated-extra-sdhc.patch' + 'rockchip-rk356x-Stop-overriding-sdhci-mmc-aliases.patch' + 'p3450-fix-board.patch' + 'JetsonTX2-Fix-upstream-device-tree-naming.patch' + 'Allwinner-fix-booting-on-a-number-of-devices.patch' + 'Fix-NVMe-not-only-on-Raspberry-Pi-5.patch' + 'raspberrypi-Add-quirk-for-RPi5-2Gb-rev-1.0.patch' + 'openssl-no-engine.patch' + 'quiet-console.patch' +) +sha256sums=('ff36bacdeb91c2bb911ce6fdd198fb1a943c47a6931f986cecd22443b04c1b06' + '501fe48668729525520253228945dc728c9bf2d9f0a41744711ca51a060e9c7b' + '9d1796f8c1492bb50c13f865b4bc6854f825218836e55ce36e348c4dd77caa74' + '0f980cf61fab268a6aa2440408548d3874aee1de6eb6e3be291690b1d2eb6904' + '16904a97fc62bba84bde3a18885156b8c202c366bb6a0e0dd54414c016695876' + '32d966701ebd7675092c1fd991e2444a0a1d08d83a10f34c88e05a3c19b41834' + 'c77853d6c25b11b7ffca8d522e03bf8e1305b8637e27c5074ddeef3a22956bbf' + 'f9106baa633f742f7fff396b5335a9c0ead4fa7416cdfd2fea743c1da433960d' + '1a152c1ddc46316ec94aa19be40b96e1e8bab71a82f1e11ca59c9f5ec86808a5' + '3dd92f1f51fccfbb1ceae701db68a6ae42916ddc2d4645b3a61486b582721a54' + '3ee57dcf7d36351de5905dadf41803cf2994bba40d77f4fe5e3fcd9131ddb54e' + '064e51eabf34d04cfade37115d3fd76c4e8005973faf4dedf410f0b5bbd1ea18' + '4aa93c1c3ae7fe20e844505d521b921b9649dae03b74ad847ebd6318f02f71a2' + '9cf3e7694efd82a0fe6725069b12971a4f32d05e4e9862089cbf1779a7d2df7c' + '77aff72eb2319d3a80e0ccb63bdfb7d1c503c91fb515acc9525d029b25bb1fdc' + '0bb2e47484d67935f1ef6107433f51747211a143f549b4ba60081a143026fb22' + '300ed1bd17136bed9ff2528dcdc029396e6c373c0606653dc7905625bb8511a7' + 'dbc08bd374f299d92fc0e9022e37a1629055675bc874a904c1eefe5fb9699e2f' + '3ede37fd744d60d934a7d5186f0bcd13d67ae9a48d973283356bbe7451d337e4' + '9c3db1a504c5eeac54fadb1dadb45ac5ea36316c39fadb73836102ac4bf125f0') +b2sums=('8041a979068ea00ac8402dcf35b34277907e497b2de060edf43a1960abddcfb5449d963334eb2a8c9f9a869452be55bf4c728dbda7c381892a145de72f3a59aa' + '8fcba2b6c098125fc6ddee33b634af35bc6a8ae93c4f9cf3698f3f4455f97e22c6d8630c60776710770128f73377d2a9364c70dab6705707c1f3223e7493ce14' + '79709278b47f8e21e7376031c3dc472070c5b18b29b48eefc9d3a4d97b69104eae9325ed8fff8e7a509a89727c8335c90345807d72afacee069557e46ff5724c' + '34bebf1fe1126ff03d31abaca5908036268c231a77beebbe683cca3f80a4add12faf0e0ed27975c5fa1ba2a9994395ad0ee8115e9ecb8d3686975602981bf136' + 'b4ede5b7eced7a3459a169f5d49712647cc7cc3aec9935eea6283522245b6c3cc300ed8fb81be0dd942dddefd3bda49530270ec3f9215d2f9a52efa4c3bc5ba9' + 'b1cca3b2f31a1ab50f5fb637122d24593c23f99d773abba5a145d8701471dcb159de2bed937ea4a40b57501efb442c2ac5a2d49ab595f8b111320b11a182e710' + '2a61869cdefd59715d8d860e4860d52a6e3fc33f2b8511c5d6f78c68f7dda00195b29bf9a213668a3eedf445958b923bc7051fc38c6965c3b7803266cae16bb2' + '4bb6155f1db7a9df5e735cf353d5c1cc04525b5a2cd06fba93d3046a216a7210a6d0e1bf3e2deb07bbb58391d47804208e6e12b724000d68df9c5de5d2c52aae' + 'faa1ee756bda5714c31105b40fded40167c31d8458bc2b460dda06ad42fec60b7a71ed32a26fb036f744c8496641e3f271a39fd85f4de3ad86f1b8e9efec2dd1' + '6cf5a8fe4631a60247df626e4fea6efadad4d2ccf62dd30330fa0a98546deee17a394d8b7fef8fa5446311f01c05a303f88818a4ffa6b49810c5ec06fae1aa46' + '06c2d9f2d1b9285faf10245a325bb3937f6c98170f069e9517bf1662c0f3f4ea8499c911169c60e474a97c54b74d96297ccf0268edb0a3e602f7d0302668484d' + '3905954cb92f54a9b1f479515a1f8b949b2692eeabad1955ec2c013bf920e2c79a2008408fc569f246c1184443baea4c96a63226cd32afd1149b681adb5b07d4' + 'ab734d11f811c467bad6e417700c67188dd82593e636172ac75f36aded39eea095b7a432ab7b4d377d4bb5e824a99592fb99017b7437b6f9bfeeb1cf962f35a6' + '828275a8cdda2df5ef0698f12871cb24bcd758373d43907120495eb094fc1ddf6e25414384a52c56f2c5629a2b1702571fd368c02f1ae67ae25a2d59c0a850f3' + '08bd592cc066e2dc9216228384a6c1d650860479ae3fa8fbe4ac0b6cf8596a25e5c11bbb52a19e3c35a4de279b4746e30e376ba4273f0121c2889d7ebc6a938d' + 'b018ec8f631f9ec15bb054f85b526fb4b4bb04b954ef9a038a6c9fd1230bbbb5af8d82b04d30c220820cb5f35c77ad1e3763835244da93486f3261fe12d1f06a' + '338e2cd0b3e36f523ec7bbc59b91418fd5b6ec73588a510f488168a579de83d2b3978bb7ad641ed3a0a931e1e1555b43a2cd59f63da389545afca1579df4f256' + 'd63591d50862f07faf35e0adb52d73c669b4c29d046585878dcd4142faa049dd84088a9f21d34159f7103971b2b443d33f8a28ed77f4265f0e9d976bd66334f8' + '81a2b3b089267d636cc60f23a0f4f82ba36ecb5e210c84c7cbdf2af1b764e0c40ba3b22fec26bc7c8eab052cfa30893aac4b3c76b9a56d763841b8f7821820de' + '451006bb05bf4107347267dfb62b670d25d8991dcdb93c23221eca876bc8a2f9af7dc9e76e2c675aa44a34ef1a86deb00c128bf57f4096301f5a235ebfd69ca7') + +prepare() { + cd "${srcdir}/$_srcname" + + cat ../disable-VBE-by-default.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../enable-bootmenu-by-default.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../uefi-distro-load-FDT-from-any-partition-on-boot-device.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../uefi-Add-all-options-for-EFI-System-Partitions.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../uefi-initial-find_fdt_location-for-finding-the-DT-on-disk.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../uefi-enable-SetVariableRT-with-volotile-storage.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../uefi-enable-https-boot-by-default.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../pylibfdt-Replace-removed-SWIG-Python-2-compatibility-macros.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../USB-PD-TCPM-improvements.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../rockchip-Enable-preboot-start-for-pci-usb.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../rockchip-rk3568-nanopi-r5-Drop-duplicated-extra-sdhc.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../rockchip-rk356x-Stop-overriding-sdhci-mmc-aliases.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../p3450-fix-board.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../JetsonTX2-Fix-upstream-device-tree-naming.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../Allwinner-fix-booting-on-a-number-of-devices.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../Fix-NVMe-not-only-on-Raspberry-Pi-5.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../raspberrypi-Add-quirk-for-RPi5-2Gb-rev-1.0.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../openssl-no-engine.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + cat ../quiet-console.patch | patch -p1 -s --fuzz=0 --no-backup-if-mismatch -f + + make apple_m1_defconfig + scripts/config --file .config \ + -e OF_UPSTREAM_BUILD_VENDOR \ + --set-str OF_UPSTREAM_VENDOR apple + # Silent console: nothing on the panel or the serial port from U-Boot + # itself (the banner, the environment load, the bootflow scan). The + # environment's silent=1 applies from relocation on; the console stays + # muted until that environment is read. The kernel's console is not + # touched, and the U-Boot logo is not drawn. No boot delay: with nothing + # shown there is nothing to interrupt. + scripts/config --file .config \ + -e SILENT_CONSOLE \ + -e SILENT_CONSOLE_UPDATE_ON_SET \ + -e SILENT_CONSOLE_UPDATE_ON_RELOC \ + -e SILENT_CONSOLE_UNTIL_ENV \ + -e SILENT_U_BOOT_ONLY \ + -d VIDEO_LOGO \ + --set-val BOOTDELAY 0 + make olddefconfig +} + +build() { + cd "${srcdir}/$_srcname" + make +} + +package() { + cd "${srcdir}" + + tgtdir="$pkgdir/usr/lib/asahi-boot" + dtbdir="$_srcname/dts/upstream/src/arm64/apple" + + install -Dt "$tgtdir" -m644 "$_srcname/u-boot-nodtb.bin" + install -d "$tgtdir/dtb" + + # Exclude known non-Apple-Silicon DT families: + # t7000: A8-based iPhone 6 / 6 Plus + # t7001: A8X-based iPad Air 2 + # t8010: A10-based iPhone 7 / 7 Plus, iPad 6 / 7, iPod touch 7 + # t8011: A10X-based Apple TV 4K (1st gen), iPad Pro 10.5 / 12.9 (2nd gen) + # t8012: T2-based Intel Macs + # t8015: A11-based iPhone 8 / 8 Plus / X + find "$dtbdir" -maxdepth 1 -type f -name 't*.dtb' \ + ! -name 't7000-*.dtb' \ + ! -name 't7001-*.dtb' \ + ! -name 't8010-*.dtb' \ + ! -name 't8011-*.dtb' \ + ! -name 't8012-*.dtb' \ + ! -name 't8015-*.dtb' \ + -exec install -m644 -t "$tgtdir/dtb" {} + +} diff --git a/pkgbuilds/uboot-asahi/USB-PD-TCPM-improvements.patch b/pkgbuilds/uboot-asahi/USB-PD-TCPM-improvements.patch new file mode 100644 index 0000000..f84e6f3 --- /dev/null +++ b/pkgbuilds/uboot-asahi/USB-PD-TCPM-improvements.patch @@ -0,0 +1,717 @@ +From patchwork Wed Feb 26 18:44:44 2025 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Sebastian Reichel +X-Patchwork-Id: 2052544 +X-Patchwork-Delegate: marek.vasut@gmail.com +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + dkim=pass (2048-bit key; + unprotected) header.d=collabora.com header.i=@collabora.com + header.a=rsa-sha256 header.s=mail header.b=IthL1Z+m; + dkim-atps=neutral +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de + (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) +Received: from phobos.denx.de (phobos.denx.de + [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature ECDSA (secp384r1)) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4Z33M03Hhjz1yCp + for ; Thu, 27 Feb 2025 05:45:32 +1100 (AEDT) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id BFF7680FF0; + Wed, 26 Feb 2025 19:45:16 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=collabora.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Authentication-Results: phobos.denx.de; + dkim=pass (2048-bit key; + unprotected) header.d=collabora.com header.i=@collabora.com + header.b="IthL1Z+m"; + dkim-atps=neutral +Received: by phobos.denx.de (Postfix, from userid 109) + id 7614F810F9; Wed, 26 Feb 2025 19:45:13 +0100 (CET) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, + DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, + SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.2 +Received: from bali.collaboradmins.com (bali.collaboradmins.com + [IPv6:2a01:4f8:201:9162::2]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id 2173180FDE + for ; Wed, 26 Feb 2025 19:45:08 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=collabora.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=sebastian.reichel@collabora.com +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; + s=mail; t=1740595507; + bh=Fnp6a8opSCXiH5NJdu5uivbBBZF3pe1CXyluh8+n8/c=; + h=From:To:Cc:Subject:Date:In-Reply-To:References:From; + b=IthL1Z+mDeIRy9MWEa0lGyJWuHk/Pu+yUpLly7RG6NIpC0lCgla3ssiy2x+8/G+bz + DWjrKUMxgWtqfjfffRiKEJZ6Z2sAXJ1ePvDmMtbGoolWSrzsV2+cWh0byYrEBuVBe9 + Vp0ye7gJ4Wt610s4QHa4xoYcm7h9KGmE3IEjb+XYFv1HMJIOoACl04BwkLctza1KBr + XlIqC+snvRP/yQxa2+6RYGXgQD5JnJxCM1orXVIJxyyJxTzb3XgNir60Gb4HI9724H + LDZy0Hq7NQ1zF0h1yVUOklDotw17isbNoI4K3JCExR+r7GsdESsGY6cwAbZY71iWlf + OsSSrV6TZtAMg== +Received: from jupiter.universe (dyndsl-091-249-076-032.ewe-ip-backbone.de + [91.249.76.32]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature RSA-PSS (4096 bits)) + (No client certificate requested) (Authenticated sender: sre) + by bali.collaboradmins.com (Postfix) with ESMTPSA id A18CC17E0881; + Wed, 26 Feb 2025 19:45:07 +0100 (CET) +Received: by jupiter.universe (Postfix, from userid 1000) + id 32E35480034; Wed, 26 Feb 2025 19:45:07 +0100 (CET) +From: Sebastian Reichel +To: Marek Vasut , u-boot@lists.denx.de, + Jonas Karlman , Soeren Moch +Cc: Tim Harvey , + Philipp Tomsich , + Anand Moon , Maxim Kiselev , + Sebastian Reichel +Subject: [PATCH v2 1/4] usb: tcpm: improve handling of some power-supplies +Date: Wed, 26 Feb 2025 19:44:44 +0100 +Message-ID: <20250226184506.64114-2-sebastian.reichel@collabora.com> +X-Mailer: git-send-email 2.47.2 +In-Reply-To: <20250226184506.64114-1-sebastian.reichel@collabora.com> +References: <20250226184506.64114-1-sebastian.reichel@collabora.com> +MIME-Version: 1.0 +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.39 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean + +When the Rock 5B is booted with the current TCPM with its power supplied +by a "Cambrionix PDSync-C4" port it reaches the power-supply ready state. +Once that has happened the hub starts sending GetSinkCap messages, but +U-Boot already stopped processing PD messages. After retrying a bunch of +times the hub instead sends soft resets. Since U-Boot will also not +react to them, the USB hub will follow-up with a hard reset and that +cuts off the supply voltage. + +Since the state machine is already prepared to handle GetSinkCap +messages, try to avoid this by handling incoming messages for another +50ms after reaching the ready state. + +Fixes: 1db4c0ac77e3 ("usb: tcpm: add core framework") +Signed-off-by: Sebastian Reichel +--- + drivers/usb/tcpm/tcpm.c | 31 ++++++++++++++++++++++++++----- + 1 file changed, 26 insertions(+), 5 deletions(-) + +diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c +index 0aee57cb2f4a..b754b4dcd0b5 100644 +--- a/drivers/usb/tcpm/tcpm.c ++++ b/drivers/usb/tcpm/tcpm.c +@@ -2229,6 +2229,17 @@ static int tcpm_port_init(struct udevice *dev) + return 0; + } + ++static inline void tcpm_poll_one_event(struct udevice *dev) ++{ ++ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev); ++ struct tcpm_port *port = dev_get_uclass_plat(dev); ++ ++ drvops->poll_event(dev); ++ port->poll_event_cnt++; ++ udelay(500); ++ tcpm_check_and_run_delayed_work(dev); ++} ++ + static void tcpm_poll_event(struct udevice *dev) + { + const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev); +@@ -2242,15 +2253,25 @@ static void tcpm_poll_event(struct udevice *dev) + (port->state == SNK_READY || port->state == SRC_READY)) + break; + +- drvops->poll_event(dev); +- port->poll_event_cnt++; +- udelay(500); +- tcpm_check_and_run_delayed_work(dev); ++ tcpm_poll_one_event(dev); + } + +- if (port->state != SNK_READY && port->state != SRC_READY) ++ /* ++ * Some power-supplies send GetSinkCap shortly after they are ready. ++ * If they do not receive a response after a few retries they will issue ++ * a soft-reset followed by a hard reset, which kills the board power. ++ * Let's poll for 50ms after reaching the ready state to check if the ++ * power-supply wants something from us. ++ */ ++ if (port->state == SNK_READY) { ++ port->poll_event_cnt = 0; ++ ++ while (port->poll_event_cnt < 100) ++ tcpm_poll_one_event(dev); ++ } else { + dev_warn(dev, "TCPM: exit in state %s\n", + tcpm_states[port->state]); ++ } + + /* + * At this time, call the callback function of the respective pd chip + +From patchwork Wed Feb 26 18:44:45 2025 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Sebastian Reichel +X-Patchwork-Id: 2052546 +X-Patchwork-Delegate: marek.vasut@gmail.com +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + dkim=pass (2048-bit key; + unprotected) header.d=collabora.com header.i=@collabora.com + header.a=rsa-sha256 header.s=mail header.b=hgX6o5vZ; + dkim-atps=neutral +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de + (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) +Received: from phobos.denx.de (phobos.denx.de + [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature ECDSA (secp384r1)) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4Z33MJ5sFGz1yCp + for ; Thu, 27 Feb 2025 05:45:48 +1100 (AEDT) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id 69A29811C5; + Wed, 26 Feb 2025 19:45:17 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=collabora.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Authentication-Results: phobos.denx.de; + dkim=pass (2048-bit key; + unprotected) header.d=collabora.com header.i=@collabora.com + header.b="hgX6o5vZ"; + dkim-atps=neutral +Received: by phobos.denx.de (Postfix, from userid 109) + id 832B180FB9; Wed, 26 Feb 2025 19:45:14 +0100 (CET) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, + DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, + RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED, + SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.2 +Received: from bali.collaboradmins.com (bali.collaboradmins.com + [148.251.105.195]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id 2178380FF0 + for ; Wed, 26 Feb 2025 19:45:08 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=collabora.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=sebastian.reichel@collabora.com +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; + s=mail; t=1740595507; + bh=H/gQCZCWVmLNRIaBLvWsE35SaDMWvta2+rTkymkHQJE=; + h=From:To:Cc:Subject:Date:In-Reply-To:References:From; + b=hgX6o5vZwM72CR2hgo76tgo2eE00qHM7oNja381ACl8gIUUVioy8RTeqXtum89tBf + aIwKm/oNQNvGxy92pX5/1Jg/MwYZKS8jDukz6ZgsLKu22rJXf58FFoV095AcEkUMB2 + lrO4KJgFW/x1FK8GmSVSXXH+iUYJLkzsfZKgHKoQYHZcVXJRgFjHR5ytGKsA/fq4el + 7Wrx3lmLNjL7fYlDF5AyY+Wl6hqzjDq58nFedXHv8TEHx6fzfB0IBzOL/F8bGqVcyy + lal7bjJvMPoA9zfgIC77R1neWktg5FSro75YciSRSj9lmMNCmESnSPCgsZ4wBW0V6f + ppD77gOtjDfLQ== +Received: from jupiter.universe (dyndsl-091-249-076-032.ewe-ip-backbone.de + [91.249.76.32]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature RSA-PSS (4096 bits)) + (No client certificate requested) (Authenticated sender: sre) + by bali.collaboradmins.com (Postfix) with ESMTPSA id 9725D17E087F; + Wed, 26 Feb 2025 19:45:07 +0100 (CET) +Received: by jupiter.universe (Postfix, from userid 1000) + id 34511480037; Wed, 26 Feb 2025 19:45:07 +0100 (CET) +From: Sebastian Reichel +To: Marek Vasut , u-boot@lists.denx.de, + Jonas Karlman , Soeren Moch +Cc: Tim Harvey , + Philipp Tomsich , + Anand Moon , Maxim Kiselev , + Sebastian Reichel +Subject: [PATCH v2 2/4] usb: tcpm: avoid resets for missing source capability + messages +Date: Wed, 26 Feb 2025 19:44:45 +0100 +Message-ID: <20250226184506.64114-3-sebastian.reichel@collabora.com> +X-Mailer: git-send-email 2.47.2 +In-Reply-To: <20250226184506.64114-1-sebastian.reichel@collabora.com> +References: <20250226184506.64114-1-sebastian.reichel@collabora.com> +MIME-Version: 1.0 +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.39 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean + +The current TCPM code implements source capability message handling +according to the USB-PD specification. Unfortunately some USB PD +sources do not properly follow the specification and do not send +source capability messages after a soft reset when they already +negotiated a specific contract before. The currently implemented way +(and what is described in the specificiation) to resolve this problem +is triggering a hard reset. + +But a hard reset is fatal on batteryless platforms powered via USB-C PD, +since that removes VBUS for some time. Since this is triggered at boot +time, the system may get stuck in a boot loop. + +For example I noticed the following behaviour on a Radxa Rock 5B +combined with an affected power-supply: + +1. The system is booted up with current code +2. A reboot is requested +3. U-Boot TCPM / fusb302 driver sends soft reset and waits for the + source capability message +4. No new source capability message is send by the power-supply + after the soft reset +5. U-Boot sends a hard reset +6. The board resets, but the fusb302 registers are not reset. This + is because of a hardware glitch. The serial pins are high when + no data is exchanged. Apparently the RK3588 has protection diodes, + which leak some voltage into the power-domain. The Rock 5B serial + pins and the fusb302 are using the same 3.3V power domain and the + leaked voltage is enough to keep the fusb302 registers alive. +7. After the hard reset the power-supply sends another source capability + message, which is auto-acked by fusb302 (because the register state + is kept) even though the U-Boot driver has not yet probed. Once the + U-Boot driver probes it sends another soft reset and waits for a new + source capability message, which never arrives. + +Fortunately the affected power-supplies (I have two setups showing this +behaviour) support sending a source capability message when explicitly +being asked. Thus an easy workaround to handle this is deviating from +the USB-PD specification and sending a Get_Source_Cap message and +waiting some time longer before doing the hard reset. + +Note, that I recently added the same workaround to the Linux kernel +with a slightly different rationale (since it needs to take over from +U-Boot). + +Fixes: 1db4c0ac77e3 ("usb: tcpm: add core framework") +Signed-off-by: Sebastian Reichel +--- + drivers/usb/tcpm/tcpm-internal.h | 1 + + drivers/usb/tcpm/tcpm.c | 32 +++++++++++++++++++++++++++++--- + 2 files changed, 30 insertions(+), 3 deletions(-) + +diff --git a/drivers/usb/tcpm/tcpm-internal.h b/drivers/usb/tcpm/tcpm-internal.h +index 561442090027..4861f4d13866 100644 +--- a/drivers/usb/tcpm/tcpm-internal.h ++++ b/drivers/usb/tcpm/tcpm-internal.h +@@ -30,6 +30,7 @@ + S(SNK_DISCOVERY_DEBOUNCE), \ + S(SNK_DISCOVERY_DEBOUNCE_DONE), \ + S(SNK_WAIT_CAPABILITIES), \ ++ S(SNK_WAIT_CAPABILITIES_TIMEOUT), \ + S(SNK_NEGOTIATE_CAPABILITIES), \ + S(SNK_TRANSITION_SINK), \ + S(SNK_TRANSITION_SINK_VBUS), \ +diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c +index b754b4dcd0b5..786d92fa4c6f 100644 +--- a/drivers/usb/tcpm/tcpm.c ++++ b/drivers/usb/tcpm/tcpm.c +@@ -1424,7 +1424,8 @@ static inline enum tcpm_state hard_reset_state(struct tcpm_port *port) + return ERROR_RECOVERY; + if (port->pwr_role == TYPEC_SOURCE) + return SRC_UNATTACHED; +- if (port->state == SNK_WAIT_CAPABILITIES) ++ if (port->state == SNK_WAIT_CAPABILITIES || ++ port->state == SNK_WAIT_CAPABILITIES_TIMEOUT) + return SNK_READY; + return SNK_UNATTACHED; + } +@@ -1650,10 +1651,35 @@ static void run_state_machine(struct udevice *dev) + tcpm_set_state(dev, SOFT_RESET_SEND, + PD_T_SINK_WAIT_CAP); + } else { +- tcpm_set_state(dev, hard_reset_state(port), +- PD_T_SINK_WAIT_CAP); ++ if (!port->self_powered) ++ tcpm_set_state(dev, SNK_WAIT_CAPABILITIES_TIMEOUT, ++ PD_T_SINK_WAIT_CAP); ++ else ++ tcpm_set_state(dev, hard_reset_state(port), ++ PD_T_SINK_WAIT_CAP); + } + break; ++ case SNK_WAIT_CAPABILITIES_TIMEOUT: ++ /* ++ * There are some USB PD sources in the field, which do not ++ * properly implement the specification and fail to start ++ * sending Source Capability messages after a soft reset. The ++ * specification suggests to do a hard reset when no Source ++ * capability message is received within PD_T_SINK_WAIT_CAP, ++ * but that might effectively kil the machine's power source. ++ * ++ * This slightly diverges from the specification and tries to ++ * recover from this by explicitly asking for the capabilities ++ * using the Get_Source_Cap control message before falling back ++ * to a hard reset. The control message should also be supported ++ * and handled by all USB PD source and dual role devices ++ * according to the specification. ++ */ ++ if (tcpm_pd_send_control(dev, PD_CTRL_GET_SOURCE_CAP)) ++ tcpm_set_state_cond(dev, hard_reset_state(port), 0); ++ else ++ tcpm_set_state(dev, hard_reset_state(port), PD_T_SINK_WAIT_CAP); ++ break; + case SNK_NEGOTIATE_CAPABILITIES: + port->pd_capable = true; + port->hard_reset_count = 0; + +From patchwork Wed Feb 26 18:44:46 2025 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Sebastian Reichel +X-Patchwork-Id: 2052545 +X-Patchwork-Delegate: marek.vasut@gmail.com +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + dkim=pass (2048-bit key; + unprotected) header.d=collabora.com header.i=@collabora.com + header.a=rsa-sha256 header.s=mail header.b=BxTv7Lfo; + dkim-atps=neutral +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de + (client-ip=85.214.62.61; helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) +Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4Z33M971h7z1yCp + for ; Thu, 27 Feb 2025 05:45:41 +1100 (AEDT) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id 1B09C8118B; + Wed, 26 Feb 2025 19:45:17 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=collabora.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Authentication-Results: phobos.denx.de; + dkim=pass (2048-bit key; + unprotected) header.d=collabora.com header.i=@collabora.com + header.b="BxTv7Lfo"; + dkim-atps=neutral +Received: by phobos.denx.de (Postfix, from userid 109) + id 4399E810F9; Wed, 26 Feb 2025 19:45:14 +0100 (CET) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, + DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, + SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.2 +Received: from bali.collaboradmins.com (bali.collaboradmins.com + [IPv6:2a01:4f8:201:9162::2]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id 2163F80FB9 + for ; Wed, 26 Feb 2025 19:45:08 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=collabora.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=sebastian.reichel@collabora.com +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; + s=mail; t=1740595507; + bh=7OaDpd2idE5ETWAiOZte0kuOU6ZYitEVWkDgDmqaQU8=; + h=From:To:Cc:Subject:Date:In-Reply-To:References:From; + b=BxTv7LfoAae43YSXPquVPmmwStgQirXCxaSBD0fo554R5O/si/Iif5L+zXXJocATJ + z7+I7ZKbknkTeVyzoRDCXvXIVz2bFdTRWQN4Z2iwJbVI1covAv/Gn4wIE2Q6hbNKbU + qV5AKg7gvrKkbztbo7N9bHNjkrlAbd8B4Ew3SR8FM46DMAOwNzVsoX3rLMcPtlWzHj + 4OBKCx22H/4ASIWjMbYoDE09DyQ+CPOf8JcX8OPwFcH9j6vW6cEWvyjiAyDj6LnwOq + KD+ZYVIkWhkIzAkE2NHB2IpA4xlxW8i18BJzNBD8N1eBx/HCgb0camb4KanY1Hta/d + 4ipLyuFaCkMog== +Received: from jupiter.universe (dyndsl-091-249-076-032.ewe-ip-backbone.de + [91.249.76.32]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest + SHA256) + (No client certificate requested) (Authenticated sender: sre) + by bali.collaboradmins.com (Postfix) with ESMTPSA id 816AA17E0861; + Wed, 26 Feb 2025 19:45:07 +0100 (CET) +Received: by jupiter.universe (Postfix, from userid 1000) + id 361F2480038; Wed, 26 Feb 2025 19:45:07 +0100 (CET) +From: Sebastian Reichel +To: Marek Vasut , u-boot@lists.denx.de, + Jonas Karlman , Soeren Moch +Cc: Tim Harvey , + Philipp Tomsich , + Anand Moon , Maxim Kiselev , + Sebastian Reichel +Subject: [PATCH v2 3/4] usb: tcpm: print error on hard reset +Date: Wed, 26 Feb 2025 19:44:46 +0100 +Message-ID: <20250226184506.64114-4-sebastian.reichel@collabora.com> +X-Mailer: git-send-email 2.47.2 +In-Reply-To: <20250226184506.64114-1-sebastian.reichel@collabora.com> +References: <20250226184506.64114-1-sebastian.reichel@collabora.com> +MIME-Version: 1.0 +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.39 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean + +A USB-PD hard reset involves removing the voltage from VBUS for some +time. So basically it has the same effect as removing the USB-C plug +for a short moment. If the machine is powered from the USB-C port and +does not have a fallback supply (e.g. a battery), this will result in +a full machine reset due to power loss. + +Ideally we want to avoid triggering a hard reset on these boards. A +non-working USB-C port is probably better than unplanned reboots. But +boards with a backup supply should do the hard reset to get everything +working again. + +In theory it would be enough to check the self_powered property, but +it seems the property might not be configured consistently enough in +system firmwares. + +USB-PD hard resets should happen rarely in general, so let's at least +print an error message before the potential board reset happens. This +is also useful, since it immediately gives away which device triggered +the hard reset. + +Fixes: 1db4c0ac77e3 ("usb: tcpm: add core framework") +Signed-off-by: Sebastian Reichel +--- + drivers/usb/tcpm/tcpm.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c +index 786d92fa4c6f..909fe2ef4fcb 100644 +--- a/drivers/usb/tcpm/tcpm.c ++++ b/drivers/usb/tcpm/tcpm.c +@@ -1711,6 +1711,8 @@ static void run_state_machine(struct udevice *dev) + + /* Hard_Reset states */ + case HARD_RESET_SEND: ++ if (!port->self_powered && port->port_type == TYPEC_PORT_SNK) ++ dev_err(dev, "Initiating hard-reset, which might result in machine power-loss.\n"); + tcpm_pd_transmit(dev, TCPC_TX_HARD_RESET, NULL); + tcpm_set_state(dev, HARD_RESET_START, 0); + port->wait_dr_swap_message = false; + +From patchwork Wed Feb 26 18:44:47 2025 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +X-Patchwork-Submitter: Sebastian Reichel +X-Patchwork-Id: 2052542 +X-Patchwork-Delegate: marek.vasut@gmail.com +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + dkim=pass (2048-bit key; + unprotected) header.d=collabora.com header.i=@collabora.com + header.a=rsa-sha256 header.s=mail header.b=K4mNmlOy; + dkim-atps=neutral +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de + (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; + envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) +Received: from phobos.denx.de (phobos.denx.de + [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature ECDSA (secp384r1)) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4Z33Lk40FPz1yCp + for ; Thu, 27 Feb 2025 05:45:18 +1100 (AEDT) +Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) + by phobos.denx.de (Postfix) with ESMTP id DAFC780FF2; + Wed, 26 Feb 2025 19:45:12 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=collabora.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de +Authentication-Results: phobos.denx.de; + dkim=pass (2048-bit key; + unprotected) header.d=collabora.com header.i=@collabora.com + header.b="K4mNmlOy"; + dkim-atps=neutral +Received: by phobos.denx.de (Postfix, from userid 109) + id 56A22810F9; Wed, 26 Feb 2025 19:45:11 +0100 (CET) +X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de +X-Spam-Level: +X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, + DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_BLOCKED, + SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.2 +Received: from bali.collaboradmins.com (bali.collaboradmins.com + [IPv6:2a01:4f8:201:9162::2]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id 217E080FF2 + for ; Wed, 26 Feb 2025 19:45:08 +0100 (CET) +Authentication-Results: phobos.denx.de; + dmarc=pass (p=none dis=none) header.from=collabora.com +Authentication-Results: phobos.denx.de; + spf=pass smtp.mailfrom=sebastian.reichel@collabora.com +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; + s=mail; t=1740595507; + bh=FeXZr6pfjIqGXvnsLHDigD702EmgVNH91IWVsD16udQ=; + h=From:To:Cc:Subject:Date:In-Reply-To:References:From; + b=K4mNmlOycXzLuJnNeDxyXLVCeZWNr//ycRqDt3ld9634ThDvJ0+y49X3z2nYW+fei + HCqH2AoFiXj7khjiFpwCA4xOlYaTrF9yb0M9ApRxJ8vGuslNIK2k9osj8IJMoWmY8S + fEez4H6Wso4VamCnXKCze2G5np5mhola5QxSnIJ2CXdFP9TM1x54P+XyeJMX6IZK0X + lpZ0R8mACHGgXnkBASRMbo0CnLfSVrUgA83A0FHT7UgxfW8399wHol/FBakg5WS8nt + 47154TDW4ikoGF4dHBeD0KemJSR7I4F+nnUVQfgg8sXZKVm4yilBT+yx3WWBVCjQDl + 3igqfexnaUKnA== +Received: from jupiter.universe (dyndsl-091-249-076-032.ewe-ip-backbone.de + [91.249.76.32]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest + SHA256) + (No client certificate requested) (Authenticated sender: sre) + by bali.collaboradmins.com (Postfix) with ESMTPSA id 7FBB317E065A; + Wed, 26 Feb 2025 19:45:07 +0100 (CET) +Received: by jupiter.universe (Postfix, from userid 1000) + id 37B6A480039; Wed, 26 Feb 2025 19:45:07 +0100 (CET) +From: Sebastian Reichel +To: Marek Vasut , u-boot@lists.denx.de, + Jonas Karlman , Soeren Moch +Cc: Tim Harvey , + Philipp Tomsich , + Anand Moon , Maxim Kiselev , + Sebastian Reichel +Subject: [PATCH v2 4/4] usb: tcpm: improve data role mismatch error recovery +Date: Wed, 26 Feb 2025 19:44:47 +0100 +Message-ID: <20250226184506.64114-5-sebastian.reichel@collabora.com> +X-Mailer: git-send-email 2.47.2 +In-Reply-To: <20250226184506.64114-1-sebastian.reichel@collabora.com> +References: <20250226184506.64114-1-sebastian.reichel@collabora.com> +MIME-Version: 1.0 +X-BeenThere: u-boot@lists.denx.de +X-Mailman-Version: 2.1.39 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.denx.de +Sender: "U-Boot" +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean + +On Radxa ROCK 5B I managed to get U-Boot into an endless loop of +printing + +fusb302 usb-typec@22: TCPM: data role mismatch, initiating error recovery + +messages by changing the data role in Linux and then rebooting the +system. This is happening because the external device (A cheap USB-C hub +powered through a USB-C PD power-supply) kept its state and the error +recovery path for non self-powered devices is not enough to change it. + +Avoid this by swapping our own data role when the error recovery stage +is reached for a port, which is not self-powered. Right now data support +is limited anyways and once proper support is added we can use the data +role swap request to get the desired data direction after the initial +negotiation completed. + +Fixes: 1db4c0ac77e3 ("usb: tcpm: add core framework") +Signed-off-by: Sebastian Reichel +--- + drivers/usb/tcpm/tcpm.c | 30 +++++++++++++++++++++++++++--- + 1 file changed, 27 insertions(+), 3 deletions(-) + +diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c +index 909fe2ef4fcb..12d66d470f9a 100644 +--- a/drivers/usb/tcpm/tcpm.c ++++ b/drivers/usb/tcpm/tcpm.c +@@ -833,6 +833,28 @@ static void tcpm_pd_ctrl_request(struct udevice *dev, + } + } + ++static void tcpm_recover_data_role_mismatch(struct udevice *dev) ++{ ++ struct tcpm_port *port = dev_get_uclass_plat(dev); ++ ++ dev_err(dev, "TCPM: data role mismatch, initiating error recovery\n"); ++ if (port->self_powered) { ++ tcpm_set_state(dev, ERROR_RECOVERY, 0); ++ return; ++ } ++ ++ /* ++ * The error recovery will not help for devices, which are not ++ * self-powered because the error recovery avoids killing the board ++ * power. Since this can happen early on sending ++ * a DR_SWAP request is not sensible. Instead let's change our own ++ * data role. It can be swapped back once USB-PD reached the ready ++ * state. ++ */ ++ tcpm_set_roles(dev, true, port->pwr_role, ++ port->data_role == TYPEC_HOST ? TYPEC_DEVICE : TYPEC_HOST); ++} ++ + static void tcpm_pd_rx_handler(struct udevice *dev, + const struct pd_message *msg) + { +@@ -867,9 +889,11 @@ static void tcpm_pd_rx_handler(struct udevice *dev, + remote_is_host = !!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE); + local_is_host = port->data_role == TYPEC_HOST; + if (remote_is_host == local_is_host) { +- dev_err(dev, "TCPM: data role mismatch, initiating error recovery\n"); +- tcpm_set_state(dev, ERROR_RECOVERY, 0); +- } else { ++ tcpm_recover_data_role_mismatch(dev); ++ local_is_host = port->data_role == TYPEC_HOST; ++ } ++ ++ if (remote_is_host != local_is_host) { + if (cnt) + tcpm_pd_data_request(dev, msg); + else diff --git a/pkgbuilds/uboot-asahi/disable-VBE-by-default.patch b/pkgbuilds/uboot-asahi/disable-VBE-by-default.patch new file mode 100644 index 0000000..1f1b710 --- /dev/null +++ b/pkgbuilds/uboot-asahi/disable-VBE-by-default.patch @@ -0,0 +1,32 @@ +From d9e3a49bc2a29922da5bddd95772279f6edba802 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Fri, 18 Aug 2023 13:28:05 +0100 +Subject: [PATCH] disable VBE by default + +The VBE protocol needs explicit device support and as +such isn't particularly useful by itself without that, +it also adds size and the potential of an attack vector +so devices that wish to use this protocol should +explicitly opt in to it like all other large features +in U-Boot. + +Signed-off-by: Peter Robinson +--- + boot/Kconfig | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/boot/Kconfig b/boot/Kconfig +index 30eb5b328d7..e8c3fbcb11d 100644 +--- a/boot/Kconfig ++++ b/boot/Kconfig +@@ -635,7 +635,6 @@ config BOOTMETH_QFW + config BOOTMETH_VBE + bool "Bootdev support for Verified Boot for Embedded" + depends on FIT +- default y + select BOOTMETH_GLOBAL + select EVENT + help +-- +2.49.0 + diff --git a/pkgbuilds/uboot-asahi/enable-bootmenu-by-default.patch b/pkgbuilds/uboot-asahi/enable-bootmenu-by-default.patch new file mode 100644 index 0000000..1ecd514 --- /dev/null +++ b/pkgbuilds/uboot-asahi/enable-bootmenu-by-default.patch @@ -0,0 +1,49 @@ +From a956480a6d52ae90ef4a46275c2ee29a4074de36 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Fri, 18 Aug 2023 23:10:42 +0100 +Subject: [PATCH] enable bootmenu by default + +We want the boot menu on all Fedora devices as it makes +it a whole lot more user friendly. + +Signed-off-by: Peter Robinson +--- + boot/Kconfig | 2 ++ + cmd/Kconfig | 1 + + 2 files changed, 3 insertions(+) + +diff --git a/boot/Kconfig b/boot/Kconfig +index 467b09e2350..ad67b1f65a6 100644 +--- a/boot/Kconfig ++++ b/boot/Kconfig +@@ -1281,6 +1281,7 @@ config BOOTDELAY + + config AUTOBOOT_KEYED + bool "Stop autobooting via specific input key / string" ++ default y + help + This option enables stopping (aborting) of the automatic + boot feature only by issuing a specific input key or +@@ -1433,6 +1434,7 @@ config AUTOBOOT_MENUKEY + + config AUTOBOOT_MENU_SHOW + bool "Show a menu on boot" ++ default y + depends on CMD_BOOTMENU + help + This enables the boot menu, controlled by environment variables +diff --git a/cmd/Kconfig b/cmd/Kconfig +index 43ca10f69cc..3d39f2914cc 100644 +--- a/cmd/Kconfig ++++ b/cmd/Kconfig +@@ -413,6 +413,7 @@ source lib/efi_selftest/Kconfig + + config CMD_BOOTMENU + bool "bootmenu" ++ default y + select MENU + select CHARSET + help +-- +2.41.0 + diff --git a/pkgbuilds/uboot-asahi/openssl-no-engine.patch b/pkgbuilds/uboot-asahi/openssl-no-engine.patch new file mode 100644 index 0000000..ec771f5 --- /dev/null +++ b/pkgbuilds/uboot-asahi/openssl-no-engine.patch @@ -0,0 +1,178 @@ +From 88a0fe6c1fda46c8bacd861044f39930ce1c9bc0 Mon Sep 17 00:00:00 2001 +From: Davide Cavalca +Date: Sun, 29 Sep 2024 17:20:59 -0700 +Subject: [PATCH] lib: allow building when OpenSSL ENGINE is disabled + +Recent OpenSSL releases deprecate ENGINE, and some distributions have +started to remove it altogether. Gate the logic using it behind a +feature check so that we'll build regardless. + +Signed-off-by: Davide Cavalca +--- + lib/aes/aes-encrypt.c | 2 ++ + lib/rsa/rsa-sign.c | 22 ++++++++++++++++++++++ + 2 files changed, 24 insertions(+) + +diff --git a/lib/aes/aes-encrypt.c b/lib/aes/aes-encrypt.c +index e74e35eaa2..7758438c32 100644 +--- a/lib/aes/aes-encrypt.c ++++ b/lib/aes/aes-encrypt.c +@@ -16,7 +16,9 @@ + #include + #include + #include ++#ifndef OPENSSL_NO_ENGINE + #include ++#endif + #include + + #if OPENSSL_VERSION_NUMBER >= 0x10000000L +diff --git a/lib/rsa/rsa-sign.c b/lib/rsa/rsa-sign.c +index 2304030e32..bb9c93ea65 100644 +--- a/lib/rsa/rsa-sign.c ++++ b/lib/rsa/rsa-sign.c +@@ -19,7 +19,9 @@ + #include + #include + #include ++#ifndef OPENSSL_NO_ENGINE + #include ++#endif + + static int rsa_err(const char *msg) + { +@@ -89,6 +91,7 @@ err_cert: + return ret; + } + ++#ifndef OPENSSL_NO_ENGINE + /** + * rsa_engine_get_pub_key() - read a public key from given engine + * +@@ -157,6 +160,7 @@ static int rsa_engine_get_pub_key(const char *keydir, const char *name, + + return 0; + } ++#endif + + /** + * rsa_get_pub_key() - read a public key +@@ -170,8 +174,10 @@ static int rsa_engine_get_pub_key(const char *keydir, const char *name, + static int rsa_get_pub_key(const char *keydir, const char *name, + ENGINE *engine, EVP_PKEY **evpp) + { ++#ifndef OPENSSL_NO_ENGINE + if (engine) + return rsa_engine_get_pub_key(keydir, name, engine, evpp); ++#endif + return rsa_pem_get_pub_key(keydir, name, evpp); + } + +@@ -217,6 +223,7 @@ static int rsa_pem_get_priv_key(const char *keydir, const char *name, + return 0; + } + ++#ifndef OPENSSL_NO_ENGINE + /** + * rsa_engine_get_priv_key() - read a private key from given engine + * +@@ -293,6 +300,7 @@ static int rsa_engine_get_priv_key(const char *keydir, const char *name, + + return 0; + } ++#endif + + /** + * rsa_get_priv_key() - read a private key +@@ -306,9 +314,11 @@ static int rsa_engine_get_priv_key(const char *keydir, const char *name, + static int rsa_get_priv_key(const char *keydir, const char *name, + const char *keyfile, ENGINE *engine, EVP_PKEY **evpp) + { ++#ifndef OPENSSL_NO_ENGINE + if (engine) + return rsa_engine_get_priv_key(keydir, name, keyfile, engine, + evpp); ++#endif + return rsa_pem_get_priv_key(keydir, name, keyfile, evpp); + } + +@@ -325,6 +335,7 @@ static int rsa_init(void) + return 0; + } + ++#ifndef OPENSSL_NO_ENGINE + static int rsa_engine_init(const char *engine_id, ENGINE **pe) + { + const char *key_pass; +@@ -380,6 +391,7 @@ static void rsa_engine_remove(ENGINE *e) + ENGINE_free(e); + } + } ++#endif + + static int rsa_sign_with_key(EVP_PKEY *pkey, struct padding_algo *padding_algo, + struct checksum_algo *checksum_algo, +@@ -471,11 +483,13 @@ int rsa_sign(struct image_sign_info *info, + if (ret) + return ret; + ++#ifndef OPENSSL_NO_ENGINE + if (info->engine_id) { + ret = rsa_engine_init(info->engine_id, &e); + if (ret) + return ret; + } ++#endif + + ret = rsa_get_priv_key(info->keydir, info->keyname, info->keyfile, + e, &pkey); +@@ -486,17 +500,21 @@ int rsa_sign(struct image_sign_info *info, + if (ret) + goto err_sign; + ++#ifndef OPENSSL_NO_ENGINE + EVP_PKEY_free(pkey); + if (info->engine_id) + rsa_engine_remove(e); + + return ret; ++#endif + + err_sign: + EVP_PKEY_free(pkey); + err_priv: ++#ifndef OPENSSL_NO_ENGINE + if (info->engine_id) + rsa_engine_remove(e); ++#endif + return ret; + } + +@@ -636,11 +654,13 @@ int rsa_add_verify_data(struct image_sign_info *info, void *keydest) + ENGINE *e = NULL; + + debug("%s: Getting verification data\n", __func__); ++#ifndef OPENSSL_NO_ENGINE + if (info->engine_id) { + ret = rsa_engine_init(info->engine_id, &e); + if (ret) + return ret; + } ++#endif + ret = rsa_get_pub_key(info->keydir, info->keyname, e, &pkey); + if (ret) + goto err_get_pub_key; +@@ -717,8 +737,10 @@ done: + err_get_params: + EVP_PKEY_free(pkey); + err_get_pub_key: ++#ifndef OPENSSL_NO_ENGINE + if (info->engine_id) + rsa_engine_remove(e); ++#endif + + if (ret) + return ret; +-- +2.46.2 + diff --git a/pkgbuilds/uboot-asahi/p3450-fix-board.patch b/pkgbuilds/uboot-asahi/p3450-fix-board.patch new file mode 100644 index 0000000..74475d5 --- /dev/null +++ b/pkgbuilds/uboot-asahi/p3450-fix-board.patch @@ -0,0 +1,25 @@ +From c9b65320d64a60cde74c4c1f9cb4a05198a456c2 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Wed, 10 Sep 2025 13:27:52 +0100 +Subject: [PATCH] further size reductions + +--- + configs/p3450-0000_defconfig | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/configs/p3450-0000_defconfig b/configs/p3450-0000_defconfig +index 2ebcf8b83fc..a5e717a02ed 100644 +--- a/configs/p3450-0000_defconfig ++++ b/configs/p3450-0000_defconfig +@@ -27,6 +27,8 @@ CONFIG_CONSOLE_MUX=y + CONFIG_SYS_STDIO_DEREGISTER=y + CONFIG_SYS_PROMPT="Tegra210 (P3450-0000) # " + # CONFIG_BOOTM_PLAN9 is not set ++# CONFIG_SYS_DTC_PAD_BYTES is not set ++# CONFIG_LEGACY_IMAGE_FORMAT is not set + # CONFIG_BOOTM_RTEMS is not set + # CONFIG_BOOTM_VXWORKS is not set + # CONFIG_CMD_ELF is not set +-- +2.51.0 + diff --git a/pkgbuilds/uboot-asahi/pylibfdt-Replace-removed-SWIG-Python-2-compatibility-macros.patch b/pkgbuilds/uboot-asahi/pylibfdt-Replace-removed-SWIG-Python-2-compatibility-macros.patch new file mode 100644 index 0000000..43f321f --- /dev/null +++ b/pkgbuilds/uboot-asahi/pylibfdt-Replace-removed-SWIG-Python-2-compatibility-macros.patch @@ -0,0 +1,234 @@ +From patchwork Tue Aug 11 13:34:33 2026 +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit +X-Patchwork-Submitter: Jaipaul Cheernam +X-Patchwork-Id: 2283713 +Return-Path: +X-Original-To: incoming@patchwork.ozlabs.org +Delivered-To: patchwork-incoming@legolas.ozlabs.org +Authentication-Results: legolas.ozlabs.org; + dkim=fail reason="signature verification failed" (2048-bit key; + unprotected) header.d=lists.u-boot-project.org + header.i=@lists.u-boot-project.org header.a=rsa-sha256 header.s=default + header.b=N2mjzMfT; + dkim-atps=neutral +Authentication-Results: legolas.ozlabs.org; + spf=pass (sender SPF authorized) smtp.mailfrom=lists.u-boot-project.org + (client-ip=140.211.166.137; helo=smtp4.osuosl.org; + envelope-from=u-boot-bounces@lists.u-boot-project.org; + receiver=patchwork.ozlabs.org) +Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) + key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384) + (No client certificate requested) + by legolas.ozlabs.org (Postfix) with ESMTPS id 4hKCMS4PSRz1xtl + for ; Tue, 11 Aug 2026 23:36:36 +1000 (AEST) +Received: from localhost (localhost [127.0.0.1]) + by smtp4.osuosl.org (Postfix) with ESMTP id A0013402ED; + Tue, 11 Aug 2026 13:36:34 +0000 (UTC) +X-Virus-Scanned: amavis at osuosl.org +Received: from smtp4.osuosl.org ([127.0.0.1]) + by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP + id p3bOda34E2S1; Tue, 11 Aug 2026 13:36:34 +0000 (UTC) +X-Comment: SPF check N/A for local connections - client-ip=140.211.166.142; + helo=lists1.osuosl.org; + envelope-from=u-boot-bounces@lists.u-boot-project.org; receiver= +DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org C8D6040520 +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=lists.u-boot-project.org ; s=default; t=1786455393; + bh=cBVuDcRfAz5FmVboiaFLD22qM9rj4tnKRC4LViq6X68=; + h=From:To:Cc:Subject:Date:List-Id:List-Unsubscribe:List-Archive: + List-Post:List-Help:List-Subscribe:From; + b=N2mjzMfTYSqmTzk5FULwaFpMrGP5bVx/I9fHulmwzUjiqqJ9tTuxFgg7YCzUy5gHA + nOaWzLi5SSXsmRZgFXcjHu42Dlk9xTHR2qq/4dS0Jbc0rahx6+PQT1YjAWBHDpsl26 + WZH2MDE/Gv/VVK7wjRp2lYfVtOs39dZAWUSyvYaH8vRwIKUb696ujmQYkGRa4RSkVO + WoL+nEFC1B1XaZWlY7HLMogdXPKXD4VqvBU86Oc/lzjf/NAZAjtkKnBLCG8TC2l2AY + E/TYLlN3XJYwSxHLzMqMCU0/TSwwT1z4kgibHFNq8XUpt1VwMloPxSTvHz9JvZNYOs + wwM5F4y6KhaDw== +Received: from lists1.osuosl.org (lists1.osuosl.org [140.211.166.142]) + by smtp4.osuosl.org (Postfix) with ESMTP id C8D6040520; + Tue, 11 Aug 2026 13:36:33 +0000 (UTC) +X-Original-To: u-boot@lists.u-boot-project.org +Delivered-To: u-boot@lists.u-boot-project.org +Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) + by lists1.osuosl.org (Postfix) with ESMTP id 38B5E259 + for ; Tue, 11 Aug 2026 13:34:51 +0000 (UTC) +Received: from localhost (localhost [127.0.0.1]) + by smtp3.osuosl.org (Postfix) with ESMTP id 2B001607C7 + for ; Tue, 11 Aug 2026 13:34:51 +0000 (UTC) +X-Virus-Scanned: amavis at osuosl.org +Received: from smtp3.osuosl.org ([127.0.0.1]) + by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP + id eGFQZMQurFKG for ; + Tue, 11 Aug 2026 13:34:50 +0000 (UTC) +X-Comment: SPF check N/A for local connections - client-ip=85.214.62.61; + helo=phobos.denx.de; envelope-from=jaipaul.cheernam@est.tech; + receiver= +DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org DCF5E607BD +DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org DCF5E607BD +Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) + by smtp3.osuosl.org (Postfix) with ESMTPS id DCF5E607BD + for ; Tue, 11 Aug 2026 13:34:49 +0000 (UTC) +Received: by phobos.denx.de (Postfix, from userid 109) + id 1444784A30; Tue, 11 Aug 2026 15:34:47 +0200 (CEST) +Received: from MRWPR03CU001.outbound.protection.outlook.com + (mail-francesouthazlp170110003.outbound.protection.outlook.com + [IPv6:2a01:111:f403:c207::3]) + (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) + (No client certificate requested) + by phobos.denx.de (Postfix) with ESMTPS id D7A5C849A1 + for ; Tue, 11 Aug 2026 15:34:44 +0200 (CEST) +ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; + b=F/iYm8I1iNna6pUS6fgZzFjCemWpknd0tJi480HHtcO6J5oiHNQHuS8TUlSvhc+RIG6xiOAg2WmQnvfUJCgjWzvLxQdJ8wGHLOg/TvimuL/XDMdwtYzqUN6b4PC3SYhJZk3sRSEZj6+3q5TRszIXJDsKEPqWkQE5oOVP9n1FY9ysY6EsjQMakTDpQ/RpkUkYgn50oLOyLLsDl3H0VSmZGfHqX0QOkwYIW/T8k3Fx/sLrlSiTfKiCPwrdnhwz7ABjlA79EvSU6/TvI/nefnSXeyUhYj0S6rPUadLIvlHFLjaFZ93ldV9COTfx96z44ltNtyAmvkIlMc6g22V3Dv8hAg== +ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; + s=arcselector10001; + h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; + bh=cBVuDcRfAz5FmVboiaFLD22qM9rj4tnKRC4LViq6X68=; + b=kO0F3iz0TnoDLl3Fiv7ulw2CObdD+lulFmKuohamR8N6FEc5UzGf7TInNwu6ce7RXGSVuFd7hfaEsq6r/o3PhBgHar8MJtVXKc+dvUPG5YuOV7nTz6qTe9trkJKfQmaFIuzBrRusEp8J4y32eMnjVSFzFXNV2eI9S3+WCoakNjmNFR8JoZWsaeqgxsTl6CGsWOYvktuUErx0GcRg4AgQmZU2EiDiD3VVrXKk+AlQY4GOTcgDJSHsqmrqn/xlr1O+9o2xvoc0RJMvz9upXok7Jo2eNeoty0UCLuSLzfxRnVJJriUkIOxFFdTQjuVzoeQMNSB7k6iXu8tgDwbcBiyzkg== +ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass + smtp.mailfrom=est.tech; dmarc=pass action=none header.from=est.tech; + dkim=pass header.d=est.tech; arc=none +Received: from DU7PPF66507B2D7.EURP189.PROD.OUTLOOK.COM (2603:10a6:18:3::ad4) + by VI1P18901MB0623.EURP189.PROD.OUTLOOK.COM (2603:10a6:800:125::14) + with Microsoft SMTP Server (version=TLS1_2, + cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.315.11; Tue, 11 Aug + 2026 13:34:41 +0000 +Received: from DU7PPF66507B2D7.EURP189.PROD.OUTLOOK.COM + ([fe80::7ab2:c6af:6760:5c85]) by DU7PPF66507B2D7.EURP189.PROD.OUTLOOK.COM + ([fe80::7ab2:c6af:6760:5c85%7]) with mapi id 15.21.0315.008; Tue, 11 Aug 2026 + 13:34:41 +0000 +From: Jaipaul Cheernam +To: u-boot@lists.denx.de +Cc: Jaipaul Cheernam +Subject: [PATCH] pylibfdt: Replace removed SWIG Python 2 compatibility macros +Date: Tue, 11 Aug 2026 15:34:33 +0200 +Message-Id: <20260811133433.18852-1-jaipaul.cheernam@est.tech> +X-Mailer: git-send-email 2.39.5 (Apple Git-154) +X-ClientProxiedBy: LO4P265CA0183.GBRP265.PROD.OUTLOOK.COM + (2603:10a6:600:311::6) To DU7PPF66507B2D7.EURP189.PROD.OUTLOOK.COM + (2603:10a6:18:3::ad4) +MIME-Version: 1.0 +X-MS-PublicTrafficType: Email +X-MS-TrafficTypeDiagnostic: DU7PPF66507B2D7:EE_|VI1P18901MB0623:EE_ +X-MS-Office365-Filtering-Correlation-Id: 8a2304be-1609-4ac3-d596-08def7ad4c24 +X-MS-Exchange-SenderADCheck: 1 +X-MS-Exchange-AntiSpam-Relay: 0 +X-Microsoft-Antispam: BCL:0; + ARA:13230040|366016|23010399003|376014|1800799024|6133799003|10067099003|56012099006|11063799006|18002099003; +X-Microsoft-Antispam-Message-Info: + ADZCK8hJVm6F3wH0lN+1CH3ycpX/oWr0C7rdxob6unX1/P+tCnAEcRYEgIIPZwnHqbQDW5p6JD2T3EG67ZpnAdgv+1d2HZMQepsfe3LElh3IHx0BPlzA3Vty6psoCgoWs4SMcNTrIVV3UqBrl3s/ttqDI2opVebhHDoAf8Z50f5xlclKz2O8gSMv8FOB9NyfaRUwtlAVgkE428WD6Wia/D/e92aD33o3Z4r2Pz7UzkUhLPLrNeVcSYh94xaUrbMlilcRmFygJWWpgeZsh+7a8kFV/eb42A37qzZ5DcE2yO5PZaA4ckohcSQ2QCA+BnKRUKh3rxkKmCeVglTgdpKg9qvpxmE+Ndzo97z6t8EimCMtdnMQxkDIA6yZiqO7xc2qjdfQ7azC4KZ3h0xq4J04c+HxOzwrQ6t/1IZd0XkN9IrgCyVRhOPL593gaeMD9Iapy4fOTZuQTL6PmUjCMNC7KNiyzyg2gtgWYist7MODeGSDNRUBR3X4hOON3nYYRXl/t+WsxRKhD5ykRF0EUEGh140FdrrFz00eXO6LIIbJUyaf1ud/SBpIw15warKR5ojDTyGUIuhg+toVhwib8/OGuFI5VxDdfgfCPVncMzarvbqXd3nx8r/NAjbPGYY9yKoOEoJ4CVfWa5NbVK3z/aCJlrnBCNTiYc8m29cvV3Ch808= +X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; + IPV:NLI; SFV:NSPM; H:DU7PPF66507B2D7.EURP189.PROD.OUTLOOK.COM; PTR:; + CAT:NONE; + SFS:(13230040)(366016)(23010399003)(376014)(1800799024)(6133799003)(10067099003)(56012099006)(11063799006)(18002099003); + DIR:OUT; SFP:1101; +X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 +X-MS-Exchange-AntiSpam-MessageData-0: + v1SJ9iF1FZ4Ddch8PL37hr0CiYoSTtojtB+aom++qNSm/JaqntarQvycHwKOYPL0X3VIS0vCDGJSC3b6gdCPlWLoGPlNDQJYLlTHOfds4mIdrbG5HCQ8X4EoM9uDG/9vWdsUSeRJnh1D8Q1gwPU2JRfArF7684CnJny/jZyepPyTurvWXmjo6skYOwzuRosHH9ZCocSL2xHtHu2N0HirNE+qvbo53SlhzUhQzvvoLlOHVHnSdJDTDRUfPTF3lginIWrs2IzeZkaZIbuV4+/dW5CfSJWX0Y/tHWJZ5Ny7Lbc+uG5kOidZBfaCN8UfKqZ5RTC1dkx0X6BKwkuKC3/unMfJIZAQg8Q5QmhF1uftv3XcyCrH0gNCyyVAl7lLegxBbzWJdGZPbfLh2STTurHhzeJ3hJtClRfwKE03a+TYu+44Ekc0nUdTCdn7Hpf6hnBnTmScp1QWP/toxL70lv4JhkNeZLia+I7vw7Elk6PIK1KWHY2Wlm1sGSiI2jlDnRBpPSDakUhEPWcFI+jz2XzJZ9+sBh/bzsmPqLeH71fS1cDiOyQG5lG0OWoewtDfmk4QE1ViS8UTY6oeE1lL7RNarDf+KoDL75rAwUtJATqdEqfaY1fxnRrVfaawTZIL4jYe56ITk7NiAhZ2aJXJ9WvxvXsu2Q4f+RMBoMuYSuf/B4Eil873iPGk7f9KcFV0F9e7xVcnb6bMZ2xOOxFcuZ/FVblgRYviX75D9uroJan3ZJIBqmgQL1Mq2rfIAMctUIcTYWBg8qFOVymFr22cxfzoD/puCigGWDWKl2bRk8s4WaN7PuznMXOrdvs3d9FMEQfy5oyxE4dpv/527M2l0Lqa5bGEK4y20y7FYfu/js6Ja5t38S1ZgQV3SIEqvAzCHWbYOBgMg/969p32OF20GFx7ZxF+rWXJ5QLbfimyrZ0jw+nuPvaA1BmvtjCgBLM24eIvdJuHhRjEqaCBEGwGutPeikNcxQ2x45eMlXc1OPZymG5DLgq2FMyUyQAoCrZ70YMCO3yNThR+Vfh8qc/qNpl7pLwIqvT7/5x9ZYPBINAjWwOuutEdBTH2D15WAsKB8IcxVMpjpEhaNNAMEEr3qaahViyCwFhx8GZY39yQRdbyb7dweQK/2hPFY/M2TSiGPLbxGIVOxu048L2ePB1ZMCT9v6/7pzft7L9LKOAmj5fj7zMIm9IzfaYRiPbT9KkGvcbOQ85BMLZdw4QJYUpUt9PKf6zxidBmSUfD8WAObbMF8wW9NFRSEeEdh5CMh1lRyEnFsvCFJov8+CJAcK0kYeXjJwcbaDiAKIWGk8MF8lPWIZk/cFpbsetUN6W5LQT+oxa4qttRPpjixTvJFK9i8NUHuPtZzJ0Knv9rt/4VmG7oziwVngajIkj5ByXT21gg1iX8EEqLJznzX5pHeKWV9UAvgm+fmT+zhrgV4LL+ySkPaSSNJz9eBvp3qxU4486MXyw0ISCob64j6LkZ2tYGULV2PF8caUN+J5l1cUu2ySXr7Pt4LyoMKMk/7QEJgUqzxB4T//af1ASXB0//pNSTXgPbo4eCPFtqTkVDD+KxXRJsNyGVmSJGfDE67Y7mF28SQidNW7gguYRcn7DJHFxNm1/Co6kVsDf4Xp9BOQ2HROs67mxZqicBzEqifFMBNigrr4qcA9Qcaqpkewrz9/7zW8TBs/5v6KWQHbMKffzp6PPtnwX/rVrK3VBd8TusgBYky7Q2scVbONq4qDA/ErH5tWob1bBCIPdzl9ngXrCgsknnFHs= +X-OriginatorOrg: est.tech +X-MS-Exchange-CrossTenant-Network-Message-Id: + 8a2304be-1609-4ac3-d596-08def7ad4c24 +X-MS-Exchange-CrossTenant-AuthSource: DU7PPF66507B2D7.EURP189.PROD.OUTLOOK.COM +X-MS-Exchange-CrossTenant-AuthAs: Internal +X-MS-Exchange-CrossTenant-OriginalArrivalTime: 11 Aug 2026 13:34:41.3741 (UTC) +X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted +X-MS-Exchange-CrossTenant-Id: d2585e63-66b9-44b6-a76e-4f4b217d97fd +X-MS-Exchange-CrossTenant-MailboxType: HOSTED +X-MS-Exchange-CrossTenant-UserPrincipalName: + 9S3MTmbV8NoDKIO+2csvXOXSX+h+vv/3+dsv/aYgYwFK6nBqKPXaQTK18nwZ9STbTS8eocOH32TrCptEz96k1m5bWAyhRvMPyQD1xqNBHC4= +X-MS-Exchange-Transport-CrossTenantHeadersStamped: VI1P18901MB0623 +X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de +X-Virus-Status: Clean +X-Mailman-Approved-At: Tue, 11 Aug 2026 13:36:32 +0000 +X-Mailman-Original-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=est.tech; s=selector1; + h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; + bh=cBVuDcRfAz5FmVboiaFLD22qM9rj4tnKRC4LViq6X68=; + b=pqeWauqz6d5LeBvZqnf0Vhya3rUJMPVJBkYn0KjHe11y8MAnCj2YKA4NsHfxYgJwkrtzBngWWBmMuUwulnTUPfh3czsocU/HiR1U+jCVAcvcPj55A0JETJ4yzxa5edLmeRYJuzsnnkbx/PCnabanIJQNikIQiZPSYzLujjk5w+8uDoqw9b5fy+0DrexXTt4ygBBpl5++UWUu/ZM79h7yIW1OIMNbg9SdLZVbjdgFl1jrxU0e8eJEGgOVWAwAHQ4vChd/Fanm6NkU/8YU99cRqrLXjjOZedJF5ZbuxnLqfQev0dTeHnqf7PnTtRKIXcexkdiggESk1fsx72bytELfag== +X-Mailman-Original-Authentication-Results: smtp3.osuosl.org; + dmarc=none (p=none dis=none) + header.from=est.tech +X-Mailman-Original-Authentication-Results: smtp3.osuosl.org; + spf=fail smtp.mailfrom=est.tech +X-Mailman-Original-Authentication-Results: smtp3.osuosl.org; + dkim=pass (2048-bit key, + unprotected) header.d=est.tech header.i=@est.tech header.a=rsa-sha256 + header.s=selector1 header.b=pqeWauqz +X-Mailman-Original-Authentication-Results: phobos.denx.de; + dmarc=none (p=none dis=none) + header.from=est.tech +X-Mailman-Original-Authentication-Results: phobos.denx.de; spf=pass + smtp.mailfrom=jaipaul.cheernam@est.tech +X-Mailman-Original-Authentication-Results: phobos.denx.de; + dkim=pass (2048-bit key; + unprotected) header.d=est.tech header.i=@est.tech header.b="pqeWauqz"; + dkim-atps=neutral +X-Mailman-Original-Authentication-Results: dkim=none (message not signed) + header.d=none;dmarc=none action=none header.from=est.tech; +X-BeenThere: u-boot@lists.u-boot-project.org +X-Mailman-Version: 2.1.30 +Precedence: list +List-Id: U-Boot discussion +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Errors-To: u-boot-bounces@lists.u-boot-project.org +Sender: "U-Boot" + +SWIG 4.5.0 removed Python 2 compatibility macros (PyInt_*, PyString_*) +from its runtime header pyhead.swg (see commit 79f7a2b7cb7d). Replace +them with their Python 3 C API equivalents: + +- PyString_FromString -> PyUnicode_FromString +- PyString_AsString -> PyBytes_AsString +- PyInt_AsLong -> PyLong_AsLong + +The replacements are safe since the macros were already aliased to +these exact functions in SWIG's Python 3 code path. + +This is a backport of dtc commit 5008d1d6a356 ("pylibfdt: Replace +removed SWIG Python 2 compatibility macros"). + +Signed-off-by: Jaipaul Cheernam +Reviewed-by: Jรฉrรฉmie Dautheribes +Tested-by: Jรฉrรฉmie Dautheribes +Signed-off-by: Peter Robinson +Reviewed-by: Peter Robinson +--- + scripts/dtc/pylibfdt/libfdt.i_shipped | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/scripts/dtc/pylibfdt/libfdt.i_shipped b/scripts/dtc/pylibfdt/libfdt.i_shipped +index e4659489..326a15c7 100644 +--- a/scripts/dtc/pylibfdt/libfdt.i_shipped ++++ b/scripts/dtc/pylibfdt/libfdt.i_shipped +@@ -1033,7 +1033,7 @@ typedef uint32_t fdt32_t; + PyObject *buff; + + if ($1) { +- resultobj = PyString_FromString( ++ resultobj = PyUnicode_FromString( + fdt_string(fdt1, fdt32_to_cpu($1->nameoff))); + buff = PyByteArray_FromStringAndSize( + (const char *)($1 + 1), fdt32_to_cpu($1->len)); +@@ -1064,13 +1064,13 @@ typedef uint32_t fdt32_t; + } + $1 = PyBytes_AsString($input); + %#else +- $1 = PyString_AsString($input); /* char *str */ ++ $1 = PyBytes_AsString($input); /* char *str */ + %#endif + } + + /* typemaps used for fdt_next_node() */ + %typemap(in, numinputs=1) int *depth (int depth) { +- depth = (int) PyInt_AsLong($input); ++ depth = (int) PyLong_AsLong($input); + $1 = &depth; + } + diff --git a/pkgbuilds/uboot-asahi/quiet-console.patch b/pkgbuilds/uboot-asahi/quiet-console.patch new file mode 100644 index 0000000..8c01eb5 --- /dev/null +++ b/pkgbuilds/uboot-asahi/quiet-console.patch @@ -0,0 +1,40 @@ +From: Marcelo Alcantara +Subject: [PATCH] apple: keep the console silent on Macs + +Omarchy boots the Mac to a splash, not a terminal: with CONFIG_SILENT_CONSOLE +the console is muted whenever the 'silent' variable is set, so the default +environment sets it. Only U-Boot is silenced (CONFIG_SILENT_U_BOOT_ONLY); +the kernel command line is untouched. +--- + board/apple/mac/mac.env | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/board/apple/mac/mac.env b/board/apple/mac/mac.env +--- a/board/apple/mac/mac.env ++++ b/board/apple/mac/mac.env +@@ -2,3 +2,4 @@ stdin=serial,usbkbd,spikbd,mtpkbd + stdout=vidconsole,serial + stderr=vidconsole,serial + boot_targets=nvme usb ++silent=1 + +The banner "U-Boot 2026.07 ..." is announced on the video console after +relocation through console_puts_select(), which never looks at the silent +flag; a silent console stays silent for the banner too. +--- + common/console.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/common/console.c b/common/console.c +--- a/common/console.c ++++ b/common/console.c +@@ -1022,6 +1022,9 @@ int console_announce_r(void) + #if !CONFIG_IS_ENABLED(PRE_CONSOLE_BUFFER) + char buf[DISPLAY_OPTIONS_BANNER_LENGTH]; + ++ if (IS_ENABLED(CONFIG_SILENT_CONSOLE) && (gd->flags & GD_FLG_SILENT)) ++ return 0; ++ + display_options_get_banner(false, buf, sizeof(buf)); + + console_puts_select(stdout, false, buf); diff --git a/pkgbuilds/uboot-asahi/raspberrypi-Add-quirk-for-RPi5-2Gb-rev-1.0.patch b/pkgbuilds/uboot-asahi/raspberrypi-Add-quirk-for-RPi5-2Gb-rev-1.0.patch new file mode 100644 index 0000000..d6612ee --- /dev/null +++ b/pkgbuilds/uboot-asahi/raspberrypi-Add-quirk-for-RPi5-2Gb-rev-1.0.patch @@ -0,0 +1,32 @@ +From 72fd4fdb54721a31d2b4e9f95fdd3b3e398bb2c0 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Sun, 12 Apr 2026 01:14:12 +0100 +Subject: [PATCH] board/raspberrypi: Add quirk for RPi5 2Gb rev 1.0 + +All 2Gb Raspberry Pi 5s use the BCM2712D0 silicon, but +the early boards were still rev 1.0 so we need a quirk +to ensure the correct device tree is used. + +Signed-off-by: Peter Robinson +--- + board/raspberrypi/rpi/rpi.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/board/raspberrypi/rpi/rpi.c b/board/raspberrypi/rpi/rpi.c +index b0a1484c0fa..ff9cab6521b 100644 +--- a/board/raspberrypi/rpi/rpi.c ++++ b/board/raspberrypi/rpi/rpi.c +@@ -371,6 +371,10 @@ static void set_fdtfile(void) + const char *fdtfile; + int rev = revision & 0x0f; + ++ /* 2GB Pi 5B board rev 1.0 always has BCM2712D0 silicon */ ++ if (revision == 0xb04170) ++ rev = 1; ++ + if (env_get("fdtfile")) + return; + +-- +2.53.0 + diff --git a/pkgbuilds/uboot-asahi/rockchip-Enable-preboot-start-for-pci-usb.patch b/pkgbuilds/uboot-asahi/rockchip-Enable-preboot-start-for-pci-usb.patch new file mode 100644 index 0000000..30aad4d --- /dev/null +++ b/pkgbuilds/uboot-asahi/rockchip-Enable-preboot-start-for-pci-usb.patch @@ -0,0 +1,860 @@ +From a6909a16b4cc301f1007d9e75d02abb6d06a1ea7 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Thu, 28 May 2026 21:29:29 +0100 +Subject: [PATCH] rockchip: Enable preboot start for pci/usb + +Enable the preboot so nvme works OOTB + +Signed-off-by: Peter Robinson +--- + configs/anbernic-rgxx3-rk3566_defconfig | 2 ++ + configs/bpi-r2-pro-rk3568_defconfig | 2 ++ + configs/cm3588-nas-rk3588_defconfig | 2 ++ + configs/coolpi-4b-rk3588s_defconfig | 2 ++ + configs/coolpi-cm5-evb-rk3588_defconfig | 2 ++ + configs/evb-rk3568_defconfig | 2 ++ + configs/evb-rk3588_defconfig | 2 ++ + configs/firefly-rk3399_defconfig | 2 ++ + configs/generic-rk3568_defconfig | 2 ++ + configs/generic-rk3588_defconfig | 2 ++ + configs/jaguar-rk3588_defconfig | 2 ++ + configs/lubancat-2-rk3568_defconfig | 2 ++ + configs/nanopc-t4-rk3399_defconfig | 2 ++ + configs/nanopc-t6-rk3588_defconfig | 2 ++ + configs/nanopi-r5c-rk3568_defconfig | 2 ++ + configs/nanopi-r5s-rk3568_defconfig | 2 ++ + configs/nanopi-r6c-rk3588s_defconfig | 2 ++ + configs/nanopi-r6s-rk3588s_defconfig | 2 ++ + configs/neu6a-io-rk3588_defconfig | 2 ++ + configs/neu6b-io-rk3588_defconfig | 2 ++ + configs/nova-rk3588s_defconfig | 2 ++ + configs/odroid-m1-rk3568_defconfig | 2 ++ + configs/odroid-m1s-rk3566_defconfig | 2 ++ + configs/odroid-m2-rk3588s_defconfig | 2 ++ + configs/orangepi-3b-rk3566_defconfig | 2 ++ + configs/orangepi-5-max-rk3588_defconfig | 2 ++ + configs/orangepi-5-plus-rk3588_defconfig | 2 ++ + configs/orangepi-5-rk3588s_defconfig | 2 ++ + configs/orangepi-5-ultra-rk3588_defconfig | 2 ++ + configs/pinebook-pro-rk3399_defconfig | 1 + + configs/pinetab2-rk3566_defconfig | 2 ++ + configs/powkiddy-x55-rk3566_defconfig | 2 ++ + configs/quartz64-a-rk3566_defconfig | 2 ++ + configs/quartz64-b-rk3566_defconfig | 4 ++++ + configs/quartzpro64-rk3588_defconfig | 2 ++ + configs/radxa-cm3-io-rk3566_defconfig | 2 ++ + configs/radxa-e25-rk3568_defconfig | 2 ++ + configs/radxa-zero-3-rk3566_defconfig | 2 ++ + configs/roc-pc-mezzanine-rk3399_defconfig | 2 ++ + configs/rock-3a-rk3568_defconfig | 2 ++ + configs/rock-3b-rk3568_defconfig | 2 ++ + configs/rock-3c-rk3566_defconfig | 2 ++ + configs/rock-4c-plus-rk3399_defconfig | 2 ++ + configs/rock-4se-rk3399_defconfig | 2 ++ + configs/rock-5-itx-rk3588_defconfig | 2 ++ + configs/rock-5c-rk3588s_defconfig | 2 ++ + configs/rock-pi-4-rk3399_defconfig | 2 ++ + configs/rock-pi-4c-rk3399_defconfig | 2 ++ + configs/rock-pi-n10-rk3399pro_defconfig | 2 ++ + configs/rock5a-rk3588s_defconfig | 2 ++ + configs/rock5b-rk3588_defconfig | 2 ++ + configs/rock960-rk3399_defconfig | 2 ++ + configs/rockpro64-rk3399_defconfig | 2 ++ + configs/sige7-rk3588_defconfig | 2 ++ + configs/soquartz-blade-rk3566_defconfig | 2 ++ + configs/soquartz-cm4-rk3566_defconfig | 2 ++ + configs/soquartz-model-a-rk3566_defconfig | 2 ++ + configs/tiger-rk3588_defconfig | 2 ++ + configs/toybrick-rk3588_defconfig | 2 ++ + configs/turing-rk1-rk3588_defconfig | 2 ++ + 60 files changed, 121 insertions(+) + +diff --git a/configs/anbernic-rgxx3-rk3566_defconfig b/configs/anbernic-rgxx3-rk3566_defconfig +index 854de8b00a4..cd91f18ba80 100644 +--- a/configs/anbernic-rgxx3-rk3566_defconfig ++++ b/configs/anbernic-rgxx3-rk3566_defconfig +@@ -12,6 +12,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + # CONFIG_EFI_LOADER is not set + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y +diff --git a/configs/bpi-r2-pro-rk3568_defconfig b/configs/bpi-r2-pro-rk3568_defconfig +index c5199a39448..603e07f2456 100644 +--- a/configs/bpi-r2-pro-rk3568_defconfig ++++ b/configs/bpi-r2-pro-rk3568_defconfig +@@ -9,6 +9,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/cm3588-nas-rk3588_defconfig b/configs/cm3588-nas-rk3588_defconfig +index 5b5ef8d5ac1..53147d115dd 100644 +--- a/configs/cm3588-nas-rk3588_defconfig ++++ b/configs/cm3588-nas-rk3588_defconfig +@@ -12,6 +12,8 @@ CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/coolpi-4b-rk3588s_defconfig b/configs/coolpi-4b-rk3588s_defconfig +index e6772622403..75e0b373ce9 100644 +--- a/configs/coolpi-4b-rk3588s_defconfig ++++ b/configs/coolpi-4b-rk3588s_defconfig +@@ -17,6 +17,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/coolpi-cm5-evb-rk3588_defconfig b/configs/coolpi-cm5-evb-rk3588_defconfig +index 4326e116849..a005e4441d3 100644 +--- a/configs/coolpi-cm5-evb-rk3588_defconfig ++++ b/configs/coolpi-cm5-evb-rk3588_defconfig +@@ -17,6 +17,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/evb-rk3568_defconfig b/configs/evb-rk3568_defconfig +index 4a5a7454e10..7ec199a08af 100644 +--- a/configs/evb-rk3568_defconfig ++++ b/configs/evb-rk3568_defconfig +@@ -9,6 +9,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/evb-rk3588_defconfig b/configs/evb-rk3588_defconfig +index c7f6256a626..cfa73c660e7 100644 +--- a/configs/evb-rk3588_defconfig ++++ b/configs/evb-rk3588_defconfig +@@ -10,6 +10,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/firefly-rk3399_defconfig b/configs/firefly-rk3399_defconfig +index 83b9de5feb9..2ba8f5029c3 100644 +--- a/configs/firefly-rk3399_defconfig ++++ b/configs/firefly-rk3399_defconfig +@@ -14,6 +14,8 @@ CONFIG_DEBUG_UART_BASE=0xFF1A0000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399-firefly.dtb" + CONFIG_DISPLAY_BOARDINFO_LATE=y + CONFIG_SPL_MAX_SIZE=0x40000 +diff --git a/configs/generic-rk3568_defconfig b/configs/generic-rk3568_defconfig +index a33c3af9255..20966793568 100644 +--- a/configs/generic-rk3568_defconfig ++++ b/configs/generic-rk3568_defconfig +@@ -14,6 +14,8 @@ CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/generic-rk3588_defconfig b/configs/generic-rk3588_defconfig +index ac450e6d757..b5424ac9f16 100644 +--- a/configs/generic-rk3588_defconfig ++++ b/configs/generic-rk3588_defconfig +@@ -10,6 +10,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/jaguar-rk3588_defconfig b/configs/jaguar-rk3588_defconfig +index ed3883b4f9c..47dbd834f30 100644 +--- a/configs/jaguar-rk3588_defconfig ++++ b/configs/jaguar-rk3588_defconfig +@@ -16,6 +16,8 @@ CONFIG_DEBUG_UART_BASE=0xfeb50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/lubancat-2-rk3568_defconfig b/configs/lubancat-2-rk3568_defconfig +index 7548bf60b06..2fb8a432503 100644 +--- a/configs/lubancat-2-rk3568_defconfig ++++ b/configs/lubancat-2-rk3568_defconfig +@@ -9,6 +9,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_EFI_VAR_BUF_SIZE=16384 + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y +diff --git a/configs/nanopc-t4-rk3399_defconfig b/configs/nanopc-t4-rk3399_defconfig +index f8ae436a1ee..7aa8e1a8d42 100644 +--- a/configs/nanopc-t4-rk3399_defconfig ++++ b/configs/nanopc-t4-rk3399_defconfig +@@ -15,6 +15,8 @@ CONFIG_DEBUG_UART_BASE=0xFF1A0000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399-nanopc-t4.dtb" + CONFIG_DISPLAY_BOARDINFO_LATE=y + CONFIG_SPL_MAX_SIZE=0x40000 +diff --git a/configs/nanopc-t6-rk3588_defconfig b/configs/nanopc-t6-rk3588_defconfig +index ba9a5adece7..eaf7fa5a0fc 100644 +--- a/configs/nanopc-t6-rk3588_defconfig ++++ b/configs/nanopc-t6-rk3588_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/nanopi-r5c-rk3568_defconfig b/configs/nanopi-r5c-rk3568_defconfig +index e75a24dadc8..159ef033932 100644 +--- a/configs/nanopi-r5c-rk3568_defconfig ++++ b/configs/nanopi-r5c-rk3568_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="usb start;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/nanopi-r5s-rk3568_defconfig b/configs/nanopi-r5s-rk3568_defconfig +index c971f376b38..60b5e8fa014 100644 +--- a/configs/nanopi-r5s-rk3568_defconfig ++++ b/configs/nanopi-r5s-rk3568_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="usb start;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/nanopi-r6c-rk3588s_defconfig b/configs/nanopi-r6c-rk3588s_defconfig +index 861e3234d29..bb6e2082e3b 100644 +--- a/configs/nanopi-r6c-rk3588s_defconfig ++++ b/configs/nanopi-r6c-rk3588s_defconfig +@@ -12,6 +12,8 @@ CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/nanopi-r6s-rk3588s_defconfig b/configs/nanopi-r6s-rk3588s_defconfig +index 743dfca441b..903ec56ad6f 100644 +--- a/configs/nanopi-r6s-rk3588s_defconfig ++++ b/configs/nanopi-r6s-rk3588s_defconfig +@@ -12,6 +12,8 @@ CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/neu6a-io-rk3588_defconfig b/configs/neu6a-io-rk3588_defconfig +index 19f129e96f0..08ed699b01d 100644 +--- a/configs/neu6a-io-rk3588_defconfig ++++ b/configs/neu6a-io-rk3588_defconfig +@@ -10,6 +10,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/neu6b-io-rk3588_defconfig b/configs/neu6b-io-rk3588_defconfig +index 2cf5aeccf51..8cafd779a1c 100644 +--- a/configs/neu6b-io-rk3588_defconfig ++++ b/configs/neu6b-io-rk3588_defconfig +@@ -10,6 +10,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/nova-rk3588s_defconfig b/configs/nova-rk3588s_defconfig +index f7d7492ff46..5abb6ebbe18 100644 +--- a/configs/nova-rk3588s_defconfig ++++ b/configs/nova-rk3588s_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/odroid-m1-rk3568_defconfig b/configs/odroid-m1-rk3568_defconfig +index b00985b0405..19d7af53c7f 100644 +--- a/configs/odroid-m1-rk3568_defconfig ++++ b/configs/odroid-m1-rk3568_defconfig +@@ -17,6 +17,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/odroid-m1s-rk3566_defconfig b/configs/odroid-m1s-rk3566_defconfig +index d59882a2a59..20f6dc3c26e 100644 +--- a/configs/odroid-m1s-rk3566_defconfig ++++ b/configs/odroid-m1s-rk3566_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/odroid-m2-rk3588s_defconfig b/configs/odroid-m2-rk3588s_defconfig +index e71e61ddb0b..561c2b28b7a 100644 +--- a/configs/odroid-m2-rk3588s_defconfig ++++ b/configs/odroid-m2-rk3588s_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/orangepi-3b-rk3566_defconfig b/configs/orangepi-3b-rk3566_defconfig +index 1cd78789fa1..dcf182231da 100644 +--- a/configs/orangepi-3b-rk3566_defconfig ++++ b/configs/orangepi-3b-rk3566_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/orangepi-5-max-rk3588_defconfig b/configs/orangepi-5-max-rk3588_defconfig +index 0861df962cf..744aad89c4a 100644 +--- a/configs/orangepi-5-max-rk3588_defconfig ++++ b/configs/orangepi-5-max-rk3588_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/orangepi-5-plus-rk3588_defconfig b/configs/orangepi-5-plus-rk3588_defconfig +index 52523f87aec..e3bb4e43da9 100644 +--- a/configs/orangepi-5-plus-rk3588_defconfig ++++ b/configs/orangepi-5-plus-rk3588_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/orangepi-5-rk3588s_defconfig b/configs/orangepi-5-rk3588s_defconfig +index e0eba389289..33fe68add7b 100644 +--- a/configs/orangepi-5-rk3588s_defconfig ++++ b/configs/orangepi-5-rk3588s_defconfig +@@ -17,6 +17,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/orangepi-5-ultra-rk3588_defconfig b/configs/orangepi-5-ultra-rk3588_defconfig +index fb7f4432983..62419609695 100644 +--- a/configs/orangepi-5-ultra-rk3588_defconfig ++++ b/configs/orangepi-5-ultra-rk3588_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/pinebook-pro-rk3399_defconfig b/configs/pinebook-pro-rk3399_defconfig +index cbf95b4692f..9e4d1b1061d 100644 +--- a/configs/pinebook-pro-rk3399_defconfig ++++ b/configs/pinebook-pro-rk3399_defconfig +@@ -22,6 +22,7 @@ CONFIG_PCI=y + CONFIG_DEBUG_UART=y + CONFIG_BOOTDELAY=3 + CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399-pinebook-pro.dtb" + CONFIG_DISPLAY_BOARDINFO_LATE=y + CONFIG_SPL_MAX_SIZE=0x40000 +diff --git a/configs/pinetab2-rk3566_defconfig b/configs/pinetab2-rk3566_defconfig +index f284dadb99d..d87849f0a45 100644 +--- a/configs/pinetab2-rk3566_defconfig ++++ b/configs/pinetab2-rk3566_defconfig +@@ -18,6 +18,8 @@ CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/powkiddy-x55-rk3566_defconfig b/configs/powkiddy-x55-rk3566_defconfig +index 37a359c91a9..70bc5ab6402 100644 +--- a/configs/powkiddy-x55-rk3566_defconfig ++++ b/configs/powkiddy-x55-rk3566_defconfig +@@ -9,6 +9,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/quartz64-a-rk3566_defconfig b/configs/quartz64-a-rk3566_defconfig +index 780fd581f0a..7938b5c0d1b 100644 +--- a/configs/quartz64-a-rk3566_defconfig ++++ b/configs/quartz64-a-rk3566_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/quartz64-b-rk3566_defconfig b/configs/quartz64-b-rk3566_defconfig +index 4d92a67cd73..fe8a168373d 100644 +--- a/configs/quartz64-b-rk3566_defconfig ++++ b/configs/quartz64-b-rk3566_defconfig +@@ -1,4 +1,6 @@ + CONFIG_ARM=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_SKIP_LOWLEVEL_INIT=y + CONFIG_COUNTER_FREQUENCY=24000000 + CONFIG_ARCH_ROCKCHIP=y +@@ -17,6 +19,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/quartzpro64-rk3588_defconfig b/configs/quartzpro64-rk3588_defconfig +index 0ad74e8bbef..b9cbddfae77 100644 +--- a/configs/quartzpro64-rk3588_defconfig ++++ b/configs/quartzpro64-rk3588_defconfig +@@ -12,6 +12,8 @@ CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/radxa-cm3-io-rk3566_defconfig b/configs/radxa-cm3-io-rk3566_defconfig +index 875f0b76fa5..2523dccd6a3 100644 +--- a/configs/radxa-cm3-io-rk3566_defconfig ++++ b/configs/radxa-cm3-io-rk3566_defconfig +@@ -9,6 +9,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/radxa-e25-rk3568_defconfig b/configs/radxa-e25-rk3568_defconfig +index 779d540e22d..7e003e74520 100644 +--- a/configs/radxa-e25-rk3568_defconfig ++++ b/configs/radxa-e25-rk3568_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/radxa-zero-3-rk3566_defconfig b/configs/radxa-zero-3-rk3566_defconfig +index f5cf09a5e4a..7db7e3fcd3d 100644 +--- a/configs/radxa-zero-3-rk3566_defconfig ++++ b/configs/radxa-zero-3-rk3566_defconfig +@@ -11,6 +11,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/roc-pc-mezzanine-rk3399_defconfig b/configs/roc-pc-mezzanine-rk3399_defconfig +index 8b4d8c1b0aa..54baa07aeae 100644 +--- a/configs/roc-pc-mezzanine-rk3399_defconfig ++++ b/configs/roc-pc-mezzanine-rk3399_defconfig +@@ -21,6 +21,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + # CONFIG_ANDROID_BOOT_IMAGE is not set + CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399-roc-pc-mezzanine.dtb" + CONFIG_DISPLAY_BOARDINFO_LATE=y +diff --git a/configs/rock-3a-rk3568_defconfig b/configs/rock-3a-rk3568_defconfig +index e753185610f..19d303e7f70 100644 +--- a/configs/rock-3a-rk3568_defconfig ++++ b/configs/rock-3a-rk3568_defconfig +@@ -16,6 +16,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/rock-3b-rk3568_defconfig b/configs/rock-3b-rk3568_defconfig +index c3bf48b9e34..0cfa8eba168 100644 +--- a/configs/rock-3b-rk3568_defconfig ++++ b/configs/rock-3b-rk3568_defconfig +@@ -16,6 +16,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/rock-3c-rk3566_defconfig b/configs/rock-3c-rk3566_defconfig +index 417aa33a663..61d0e1b8c6a 100644 +--- a/configs/rock-3c-rk3566_defconfig ++++ b/configs/rock-3c-rk3566_defconfig +@@ -16,6 +16,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/rock-4c-plus-rk3399_defconfig b/configs/rock-4c-plus-rk3399_defconfig +index 0f6bdfdfb06..f31cb061a83 100644 +--- a/configs/rock-4c-plus-rk3399_defconfig ++++ b/configs/rock-4c-plus-rk3399_defconfig +@@ -17,6 +17,8 @@ CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_EFI_CAPSULE_ON_DISK=y + CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y + # CONFIG_ANDROID_BOOT_IMAGE is not set +diff --git a/configs/rock-4se-rk3399_defconfig b/configs/rock-4se-rk3399_defconfig +index 7f22aede912..caf8e02bece 100644 +--- a/configs/rock-4se-rk3399_defconfig ++++ b/configs/rock-4se-rk3399_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_EFI_CAPSULE_ON_DISK=y + CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y + # CONFIG_ANDROID_BOOT_IMAGE is not set +diff --git a/configs/rock-5-itx-rk3588_defconfig b/configs/rock-5-itx-rk3588_defconfig +index cb014de4188..e26a126533c 100644 +--- a/configs/rock-5-itx-rk3588_defconfig ++++ b/configs/rock-5-itx-rk3588_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/rock-5c-rk3588s_defconfig b/configs/rock-5c-rk3588s_defconfig +index 3d044a81498..27234c250d1 100644 +--- a/configs/rock-5c-rk3588s_defconfig ++++ b/configs/rock-5c-rk3588s_defconfig +@@ -12,6 +12,8 @@ CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/rock-pi-4-rk3399_defconfig b/configs/rock-pi-4-rk3399_defconfig +index e5c9072c27f..35852c017da 100644 +--- a/configs/rock-pi-4-rk3399_defconfig ++++ b/configs/rock-pi-4-rk3399_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_EFI_CAPSULE_ON_DISK=y + CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y + # CONFIG_ANDROID_BOOT_IMAGE is not set +diff --git a/configs/rock-pi-4c-rk3399_defconfig b/configs/rock-pi-4c-rk3399_defconfig +index 4be7b0f8130..eeee446a5bd 100644 +--- a/configs/rock-pi-4c-rk3399_defconfig ++++ b/configs/rock-pi-4c-rk3399_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_EFI_CAPSULE_ON_DISK=y + CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y + # CONFIG_ANDROID_BOOT_IMAGE is not set +diff --git a/configs/rock-pi-n10-rk3399pro_defconfig b/configs/rock-pi-n10-rk3399pro_defconfig +index 602a723ea7d..3dcc2932ae3 100644 +--- a/configs/rock-pi-n10-rk3399pro_defconfig ++++ b/configs/rock-pi-n10-rk3399pro_defconfig +@@ -13,6 +13,8 @@ CONFIG_DEBUG_UART_BASE=0xFF1A0000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + # CONFIG_ANDROID_BOOT_IMAGE is not set + CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399pro-rock-pi-n10.dtb" + # CONFIG_CONSOLE_MUX is not set +diff --git a/configs/rock5a-rk3588s_defconfig b/configs/rock5a-rk3588s_defconfig +index 24b2eef35f2..c5830054921 100644 +--- a/configs/rock5a-rk3588s_defconfig ++++ b/configs/rock5a-rk3588s_defconfig +@@ -10,6 +10,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/rock5b-rk3588_defconfig b/configs/rock5b-rk3588_defconfig +index da5fb3a2c30..053346fc035 100644 +--- a/configs/rock5b-rk3588_defconfig ++++ b/configs/rock5b-rk3588_defconfig +@@ -18,6 +18,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/rock960-rk3399_defconfig b/configs/rock960-rk3399_defconfig +index db121f03ae6..e55f1b6a034 100644 +--- a/configs/rock960-rk3399_defconfig ++++ b/configs/rock960-rk3399_defconfig +@@ -12,6 +12,8 @@ CONFIG_DEBUG_UART_BASE=0xFF1A0000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399-rock960.dtb" + CONFIG_SYS_PBSIZE=1052 + CONFIG_DISPLAY_BOARDINFO_LATE=y +diff --git a/configs/rockpro64-rk3399_defconfig b/configs/rockpro64-rk3399_defconfig +index 1d2bff35fc0..ea8e90b9f6a 100644 +--- a/configs/rockpro64-rk3399_defconfig ++++ b/configs/rockpro64-rk3399_defconfig +@@ -20,6 +20,8 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399-rockpro64.dtb" + CONFIG_DISPLAY_BOARDINFO_LATE=y + CONFIG_SPL_MAX_SIZE=0x40000 +diff --git a/configs/sige7-rk3588_defconfig b/configs/sige7-rk3588_defconfig +index 00b9759715b..1b837bd554c 100644 +--- a/configs/sige7-rk3588_defconfig ++++ b/configs/sige7-rk3588_defconfig +@@ -13,6 +13,8 @@ CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_SPL_SPI=y + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/soquartz-blade-rk3566_defconfig b/configs/soquartz-blade-rk3566_defconfig +index 7de159a8881..4137f0459b5 100644 +--- a/configs/soquartz-blade-rk3566_defconfig ++++ b/configs/soquartz-blade-rk3566_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/soquartz-cm4-rk3566_defconfig b/configs/soquartz-cm4-rk3566_defconfig +index 4455ee5b5aa..ad63ba0204f 100644 +--- a/configs/soquartz-cm4-rk3566_defconfig ++++ b/configs/soquartz-cm4-rk3566_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/soquartz-model-a-rk3566_defconfig b/configs/soquartz-model-a-rk3566_defconfig +index abdab514496..9e01345bdaa 100644 +--- a/configs/soquartz-model-a-rk3566_defconfig ++++ b/configs/soquartz-model-a-rk3566_defconfig +@@ -11,6 +11,8 @@ CONFIG_DEBUG_UART_BASE=0xFE660000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/tiger-rk3588_defconfig b/configs/tiger-rk3588_defconfig +index 105e8e10e0b..bcc2ae302a5 100644 +--- a/configs/tiger-rk3588_defconfig ++++ b/configs/tiger-rk3588_defconfig +@@ -17,6 +17,8 @@ CONFIG_DEBUG_UART_CLOCK=24000000 + # CONFIG_DEBUG_UART_BOARD_INIT is not set + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/toybrick-rk3588_defconfig b/configs/toybrick-rk3588_defconfig +index 14b231ae3e2..7af393731cd 100644 +--- a/configs/toybrick-rk3588_defconfig ++++ b/configs/toybrick-rk3588_defconfig +@@ -10,6 +10,8 @@ CONFIG_SYS_LOAD_ADDR=0xc00800 + CONFIG_DEBUG_UART_BASE=0xFEB50000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +diff --git a/configs/turing-rk1-rk3588_defconfig b/configs/turing-rk1-rk3588_defconfig +index 0b3efec23e6..515db2a2b0c 100644 +--- a/configs/turing-rk1-rk3588_defconfig ++++ b/configs/turing-rk1-rk3588_defconfig +@@ -12,6 +12,8 @@ CONFIG_DEBUG_UART_BASE=0xFEBC0000 + CONFIG_DEBUG_UART_CLOCK=24000000 + CONFIG_PCI=y + CONFIG_DEBUG_UART=y ++CONFIG_USE_PREBOOT=y ++CONFIG_PREBOOT="pci enum; usb start; nvme scan;" + CONFIG_FIT=y + CONFIG_FIT_VERBOSE=y + CONFIG_SPL_FIT_SIGNATURE=y +-- +2.54.0 + diff --git a/pkgbuilds/uboot-asahi/rockchip-rk3568-nanopi-r5-Drop-duplicated-extra-sdhc.patch b/pkgbuilds/uboot-asahi/rockchip-rk3568-nanopi-r5-Drop-duplicated-extra-sdhc.patch new file mode 100644 index 0000000..bef5a9e --- /dev/null +++ b/pkgbuilds/uboot-asahi/rockchip-rk3568-nanopi-r5-Drop-duplicated-extra-sdhc.patch @@ -0,0 +1,39 @@ +From aef38432fbc93621b434836b95b10c28aa880e84 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Tue, 9 Dec 2025 20:51:06 +0000 +Subject: [PATCH 1/2] rockchip: rk3568-nanopi-r5: Drop duplicated/extra sdhci + +The pinctrl settings are now upstream, with the upstream +voltage supplies, and the HS200 mode is confirmed to work +fine but the HS400 mode had reported issues so let's just +consume what the upstream DT has now as it's all known good. + +Reported-by: Dusty Mabe +Tested-by: Dusty Mabe +Signed-off-by: Peter Robinson +--- + arch/arm/dts/rk3568-nanopi-r5s-u-boot.dtsi | 8 -------- + 1 file changed, 8 deletions(-) + +diff --git a/arch/arm/dts/rk3568-nanopi-r5s-u-boot.dtsi b/arch/arm/dts/rk3568-nanopi-r5s-u-boot.dtsi +index 64c43374c04..7e24be1a5e7 100644 +--- a/arch/arm/dts/rk3568-nanopi-r5s-u-boot.dtsi ++++ b/arch/arm/dts/rk3568-nanopi-r5s-u-boot.dtsi +@@ -12,14 +12,6 @@ + /delete-property/ vpcie3v3-supply; + }; + +-&sdhci { +- cap-mmc-highspeed; +- mmc-hs200-1_8v; +- mmc-hs400-1_8v; +- mmc-hs400-enhanced-strobe; +- pinctrl-0 = <&emmc_bus8 &emmc_clk &emmc_cmd &emmc_datastrobe>; +-}; +- + &vcc5v0_usb_host { + /delete-property/ regulator-always-on; + /delete-property/ regulator-boot-on; +-- +2.52.0 + diff --git a/pkgbuilds/uboot-asahi/rockchip-rk356x-Stop-overriding-sdhci-mmc-aliases.patch b/pkgbuilds/uboot-asahi/rockchip-rk356x-Stop-overriding-sdhci-mmc-aliases.patch new file mode 100644 index 0000000..c67e4af --- /dev/null +++ b/pkgbuilds/uboot-asahi/rockchip-rk356x-Stop-overriding-sdhci-mmc-aliases.patch @@ -0,0 +1,33 @@ +From 29efc0396809384cf37bfcfb01c62e63216fa998 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Tue, 9 Dec 2025 21:13:46 +0000 +Subject: [PATCH 2/2] rockchip: rk356x: Stop overriding sdhci/mmc aliases + +Upstream device tree has rules where the aliases are board +specific settings, no SoC settings. Looking at upstream +rk356x boards there's a lot of variation in the setting of +mmc0/mmc1 based on the device so we should not be overriding +this here and now we sync to upstream we should just consume +those settings, to do otherwise confuses users. + +Signed-off-by: Peter Robinson +--- + arch/arm/dts/rk356x-u-boot.dtsi | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/arch/arm/dts/rk356x-u-boot.dtsi b/arch/arm/dts/rk356x-u-boot.dtsi +index 738b9673d35..f25c7970208 100644 +--- a/arch/arm/dts/rk356x-u-boot.dtsi ++++ b/arch/arm/dts/rk356x-u-boot.dtsi +@@ -7,8 +7,6 @@ + + / { + aliases { +- mmc0 = &sdhci; +- mmc1 = &sdmmc0; + spi4 = &sfc; + }; + +-- +2.52.0 + diff --git a/pkgbuilds/uboot-asahi/uefi-Add-all-options-for-EFI-System-Partitions.patch b/pkgbuilds/uboot-asahi/uefi-Add-all-options-for-EFI-System-Partitions.patch new file mode 100644 index 0000000..3bf1ec8 --- /dev/null +++ b/pkgbuilds/uboot-asahi/uefi-Add-all-options-for-EFI-System-Partitions.patch @@ -0,0 +1,57 @@ +From 86782e882e9542f5f402e00cd87efb36ad0552d3 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Sat, 2 Mar 2024 11:17:42 +0000 +Subject: [PATCH v4] disk: dos: Add all options for EFI System Partitions + +The EFI spec states that the ESP can be any of FAT12/16/32 but for +compatibility doesn't necssarily require the partition to be the +EFI partition table ID of 0xef. A number of arm devices will not +find their firmware on a FAT partition with an ID of 0xef so also +allow the original FAT12/16/32 partition IDs as they are also +permissable for an ESP. + +Signed-off-by: Peter Robinson +--- + +v2: +- Add 0x0c option +- Make hex constants consistent +- Move from if to switch statement +v3: +- Fix switch brain fart +v4: +- Drop boot_ind out of switch + + disk/part_dos.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/disk/part_dos.c b/disk/part_dos.c +index 567ead7511d..a35181dff4f 100644 +--- a/disk/part_dos.c ++++ b/disk/part_dos.c +@@ -40,10 +40,21 @@ static int get_bootable(dos_partition_t *p) + { + int ret = 0; + +- if (p->sys_ind == 0xef) ++ switch (p->sys_ind) { ++ case 0x01: ++ case 0x06: ++ case 0x0b: ++ case 0x0c: ++ case 0xef: + ret |= PART_EFI_SYSTEM_PARTITION; ++ break; ++ default: ++ break; ++ } ++ + if (p->boot_ind == 0x80) + ret |= PART_BOOTABLE; ++ + return ret; + } + +-- +2.44.0 + diff --git a/pkgbuilds/uboot-asahi/uefi-distro-load-FDT-from-any-partition-on-boot-device.patch b/pkgbuilds/uboot-asahi/uefi-distro-load-FDT-from-any-partition-on-boot-device.patch new file mode 100644 index 0000000..5954848 --- /dev/null +++ b/pkgbuilds/uboot-asahi/uefi-distro-load-FDT-from-any-partition-on-boot-device.patch @@ -0,0 +1,87 @@ +From 4306c538d4a00dd1aa46c55c3c4005c2b0bf7cd5 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Thu, 24 Nov 2022 12:57:55 +0000 +Subject: [PATCH] distro: load FDT from any partition on boot device + +In the EFI_LOADER boot path, we were only checking the FAT partition +containing the EFI payload for dtb files. But this is somewhat of a +fiction. In reality there will be one small (V)FAT partition containing +grub (or whatever the payload may be), and a second boot partition +containing kernel/initrd/fdt (typically ext4). It is this second +partition where we should be looking for a FDT to load. + +So instead scan all the partitions of the disk containing the EFI +payload. This matches where grub looks for kernel/initrd (barring +custom grub.cfg, in which case the user can use grub's 'devicetree' +command to load the correct FDT). + +The other option is somehow passing the ${fdtfile} to grub so that it +can load the FDT based on selected kernel version location (which grub +knows) and SoC/board specific ${fdtfile} (which grub does not know). + +Signed-off-by: Rob Clark +Signed-off-by: Peter Robinson +--- + include/config_distro_bootcmd.h | 37 +++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 13 deletions(-) + +diff --git a/include/config_distro_bootcmd.h b/include/config_distro_bootcmd.h +index fcb319a20ae..e71004fad9c 100644 +--- a/include/config_distro_bootcmd.h ++++ b/include/config_distro_bootcmd.h +@@ -155,28 +155,39 @@ + "fi\0" \ + \ + "load_efi_dtb=" \ +- "load ${devtype} ${devnum}:${distro_bootpart} " \ +- "${fdt_addr_r} ${prefix}${efi_fdtfile}\0" \ ++ "load ${devtype} ${devnum}:${dtb_devp} " \ ++ "${fdt_addr_r} ${prefix}${efi_fdtfile} && " \ ++ "run boot_efi_binary\0" \ + \ + "efi_dtb_prefixes=/ /dtb/ /dtb/current/\0" \ +- "scan_dev_for_efi=" \ ++ "scan_dev_for_dtb=" \ + "setenv efi_fdtfile ${fdtfile}; " \ + BOOTENV_EFI_SET_FDTFILE_FALLBACK \ + BOOTENV_RUN_EXTENSION_INIT \ +- "for prefix in ${efi_dtb_prefixes}; do " \ +- "if test -e ${devtype} " \ +- "${devnum}:${distro_bootpart} " \ +- "${prefix}${efi_fdtfile}; then " \ +- "run load_efi_dtb; " \ +- BOOTENV_RUN_EXTENSION_APPLY \ +- "fi;" \ +- "done;" \ +- "run boot_efi_bootmgr;" \ ++ "part list ${devtype} ${devnum} dtb_devplist; " \ ++ "env exists dtb_devplist || setenv dtb_devplist " \ ++ "${distro_bootpart}; " \ ++ "for dtb_devp in ${dtb_devplist}; do " \ ++ "for prefix in ${efi_dtb_prefixes}; do " \ ++ "if test -e ${devtype} " \ ++ "${devnum}:${dtb_devp} " \ ++ "${prefix}${efi_fdtfile};"\ ++ " then " \ ++ "echo Found DTB ${devtype} " \ ++ "${devnum}:${dtb_devp} " \ ++ "${prefix}${efi_fdtfile};"\ ++ "run load_efi_dtb; " \ ++ BOOTENV_RUN_EXTENSION_APPLY \ ++ "fi;" \ ++ "done; " \ ++ "done; " \ ++ "run boot_efi_binary\0" \ ++ "scan_dev_for_efi=" \ + "if test -e ${devtype} ${devnum}:${distro_bootpart} " \ + "efi/boot/"BOOTEFI_NAME"; then " \ + "echo Found EFI removable media binary " \ + "efi/boot/"BOOTEFI_NAME"; " \ +- "run boot_efi_binary; " \ ++ "run scan_dev_for_dtb; " \ + "echo EFI LOAD FAILED: continuing...; " \ + "fi; " \ + "setenv efi_fdtfile\0" +-- +2.38.1 + diff --git a/pkgbuilds/uboot-asahi/uefi-enable-SetVariableRT-with-volotile-storage.patch b/pkgbuilds/uboot-asahi/uefi-enable-SetVariableRT-with-volotile-storage.patch new file mode 100644 index 0000000..ee9bcf4 --- /dev/null +++ b/pkgbuilds/uboot-asahi/uefi-enable-SetVariableRT-with-volotile-storage.patch @@ -0,0 +1,33 @@ +From fd736dd720b29fa69a4bb87492f3f94dc6076996 Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Tue, 25 Mar 2025 17:13:44 +0000 +Subject: [PATCH] efi: enable SetVariableRT with volotile storage + +Signed-off-by: Peter Robinson +--- + lib/efi_loader/Kconfig | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig +index d4f6b56afaa..3e522131b01 100644 +--- a/lib/efi_loader/Kconfig ++++ b/lib/efi_loader/Kconfig +@@ -108,6 +108,7 @@ choice + config EFI_VARIABLE_FILE_STORE + bool "Store non-volatile UEFI variables as file" + depends on FAT_WRITE ++ default y + help + Select this option if you want non-volatile UEFI variables to be + stored as file /ubootefi.var on the EFI system partition. +@@ -115,6 +116,7 @@ config EFI_VARIABLE_FILE_STORE + config EFI_RT_VOLATILE_STORE + bool "Allow variable runtime services in volatile storage (e.g RAM)" + depends on EFI_VARIABLE_FILE_STORE ++ default y + help + When EFI variables are stored on file we don't allow SetVariableRT, + since the OS doesn't know how to write that file. At the same time +-- +2.49.0 + diff --git a/pkgbuilds/uboot-asahi/uefi-enable-https-boot-by-default.patch b/pkgbuilds/uboot-asahi/uefi-enable-https-boot-by-default.patch new file mode 100644 index 0000000..dc0a683 --- /dev/null +++ b/pkgbuilds/uboot-asahi/uefi-enable-https-boot-by-default.patch @@ -0,0 +1,82 @@ +From 7eabab2fda12b7a4417e5c7f29a507dfa9e7fffd Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Sun, 3 May 2026 09:24:34 +0100 +Subject: [PATCH] enable https boot by default + +Signed-off-by: Peter Robinson +--- + cmd/Kconfig | 1 + + lib/efi_loader/Kconfig | 1 + + lib/mbedtls/Kconfig | 3 ++- + net/Kconfig | 3 ++- + 4 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/cmd/Kconfig b/cmd/Kconfig +index c71c6824a19..8ff14adffe1 100644 +--- a/cmd/Kconfig ++++ b/cmd/Kconfig +@@ -2289,6 +2289,7 @@ config CMD_WGET + + config WGET_HTTPS + bool "wget https" ++ default y + depends on CMD_WGET + depends on PROT_TCP_LWIP + depends on MBEDTLS_LIB +diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig +index 4cb13ae7c8a..20733429962 100644 +--- a/lib/efi_loader/Kconfig ++++ b/lib/efi_loader/Kconfig +@@ -609,6 +609,7 @@ config EFI_BOOTMGR + + config EFI_HTTP_BOOT + bool "EFI HTTP Boot support" ++ default y + depends on NET + depends on CMDLINE + select CMD_NET +diff --git a/lib/mbedtls/Kconfig b/lib/mbedtls/Kconfig +index 789721ee6cd..7a241c2bc26 100644 +--- a/lib/mbedtls/Kconfig ++++ b/lib/mbedtls/Kconfig +@@ -2,7 +2,7 @@ + + choice + prompt "Crypto libraries (U-Boot Proper)" +- default LEGACY_HASHING_AND_CRYPTO ++ default MBEDTLS_LIB + help + Select crypto libraries. + LEGACY_HASHING_AND_CRYPTO for legacy crypto libraries, +@@ -15,6 +15,7 @@ config LEGACY_HASHING_AND_CRYPTO + + config MBEDTLS_LIB + bool "MbedTLS libraries" ++ default y + select MBEDTLS_LIB_X509 + endchoice + +diff --git a/net/Kconfig b/net/Kconfig +index e712a0dd2ac..3158d37dff7 100644 +--- a/net/Kconfig ++++ b/net/Kconfig +@@ -16,7 +16,7 @@ if NET + + choice + prompt "Networking stack" +- default NET_LEGACY ++ default NET_LWIP + + config NET_LEGACY + bool "Legacy U-Boot networking stack" +@@ -27,6 +27,7 @@ config NET_LEGACY + + config NET_LWIP + bool "Use lwIP for networking stack" ++ default y + select NETDEVICES + help + Include networking support based on the lwIP (lightweight IP) +-- +2.54.0 + diff --git a/pkgbuilds/uboot-asahi/uefi-initial-find_fdt_location-for-finding-the-DT-on-disk.patch b/pkgbuilds/uboot-asahi/uefi-initial-find_fdt_location-for-finding-the-DT-on-disk.patch new file mode 100644 index 0000000..ac58bad --- /dev/null +++ b/pkgbuilds/uboot-asahi/uefi-initial-find_fdt_location-for-finding-the-DT-on-disk.patch @@ -0,0 +1,190 @@ +From 56a985cec8592a8de88e7f254df82f5d444a5ecc Mon Sep 17 00:00:00 2001 +From: Peter Robinson +Date: Thu, 28 May 2026 22:08:07 +0100 +Subject: [PATCH] initial find_fdt_location for finding the DT on disk + +The old distro boot looked for a DT on the first boot partition +in the /dtb or /dtb/current directories, and Fedora extended this +to look not just on the boot partition but all the partitions on +that disk. + +The new ways of booting, both bootstd and bootefi bootmgr processes +don't properly look for those options. This provides a function +to search for the DTB on disk so that if the other DT mechanisms +don't work this will look for one on disk to provide a working DT +so that devices will continue to work as they did previously. + +Signed-off-by: Peter Robinson +--- + cmd/bootmenu.c | 5 ++ + include/efi_loader.h | 2 + + lib/efi_loader/efi_helper.c | 106 +++++++++++++++++++++++++++++++++++- + 3 files changed, 112 insertions(+), 1 deletion(-) + +diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c +index 528afd221d0..a10ea35a687 100644 +--- a/cmd/bootmenu.c ++++ b/cmd/bootmenu.c +@@ -502,6 +502,11 @@ static void handle_uefi_bootnext(void) + u16 bootnext; + efi_status_t ret; + efi_uintn_t size; ++ const char *fdt_opt; ++ ++ /* Find the DTB on disk */ ++ log_debug("We will load the DTB\n"); ++ fdt_opt = find_fdt_location(); + + /* Initialize EFI drivers */ + ret = efi_init_obj_list(); +diff --git a/include/efi_loader.h b/include/efi_loader.h +index 3a4d502631c..b11f8a2cb42 100644 +--- a/include/efi_loader.h ++++ b/include/efi_loader.h +@@ -574,6 +574,8 @@ struct efi_register_notify_event { + struct list_head handles; + }; + ++/* Find the FDT on any partition */ ++char *find_fdt_location(void); + /* called at pre-initialization */ + int efi_init_early(void); + /* Initialize efi execution environment */ +diff --git a/lib/efi_loader/efi_helper.c b/lib/efi_loader/efi_helper.c +index 44b806aadc4..61e982091b9 100644 +--- a/lib/efi_loader/efi_helper.c ++++ b/lib/efi_loader/efi_helper.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -98,6 +99,99 @@ int efi_get_pxe_arch(void) + return -EINVAL; + } + ++/* We need to pass a blk device in and return a location, or a loaded DT */ ++char *find_fdt_location(void) ++{ ++ const char *fdt_filename; ++ /* FIXME: dynamic size */ ++ char fdt_fullpath[60]; ++ const char *prefix; ++ struct udevice *blk; ++ /* FIXME: Fedora really only cares about first 2*/ ++ int MAX_PART = 8; ++ int part; ++ struct disk_partition info; ++ int fdtext; ++ int ret; ++ int retfdt; ++ int retload; ++ loff_t fdtsize; ++ ulong pbraddr; ++ loff_t len_read; ++ ++ /* step one logic: we have the DT name we're hunting for */ ++ fdt_filename = env_get("fdtfile"); ++ if (fdt_filename) { ++ log_debug("FFL: We're looking for the DTB now!: %s\n", fdt_filename); ++ ++ /* probe all block disks to search for fdt */ ++ uclass_foreach_dev_probe(UCLASS_BLK, blk) { ++ log_debug("FFL: dev name: %s\n", blk->name); ++ struct blk_desc *desc; ++ desc = dev_get_uclass_plat(blk); ++ /* we should get a partition count here for looping */ ++ /* For loop for each partition */ ++ /* Need to also deal with zero part whole disk - part_get_info_whole_disk */ ++ for (part = 1; part <= MAX_PART; part++) { ++ log_debug("FFL: part num: %d\n", part); ++ ret = part_get_info(desc, part, &info); ++ /* if we have partition check it for DT */ ++ if (ret < 0) { ++ log_debug("FFL: no partition\n"); ++ } else { ++ log_debug("FFL: we have a partition\n"); ++ /* we should have a case/for prefix in =/ /dtb/ /dtb/current/ */ ++ /* but for now we hard code if for Fedora */ ++ prefix = "/dtb"; ++ snprintf(fdt_fullpath, sizeof(fdt_fullpath), "%s/%s", prefix, fdt_filename); ++ log_debug("FFL: full name: %s\n", fdt_fullpath); ++ /* search for DT on partition and either find and exit or continue */ ++ if (fs_set_blk_dev_with_part(desc, part) == 0){ ++ log_debug("FFL: set block part pass\n"); ++ fdtext = fs_exists(fdt_fullpath); ++ if (fdtext) { ++ /* we have a fdt!*/ ++ if (fs_set_blk_dev_with_part(desc, part) == 0){ ++ retfdt = fs_size(fdt_fullpath, &fdtsize); ++ if (retfdt == 0) { ++ log_debug("FFL: we have found a DT on disk, size %lld\n", fdtsize); ++ /* Get the main fdt and map it */ ++ const char *fdt_pbr; ++ fdt_pbr = env_get("fdt_addr_r"); ++ pbraddr = hextoul(fdt_pbr, NULL); ++ log_debug("FFL: fdr addr: %s\n", fdt_pbr); ++ if (fs_set_blk_dev_with_part(desc, part) == 0){ ++ retload = fs_read(fdt_fullpath, pbraddr, 0, fdtsize, &len_read); ++ if (retload == 0) { ++ log_debug("FFL: we have a loaded DT, size %lld we can return\n", fdtsize); ++ log_info("Found DTB: %s\n", fdt_filename); ++ return fdt_pbr; ++ } else { ++ log_debug("FFL: DT load failed\n"); ++ } ++ } ++ } else { ++ log_debug("FFL: we DON'T have a DT with size\n"); ++ } ++ } ++ } else { ++ /* we don't have a fdt!*/ ++ log_debug("FFL: we DON'T have a DT\n"); ++ } ++ } else { ++ log_debug("FFL: set block part FAIL\n"); ++ } ++ } ++ } ++ } ++ } else { ++ log_debug("FFL: fdt_filename not defined!!\n"); ++ } ++ ++ /* We didn't find a FDT */ ++ return NULL; ++} ++ + /** + * efi_create_current_boot_var() - Return Boot#### name were #### is replaced by + * the value of BootCurrent +@@ -558,11 +652,21 @@ efi_status_t efi_install_fdt(void *fdt) + const char *fdt_opt; + uintptr_t fdt_addr; + ++ /* Check if there is device tree loaded from disk */ ++ fdt_opt = find_fdt_location(); ++ if (fdt_opt) ++ log_info("Found DTB on disk\n"); ++ + /* Check if there is a hardware device tree */ +- fdt_opt = env_get("fdt_addr"); ++ if (!fdt_opt) { ++ fdt_opt = env_get("fdt_addr"); ++ if (fdt_opt) ++ log_info("Found DTB: Prior firmware\n"); ++ } + /* Use our own device tree as fallback */ + if (!fdt_opt) { + fdt_opt = env_get("fdtcontroladdr"); ++ log_info("Using DT from U-Boot\n"); + if (!fdt_opt) { + log_err("need device tree\n"); + return EFI_NOT_FOUND; +-- +2.54.0 + 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 48711f6..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": "4e46b8b4ca50f6aed23c848aa4f2d16fbd2432e5" + "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 08ad707..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.137.0 +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=('fd4dff72c44598d3acb885b448256f5d82cf53f59538d97fc7d3c8d8d9d574d3') -sha256sums_aarch64=('8bff558a659d351328f5a1e319802073b59dac42f95cc0f9b2ef1b0c27387431') -sha256sums_armv7h=('b86cdd666e972a5a555d34298c91239bcbaada69ee882c4a36fe1e74d1df2070') +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/pkgbuilds/zed/.omarchy/package.json b/pkgbuilds/zed/.omarchy/package.json new file mode 100644 index 0000000..d93fa79 --- /dev/null +++ b/pkgbuilds/zed/.omarchy/package.json @@ -0,0 +1,9 @@ +{ + "source": "local", + "upstream": { + "watch": { + "github": "zed-industries/zed", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + } +} diff --git a/pkgbuilds/zed/PKGBUILD b/pkgbuilds/zed/PKGBUILD new file mode 100644 index 0000000..9cfd717 --- /dev/null +++ b/pkgbuilds/zed/PKGBUILD @@ -0,0 +1,76 @@ +# Maintainer: omarchy-mac +# Contributor: Burgess Leo (AUR zed-bin) +# +# Zed for aarch64, repacked from the official vendor release. +# Keep the canonical name used by Omarchy and Arch's x86_64 package. +# Adapted from omarchy-mac/omarchy-pkgs-aarch64 PR #25. + +pkgname=zed +pkgver=1.20.2 +pkgrel=1 +pkgdesc='A high-performance, multiplayer code editor from the creators of Atom and Tree-sitter' +arch=('aarch64') +url='https://zed.dev' +license=('GPL-3.0-or-later' 'AGPL-3.0-or-later' 'Apache-2.0') +depends=('alsa-lib' + 'glib2' + 'glibc' + 'gcc-libs' + 'libx11' + 'libxcb' + 'libxkbcommon' + 'libxkbcommon-x11' + 'vulkan-driver' + 'vulkan-icd-loader' + 'wayland' + 'zlib') +optdepends=('openbsd-netcat: SSH remote development support' + 'clang: improved C/C++ language support' + 'cmake: building C/C++ projects' + 'gopls: improved Go language support' + 'pyright: improved Python language support' + 'rust-analyzer: improved Rust language support' + 'typescript-language-server: improved TypeScript/JavaScript support' + 'org.freedesktop.secrets: to keep you logged into your Zed account') +conflicts=('zed-bin' 'zed-git' 'zed-preview-bin') +# The vendor binaries are already stripped. +options=('!strip') +source_aarch64=("${pkgname}-${pkgver}-aarch64.tar.gz::https://github.com/zed-industries/zed/releases/download/v${pkgver}/zed-linux-aarch64.tar.gz") +sha256sums_aarch64=('715a5252234522bc9e8e4a8c1f9b462cf7bb2881eed23b7c8ae650b41c24aa6f') + +package() { + cd 'zed.app' + + # CLI as zed.real, wrapped so the editor never self-updates: it reads + # ZED_UPDATE_EXPLANATION at startup and skips its own update polling when + # it is set. Updates arrive through pacman. + install -Dm755 'bin/zed' "${pkgdir}/usr/bin/zed.real" + cat > "${pkgdir}/usr/bin/zed" <<'SH' +#!/bin/sh +export ZED_UPDATE_EXPLANATION='Updates are handled by your package manager' +exec /usr/bin/zed.real "$@" +SH + chmod 755 "${pkgdir}/usr/bin/zed" + # Arch extra names the CLI zeditor; keep that spelling working too. + ln -s zed "${pkgdir}/usr/bin/zeditor" + + # The CLI looks for ../libexec/zed-editor, then ../lib/zed/zed-editor. + install -Dm755 'libexec/zed-editor' "${pkgdir}/usr/lib/zed/zed-editor" + + # Vendor desktop entry verbatim: Exec=zed and Icon=zed resolve through + # PATH and hicolor. gtk-launch dev.zed.Zed needs exactly this file name. + install -Dm644 'share/applications/dev.zed.Zed.desktop' \ + "${pkgdir}/usr/share/applications/dev.zed.Zed.desktop" + + local size + for size in 512x512 1024x1024; do + install -Dm644 "share/icons/hicolor/${size}/apps/zed.png" \ + "${pkgdir}/usr/share/icons/hicolor/${size}/apps/zed.png" + done + + install -Dm644 'licenses.md' "${pkgdir}/usr/share/licenses/${pkgname}/licenses.md" + + # zed.app/lib (bundled libxcb, libstdc++, libxkbcommon, ...) is deliberately + # not shipped; the depends above provide them. Both binaries carry + # RPATH=$ORIGIN/../lib, which is harmless when that directory is absent. +} diff --git a/pkgbuilds/zed/README.md b/pkgbuilds/zed/README.md new file mode 100644 index 0000000..7487c75 --- /dev/null +++ b/pkgbuilds/zed/README.md @@ -0,0 +1,9 @@ +# Zed on ARM + +Adapted from the tested ARM recipe in omarchy-mac/omarchy-pkgs-aarch64 #25, retaining the AUR zed-bin contributor attribution. Uses official ARM64 vendor binaries, system libraries and the canonical `zed` name. No x86_64 package is added; Arch supplies that architecture separately. + +The wrapper sets `ZED_UPDATE_EXPLANATION` so pacman owns updates. The existing GitHub release watch selects stable version tags and checks the ARM asset through the common upstream updater. Upstream's scheduled sync creates or updates its normal review PR; maintainers need GitHub PR notifications enabled and should monitor failed sync runs too. Initial version 1.20.2 preserves the validated downstream baseline; live release discovery already observes the newer 1.21.0, which requires its own artifact qualification. + +This package supplies the dependency needed by omacom/omarchy-pkgs #537 (`omazed`). Publish and validate it before enabling that dependency on ARM. It conflicts with alternate Zed packages; it does not silently remove them. + +Before this draft becomes ready, require upstream's clean ARM build/install checks and editing a disposable file in a disposable GUI session. CLI version output and ELF inspection do not prove GPU rendering or editing behavior. diff --git a/tests/artifact-helpers.sh b/tests/artifact-helpers.sh new file mode 100755 index 0000000..e3ea1a6 --- /dev/null +++ b/tests/artifact-helpers.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Self-test for helpers/artifact-helpers.sh: package files survive the +# artifact hop between build-pr.yml and publish.yml with makepkg's names +# intact, including the colon an epoch puts in them. +set -euo pipefail +ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$ROOT/helpers/artifact-helpers.sh" +T=$(mktemp -d); trap 'rm -rf "$T"' EXIT +pass() { echo "PASS: $1"; } +fail() { echo "FAIL: $1"; exit 1; } + +EPOCH='cursor-cli-1:2026.09.18.1.9a7762b-1-x86_64.pkg.tar.zst' +PLAIN='beta-1.0-1-x86_64.pkg.tar.zst' +mkdir -p "$T/built" "$T/artifact" "$T/out" +echo epoch > "$T/built/$EPOCH" +echo plain > "$T/built/$PLAIN" +echo sig > "$T/built/$PLAIN.sig" +echo db > "$T/built/omarchy.db.tar.zst" + +pack_packages "$T/built" "$T/artifact/packages.tar" || fail "pack" +[[ "$(tar -tf "$T/artifact/packages.tar" | sort | tr '\n' ' ')" == "$PLAIN $EPOCH " ]] \ + && pass "packages.tar holds the packages only, no signature or database" || fail "tar contents: $(tar -tf "$T/artifact/packages.tar" | tr '\n' ' ')" +[[ "$(ls "$T/artifact")" == "packages.tar" ]] && pass "the artifact path carries no colon" || fail "artifact listing" + +unpack_packages "$T/artifact" "$T/out" || fail "unpack" +[[ "$(ls "$T/out" | sort | tr '\n' ' ')" == "$PLAIN $EPOCH " && "$(cat "$T/out/$EPOCH")" == epoch ]] \ + && pass "epoch filename and bytes survive the round trip" || fail "round trip: $(ls "$T/out" | tr '\n' ' ')" + +# An artifact uploaded before packing existed: bare package files. +mkdir -p "$T/old" "$T/out2"; cp "$T/built"/*.pkg.tar.zst "$T/old/" +unpack_packages "$T/old" "$T/out2" && [[ "$(ls "$T/out2" | sort | tr '\n' ' ')" == "$PLAIN $EPOCH " ]] \ + && pass "a bare pre-packing artifact still unpacks" || fail "bare artifact" + +# The workflows call these bare under `bash -e`, so any non-zero status +# inside them ends the step. (The first version used `shopt -p nullglob`, +# which exits 1 when the option is off; the tests above never saw it because +# `||` suppresses errexit.) +mkdir -p "$T/out4" "$T/out5" +bash -e -c "source '$ROOT/helpers/artifact-helpers.sh'; pack_packages '$T/built' '$T/out4/packages.tar'; tar -tf '$T/out4/packages.tar' >/dev/null; unpack_packages '$T/out4' '$T/out5'" \ + && [[ "$(ls "$T/out5" | sort | tr '\n' ' ')" == "$PLAIN $EPOCH " ]] \ + && pass "pack and unpack succeed under bash -e, as the workflows call them" || fail "bash -e" + +mkdir -p "$T/empty" +if pack_packages "$T/empty" "$T/x.tar" 2>/dev/null; then fail "packing an empty build dir should fail"; else pass "empty build dir refused"; fi +if unpack_packages "$T/empty" "$T/out3" 2>/dev/null; then fail "an empty artifact should fail"; else pass "empty artifact refused"; fi 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/builder-image.cjs b/tests/builder-image.cjs new file mode 100644 index 0000000..c20f306 --- /dev/null +++ b/tests/builder-image.cjs @@ -0,0 +1,130 @@ +const assert = require('node:assert/strict'); +const { spawnSync } = require('node:child_process'); +const { chmodSync, cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, utimesSync, writeFileSync } = require('node:fs'); +const { tmpdir } = require('node:os'); +const { join } = require('node:path'); +const { test } = require('node:test'); + +const root = join(__dirname, '..'); +const workflow = readFileSync(join(root, '.github/workflows/builder-images.yml'), 'utf8'); + +function fixture(t) { + const directory = mkdtempSync(join(tmpdir(), 'builder-image-test-')); + t.after(() => rmSync(directory, { recursive: true, force: true })); + mkdirSync(join(directory, 'bin')); + mkdirSync(join(directory, 'build')); + cpSync(join(root, 'helpers'), join(directory, 'helpers'), { recursive: true }); + cpSync(join(root, 'bin/builder-image'), join(directory, 'bin/builder-image')); + writeFileSync(join(directory, 'build/Dockerfile'), 'FROM scratch\nCOPY input /input\n'); + writeFileSync(join(directory, 'build/input'), 'original input\n'); + const engine = join(directory, 'engine'); + mkdirSync(engine); + const log = join(directory, 'engine.jsonl'); + writeFileSync(join(engine, 'docker'), `#!/usr/bin/env node +const fs = require('node:fs'); +const args = process.argv.slice(2); +fs.appendFileSync(process.env.ENGINE_LOG, JSON.stringify(args) + '\\n'); +if (args[0] === 'manifest' && process.env.PRIVATE_IMAGE === '1') process.exit(1); +if (args[0] === 'push' && process.env.PUSH_FAIL === '1') process.exit(1); +if (args[0] === 'image' && args[1] === 'inspect') console.log('ghcr.io/omacom/omarchy-pkg-builder@sha256:' + 'a'.repeat(64)); +`); + chmodSync(join(engine, 'docker'), 0o755); + const env = { + ...process.env, PATH: `${engine}:${process.env.PATH}`, CONTAINER_ENGINE: 'docker', + ENGINE_LOG: log, ARCH: 'x86_64', MIRROR: 'edge', + }; + const run = (args, extraEnv = {}) => spawnSync(join(directory, 'bin/builder-image'), args, { + cwd: directory, env: { ...env, ...extraEnv }, encoding: 'utf8', + }); + const key = (...args) => { + const result = run(['key', ...args]); + assert.equal(result.status, 0, result.stderr); + return result.stdout.trim(); + }; + const calls = () => readFileSync(log, 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse); + return { directory, env, run, key, calls }; +} + +test('image keys are stable across checkout location and timestamp changes', t => { + const a = fixture(t); + const b = fixture(t); + const key = a.key(); + assert.match(key, /^v1-x86_64-edge-[a-f0-9]{64}$/); + utimesSync(join(b.directory, 'build/input'), new Date(0), new Date(0)); + assert.equal(b.key(), key); +}); + +test('image keys separate architectures, mirrors, content, modes and symlink targets', t => { + const f = fixture(t); + const keys = new Set([f.key(), f.key('--arch', 'aarch64'), f.key('--mirror', 'rc'), f.key('--mirror', 'stable')]); + writeFileSync(join(f.directory, 'build/input'), 'new input\n'); + keys.add(f.key()); + chmodSync(join(f.directory, 'build/input'), 0o755); + keys.add(f.key()); + symlinkSync('input', join(f.directory, 'build/link')); + keys.add(f.key()); + rmSync(join(f.directory, 'build/link')); + symlinkSync('Dockerfile', join(f.directory, 'build/link')); + keys.add(f.key()); + assert.equal(keys.size, 8); + mkdirSync(join(f.directory, 'pkgbuilds/example'), { recursive: true }); + const key = f.key(); + writeFileSync(join(f.directory, 'pkgbuilds/example/PKGBUILD'), 'pkgver=2\n'); + assert.equal(f.key(), key, 'package changes must not invalidate the build environment'); +}); + +test('fresh builds refresh package layers and record their compatibility key', t => { + const f = fixture(t); + const key = f.key('--arch', 'aarch64'); + const result = f.run(['build', '--arch', 'aarch64', '--tag', 'candidate:test', '--fresh']); + assert.equal(result.status, 0, result.stderr); + const build = f.calls().find(args => args[0] === 'buildx'); + assert.ok(build.includes('--no-cache')); + assert.ok(build.includes('--pull')); + assert.ok(build.includes('--load')); + assert.ok(build.includes('--platform=linux/arm64')); + assert.ok(build.includes(`org.omarchy.builder.key=${key}`)); + assert.ok(build.includes('candidate:test')); +}); + +test('invalid targets fail before starting an image build', t => { + const f = fixture(t); + for (const args of [['key', '--arch', 'invalid'], ['build', '--mirror', 'invalid'], ['key', '--fresh']]) { + assert.notEqual(f.run(args).status, 0); + } +}); + +function publish(f, extraEnv = {}) { + const script = workflow.split(' - name: Publish tested image\n')[1].split(' run: |\n')[1] + .replaceAll('${{ matrix.arch }}', 'x86_64'); + return spawnSync('bash', ['-e', '-o', 'pipefail', '-c', script], { + cwd: f.directory, encoding: 'utf8', env: { + ...f.env, REGISTRY_IMAGE: 'ghcr.io/omacom/omarchy-pkg-builder', CANDIDATE_IMAGE: 'candidate:test', + GH_TOKEN: 'fixture', GH_ACTOR: 'fixture', DOCKER_CONFIG: join(f.directory, 'auth'), + RUNNER_TEMP: f.directory, GITHUB_RUN_ID: '123', GITHUB_RUN_ATTEMPT: '1', + GITHUB_STEP_SUMMARY: join(f.directory, 'summary'), ...extraEnv, + }, + }); +} + +test('a public tested image gets a version tag before the compatible-image tag advances', t => { + const f = fixture(t); + const key = f.key(); + const result = publish(f); + assert.equal(result.status, 0, result.stderr); + const calls = f.calls(); + assert.deepEqual(calls.filter(args => args[0] === 'push').map(args => args[1]), [ + `ghcr.io/omacom/omarchy-pkg-builder:${key}-123-1`, `ghcr.io/omacom/omarchy-pkg-builder:${key}`, + ]); + assert.ok(calls.findIndex(args => args[0] === 'manifest') < calls.findLastIndex(args => args[0] === 'push')); +}); + +test('a private image or failed push never replaces the previous compatible-image tag', t => { + for (const extraEnv of [{ PRIVATE_IMAGE: '1' }, { PUSH_FAIL: '1' }]) { + const f = fixture(t); + const key = f.key(); + const result = publish(f, extraEnv); + assert.notEqual(result.status, 0); + assert.equal(f.calls().some(args => args[0] === 'push' && args[1] === `ghcr.io/omacom/omarchy-pkg-builder:${key}`), false); + } +}); diff --git a/tests/controller.sh b/tests/controller.sh new file mode 100755 index 0000000..d777fc5 --- /dev/null +++ b/tests/controller.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# Self-test for ci/controller.sh: every decision, no cloud. +# +# The controller's two API functions are overridden with canned responses and +# a recorder, then each scenario asserts which creates and deletes it issued. +set -euo pipefail +ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") + +export REPO=o/r DIGITALOCEAN_TOKEN=x GITHUB_TOKEN=x +export CLOUD_INIT="$ROOT/ci/runner-cloud-init.yaml" LOCK=/tmp/controller-test.lock +CONTROLLER_LIBRARY_ONLY=1 source "$ROOT/ci/controller.sh" + +# Calls are recorded to a file: the controller invokes the API functions +# inside command substitutions, and a subshell cannot append to an array. +CALLS_FILE=$(mktemp); trap 'rm -f "$CALLS_FILE"' EXIT +NOW=$(date -u +%FT%TZ) +OLD=$(date -u -d '5 hours ago' +%FT%TZ) + +# Scenario state: DROPLETS is "id status created" lines, QUEUED a count, +# BUSY a count. +do_api() { + local path=$1; shift + echo "do $path $*" >>"$CALLS_FILE" + case "$path" in + droplets\?*) printf '%s\n' "$DROPLETS" | jq -Rs '{droplets: [split("\n")[] | select(length>0) | split(" ") | {id: .[0]|tonumber, status: .[1], created_at: .[2]}]}' ;; + droplets) echo '{"droplet":{"id":999}}' ;; + droplets/*) echo '{}' ;; + esac +} +gh_api() { + local path=$1; shift + echo "gh $path $*" >>"$CALLS_FILE" + case "$path" in + */actions/runs\?*) jq -nc --argjson n "$QUEUED" '{workflow_runs: [range($n) | {id: .}]}' ;; + */actions/runs/*/jobs) echo '{"jobs":[{"id":1,"status":"queued","labels":["self-hosted","omarchy-builder"]}]}' ;; + */actions/runners\?*) jq -nc --argjson n "$BUSY" '{runners: [range($n) | {busy: true, labels: [{name: "omarchy-builder"}]}]}' ;; + */registration-token) echo '{"token":"T"}' ;; + esac +} + +creates() { grep -c '^do droplets -X POST' "$CALLS_FILE" || true; } +deletes() { grep -c '^do droplets/.* -X DELETE' "$CALLS_FILE" || true; } +run() { : >"$CALLS_FILE"; controller_tick >/dev/null; } +check() { # check + local c d; c=$(creates); d=$(deletes) + if [[ "$c" == "$2" && "$d" == "$3" ]]; then echo "PASS: $1"; else echo "FAIL: $1 (creates=$c want $2, deletes=$d want $3)"; cat "$CALLS_FILE"; exit 1; fi +} + +DROPLETS="" QUEUED=0 BUSY=0; run; check "idle: nothing queued, nothing to reap" 0 0 +DROPLETS="" QUEUED=2 BUSY=0; run; check "two queued, none live: create two" 2 0 +DROPLETS="1 active $NOW" QUEUED=1 BUSY=1; run; check "one queued, one live but busy: create one" 1 0 +DROPLETS="1 active $NOW" QUEUED=1 BUSY=0; run; check "one queued, one live and idle: it will take it" 0 0 +DROPLETS="1 off $NOW" QUEUED=0 BUSY=0; run; check "powered-off droplet reaped" 0 1 +DROPLETS="1 active $OLD" QUEUED=0 BUSY=0; run; check "over-age droplet reaped even if active" 0 1 +DROPLETS=$'1 active '"$NOW"$'\n2 active '"$NOW"$'\n3 active '"$NOW"$'\n4 active '"$NOW" QUEUED=3 BUSY=4; MAX_DROPLETS=4; run; check "at cap: no creates" 0 0 +DROPLETS=$'1 active '"$NOW"$'\n2 active '"$NOW" QUEUED=5 BUSY=2; MAX_DROPLETS=3; run; check "cap limits creates to remaining room" 1 0 +DROPLETS="1 off $NOW" QUEUED=1 BUSY=0; MAX_DROPLETS=4; run; check "off droplet is not capacity: reaped and replaced" 1 1 + +# The create body must carry the tag (reaper scope) and substituted user-data. +BODY_FILE=$(mktemp); trap 'rm -f "$CALLS_FILE" "$BODY_FILE"' EXIT +do_api() { if [[ $1 == droplets ]]; then printf '%s' "${*: -1}" >"$BODY_FILE"; echo '{"droplet":{"id":1}}'; else echo '{"droplets":[]}'; fi; } +gh_api() { echo '{"token":"TOK"}'; } +create_droplet >/dev/null +jq -e '.tags == ["omarchy-builder"] and .size == "g5-32vcpu-64gb-50gb" and (.user_data | test("--token \"TOK\"")) and (.user_data | test("__") | not)' "$BODY_FILE" >/dev/null \ + && echo "PASS: create body carries tag, size, substituted user-data" \ + || { echo "FAIL: create body"; jq . "$BODY_FILE" | head -20; exit 1; } diff --git a/tests/dell-xps-touchpad-haptics-install.sh b/tests/dell-xps-touchpad-haptics-install.sh new file mode 100755 index 0000000..2a5583c --- /dev/null +++ b/tests/dell-xps-touchpad-haptics-install.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +INSTALL_SCRIPT="$REPO_ROOT/pkgbuilds/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install" +TEST_ROOT=$(mktemp -d) +trap 'rm -rf "$TEST_ROOT"' EXIT + +# shellcheck source=/dev/null +source "$INSTALL_SCRIPT" + +home="$TEST_ROOT/home" +config_dir="$home/.config/omarchy" +config_path="$config_dir/dell-haptic.conf" +protected_file="$TEST_ROOT/protected" +runuser_call="$TEST_ROOT/runuser-call" +chown_call="$TEST_ROOT/chown-call" +runuser_stub="$TEST_ROOT/runuser" +mkdir -p "$config_dir" +printf 'must remain unchanged\n' >"$protected_file" +ln -s "$protected_file" "$config_path" + +chown() { + printf '%s\n' "$*" >>"$chown_call" +} + +_ensure_user_config test-user "$home" +[[ ! -e $chown_call ]] +[[ ! -e $runuser_call ]] +[[ $(cat "$protected_file") == 'must remain unchanged' ]] + +rm "$config_path" +printf '%s\n' \ + '#!/bin/bash' \ + 'set -euo pipefail' \ + '[[ $1 == --user && $2 == test-user && $3 == -- && $4 == /usr/bin/env ]]' \ + '[[ $5 == "HOME=$EXPECTED_HOME" && $6 == USER=test-user && $7 == LOGNAME=test-user ]]' \ + '[[ $8 == /usr/bin/dell-xps-touchpad-haptics && $9 == set && ${10} == high ]]' \ + 'printf "%s\n" "$*" >>"$RUNUSER_CALL"' \ + 'printf "INTENSITY=100\n" >"$EXPECTED_CONFIG"' >"$runuser_stub" +chmod +x "$runuser_stub" +export EXPECTED_HOME="$home" +export EXPECTED_CONFIG="$config_path" +export RUNUSER_CALL="$runuser_call" +_runuser_path="$runuser_stub" + +_ensure_user_config test-user "$home" +[[ ! -e $chown_call ]] +[[ -f $config_path && ! -L $config_path ]] +[[ $(cat "$config_path") == 'INTENSITY=100' ]] +grep -q '^--user test-user -- /usr/bin/env ' "$runuser_call" + +echo 'PASS: user config creation drops privileges and never chowns symlink targets' diff --git a/tests/limine-mkinitcpio-hook.sh b/tests/limine-mkinitcpio-hook.sh new file mode 100755 index 0000000..f91866b --- /dev/null +++ b/tests/limine-mkinitcpio-hook.sh @@ -0,0 +1,532 @@ +#!/bin/bash +# limine-mkinitcpio-hook: the x86_64 package is upstream's as before. On +# aarch64, limine-apple-gate leaves every machine but an Apple Silicon Mac as +# the x86_64 package behaves; a Mac keeps /boot current through mkinitcpio's +# own kernel hook, and Limine waits until Omarchy activates it. +# +# The package() of the PKGBUILD runs over an upstream-shaped source tree +# (LIMINE_ENTRY_TOOL_SRC may name a real limine-entry-tool checkout instead), +# and pacman transactions replay through the packaged hooks on fixture machines. +set -euo pipefail + +REPO_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +RECIPE=$REPO_ROOT/pkgbuilds/limine-mkinitcpio-hook +GATE=$RECIPE/limine-apple-gate +TEST_ROOT=$(mktemp -d) +trap 'rm -rf "$TEST_ROOT"' EXIT + +fail() { + echo "not ok - $1" >&2 + [[ $# -lt 2 ]] || printf '%s\n' "$2" >&2 + exit 1 +} +pass() { + echo "ok - $1" +} + +# pacman hands NeedsTargets to a hook over a socket, and a hook that stops +# reading early fails the write ("unable to write to pipe"). +SOCKET_FEED=' +import socket, subprocess, sys +parent, child = socket.socketpair() +hook = subprocess.Popen(sys.argv[1:], stdin=child) +child.close() +try: + parent.sendall(sys.stdin.buffer.read()) + parent.shutdown(socket.SHUT_WR) +except OSError as error: + print("unable to write to pipe (%s)" % error.strerror, file=sys.stderr) + hook.wait() + sys.exit(141) +sys.exit(hook.wait()) +' +socket_feed() { python3 -c "$SOCKET_FEED" "$@"; } + +apple_tree() { printf 'apple,j316s\0apple,t6000\0apple,arm-platform\0'; } +live_is_mac() { + local source + for source in /proc/device-tree/compatible /sys/firmware/devicetree/base/compatible; do + [[ -r $source ]] && tr '\0' '\n' <"$source" | grep -q '^apple,' && return 0 + done + return 1 +} + +# The gate trusts fixtures only from an unprivileged caller: as root it reads +# the live machine. Check that, then carry on as nobody. +if ((EUID == 0)); then + if ! live_is_mac; then + mkdir -p "$TEST_ROOT/root-proc/device-tree" + apple_tree >"$TEST_ROOT/root-proc/device-tree/compatible" + out=$(OMARCHY_PROC_ROOT=$TEST_ROOT/root-proc bash "$GATE" echo ran "$upstream/README.md" + echo '# CHANGELOG' >"$upstream/CHANGELOG.md" + echo 'ESP_PATH=""' >"$tool/etc/limine-entry-tool.conf" + echo 'ESP_PATH=""' >"$hook/etc/limine-entry-tool.conf" + for name in limine-install limine-entry-tool limine-reset-enroll limine-enroll-config; do + printf '#!/usr/bin/env bash\n' >"$tool/usr/bin/$name" + done + printf '#!/usr/bin/env bash\n' >"$tool/usr/lib/limine/limine-common-functions" + for name in limine-mkinitcpio limine-update; do + printf '#!/usr/bin/env bash\n' >"$hook/usr/bin/$name" + done + for name in limine-mkinitcpio-install limine-mkinitcpio-remove; do + printf '#!/usr/bin/env bash\n' >"$hook/usr/share/libalpm/scripts/$name" + done + cat >"$tool/usr/share/libalpm/hooks/80-limine-efi-deploy.hook" <<'HOOK' +[Trigger] +Operation = Install +Operation = Upgrade +Type = Package +Target = limine +Target = limine-git +Target = limine-dev + +[Action] +Description = Deploying Limine after upgrade... +When = PostTransaction +Exec = /usr/bin/limine-install --no-efi-register +HOOK + cat >"$hook/usr/share/libalpm/hooks/80-limine-efi-deploy.hook" <<'HOOK' +[Trigger] +Operation = Install +Operation = Upgrade +Type = Package +Target = limine +Target = limine-git +Target = limine-dev +Target = limine-mkinitcpio-hook +Target = limine-mkinitcpio-hook-git + +[Action] +Description = Deploying Limine after upgrade... +When = PostTransaction +Exec = /usr/bin/limine-install +HOOK + cat >"$hook/usr/share/libalpm/hooks/60-limine-mkinitcpio-remove-pre.hook" <<'HOOK' +[Trigger] +Type = Path +Operation = Remove +Target = usr/lib/modules/*/modules.builtin + +[Action] +Description = Record kernels marked for removal in Limine +When = PreTransaction +Exec = /usr/share/libalpm/scripts/limine-mkinitcpio-remove pre +NeedsTargets +HOOK + cat >"$hook/usr/share/libalpm/hooks/90-limine-mkinitcpio-remove-post.hook" <<'HOOK' +[Trigger] +Type = Path +Operation = Remove +Target = usr/lib/modules/*/modules.builtin + +[Action] +Description = Clean Limine boot entries of removed kernels +When = PostTransaction +Exec = /usr/share/libalpm/scripts/limine-mkinitcpio-remove post +HOOK + cat >"$hook/etc/pacman.d/hooks/90-mkinitcpio-install.hook" <<'HOOK' +[Trigger] +Type = Path +Operation = Install +Operation = Upgrade +Operation = Remove +Target = usr/lib/initcpio/* +Target = usr/lib/firmware/* +Target = usr/lib/modules/*/extramodules/ +Target = usr/src/*/dkms.conf +Target = usr/lib/systemd/systemd +Target = usr/bin/cryptsetup +Target = usr/bin/lvm + +[Trigger] +Type = Path +Operation = Install +Operation = Upgrade +Target = usr/lib/modules/*/modules.builtin + +[Trigger] +Type = Package +Operation = Install +Operation = Upgrade +Target = mkinitcpio +Target = mkinitcpio-git + +[Action] +Description = Updating linux initcpios... +When = PostTransaction +Exec = /usr/share/libalpm/scripts/limine-mkinitcpio-install +NeedsTargets +HOOK + cat >"$hook/usr/local/bin/mkinitcpio" <<'WRAPPER' +#!/usr/bin/env bash + +/usr/bin/mkinitcpio "$@" + +_color_reset="" +_color_yellow="" +colors="$(tput colors 2>/dev/null || echo 0)" +if ((colors >= 8)); then + _color_reset="\033[0m" + _color_yellow="\033[1;33m" +fi + +if [[ " $* " == *" -P "* || " $* " == *" --allpresets "* || " $* " == *" -p "* || " $* " == *" --preset "* ]]; then + printf '%b==> WARNING: This does not update Limine boot entries.%b\n' "${_color_yellow}" "${_color_reset}" >&2 + printf "%b Use 'limine-mkinitcpio' or 'limine-update' instead.%b\n" "${_color_yellow}" "${_color_reset}" >&2 + + read -rp "==> Would you like to run 'limine-mkinitcpio' now? [Y/n]: " answer + case "${answer,,}" in + "" | "y" | "yes") + /usr/bin/limine-mkinitcpio + ;; + *) + # nothing + ;; + esac +fi +WRAPPER +fi +printf 'native image\n' >"$upstream/build/native/nativeCompile/limine-entry-tool" +chmod -R u+w,go+rX "$upstream" +find "$upstream/install" -path '*/bin/*' -type f -exec chmod 755 {} + -o -path '*/scripts/*' -type f -exec chmod 755 {} + +ln -s "$RECIPE/limine-apple-gate" "$RECIPE/mkinitcpio-install.hook" "$SRC/" + +# makepkg runs package() with errexit. +build_package() { + local arch=$1 out=$2 + mkdir -p "$out" + CARCH=$arch srcdir=$SRC pkgdir=$out bash -euo pipefail -c 'source "$1"; package' _ "$RECIPE/PKGBUILD" || + fail "package() succeeds for $arch" +} +PKG_X86=$TEST_ROOT/pkg-x86_64 +PKG_ARM=$TEST_ROOT/pkg-aarch64 +build_package x86_64 "$PKG_X86" +build_package aarch64 "$PKG_ARM" + +manifest() { + (cd "$1" && find . \( -type f -o -type l \) -printf '%p %m %l\n' | LC_ALL=C sort) | + while read -r path mode target; do + if [[ -n $target ]]; then + echo "$path $mode -> $target" + else + echo "$path $mode $(sha256sum "$1/$path" | cut -d' ' -f1)" + fi + done +} + +scripts=/usr/share/libalpm/scripts +gate=$scripts/limine-apple-gate +upstream_hook=$upstream/install/arch-linux/limine-mkinitcpio-hook +for file in etc/pacman.d/hooks/90-mkinitcpio-install.hook usr/local/bin/mkinitcpio \ + usr/share/libalpm/hooks/{60-limine-mkinitcpio-remove-pre,80-limine-efi-deploy,90-limine-mkinitcpio-remove-post}.hook; do + cmp -s "$upstream_hook/$file" "$PKG_X86/$file" || fail "x86_64 ships upstream's $file unchanged" +done +[[ ! -e $PKG_X86$gate && ! -e $PKG_X86/usr/share/libalpm/hooks/90-mkinitcpio-apple-install.hook ]] || + fail "x86_64 ships no Apple Silicon file" +pass "x86_64 packages upstream's hooks and wrapper unchanged" + +differences=$(diff <(manifest "$PKG_X86") <(manifest "$PKG_ARM") || true) +changed=$(sed -n 's/^> \(\S*\) .*/\1/p' <<<"$differences") +expected=$(printf '%s\n' ./etc/pacman.d/hooks/90-mkinitcpio-install.hook \ + ./usr/local/bin/mkinitcpio \ + ./usr/share/libalpm/hooks/60-limine-mkinitcpio-remove-pre.hook \ + ./usr/share/libalpm/hooks/80-limine-efi-deploy.hook \ + ./usr/share/libalpm/hooks/90-limine-mkinitcpio-remove-post.hook \ + ./usr/share/libalpm/hooks/90-mkinitcpio-apple-install.hook \ + ./usr/share/libalpm/scripts/limine-apple-gate) +[[ $changed == "$expected" ]] || fail "aarch64 differs from x86_64 only in the gated hooks, the wrapper and the gate" "$changed" +[[ -z $(sed -n 's/^< \(\S*\) .*/\1/p' <<<"$differences" | grep -vxF "$expected") ]] || + fail "aarch64 keeps every x86_64 file" +[[ $(stat -c %a "$PKG_ARM$gate") == 755 ]] || fail "the gate is executable" +diff <(grep -v '^Exec = \|^Description = ' "$RECIPE/mkinitcpio-install.hook") \ + <(grep -v '^Exec = \|^Description = ' "$PKG_ARM/usr/share/libalpm/hooks/90-mkinitcpio-apple-install.hook") >/dev/null || + fail "the Mac's kernel hook keeps mkinitcpio's triggers" +[[ $(sed -n 2p "$PKG_ARM/usr/local/bin/mkinitcpio") == "$gate --mac && exec /usr/bin/mkinitcpio \"\$@\"" ]] || + fail "the aarch64 wrapper is plain mkinitcpio on a Mac" +pass "aarch64 routes Limine's hooks and the wrapper through the gate and adds the Mac's kernel hook" + +# --------------------------------------------------------------------------- +# Fixture machines, read through OMARCHY_PROC_ROOT, OMARCHY_LIMINE_GATE and +# OMARCHY_LIMINE_DEFAULT. +machine() { + local name=$1 compatible=$2 dir=$TEST_ROOT/machines/$1 + mkdir -p "$dir/proc" "$dir/state" + if [[ -n $compatible ]]; then + mkdir -p "$dir/proc/device-tree" + printf "$compatible" >"$dir/proc/device-tree/compatible" + fi +} +machine x86 '' +machine generic-aarch64 'linux,dummy-virt\0' +machine qualcomm 'lenovo,thinkpad-t14s\0qcom,x1e78100\0qcom,x1e80100\0' +machine mac-dormant 'apple,j316s\0apple,t6000\0apple,arm-platform\0' +machine mac-marker-only 'apple,j316s\0apple,t6000\0apple,arm-platform\0' +machine mac-active 'apple,j316s\0apple,t6000\0apple,arm-platform\0' +: >"$TEST_ROOT/machines/mac-marker-only/state/limine.enabled" +: >"$TEST_ROOT/machines/mac-active/state/limine.enabled" +echo 'ESP_PATH="/boot/efi"' >"$TEST_ROOT/machines/mac-active/state/limine" + +on() { + local dir=$TEST_ROOT/machines/$1 + shift + ( + export OMARCHY_PROC_ROOT=$dir/proc OMARCHY_LIMINE_GATE=$dir/state/limine.enabled OMARCHY_LIMINE_DEFAULT=$dir/state/limine + "$@" + ) +} +status() { + local rc=0 + "$@" || rc=$? + echo "$rc" +} + +for m in x86 generic-aarch64 qualcomm; do + [[ $(on "$m" status bash "$GATE" --mac) == 1 ]] || fail "$m is not a Mac" + [[ $(printf 'a\nb\n' | on "$m" bash "$GATE" cat) == $'a\nb' ]] || fail "$m runs Limine's hooks with their targets" + [[ $(on "$m" status bash "$GATE" bash -c 'exit 7' &1) ]] || fail "$m skips the Mac's hooks quietly" + [[ $(on "$m" bash "$GATE" --not-mac echo ran &1) ]] || fail "$m leaves Limine's EFI binary to omarchy-mac-boot" +done +for m in mac-dormant mac-marker-only; do + out=$(printf 'usr/lib/modules/7.1.0/modules.builtin\n' | on "$m" bash "$GATE" bash -c 'echo ran; exit 100' 2>&1) || + fail "$m passes over Limine's hooks successfully" + [[ -z $out ]] || fail "$m passes over Limine's hooks quietly" "$out" + [[ -z $(on "$m" bash "$GATE" echo ran <&- 2>&1) ]] || fail "$m passes over a hook without targets" +done +# A linux-firmware upgrade streams thousands of targets, more than a socket holds. +many_targets() { printf 'usr/lib/firmware/fixture/%s.bin\n' $(seq 60000); } +many_targets | on mac-dormant socket_feed bash "$GATE" true || fail "a dormant Mac reads every target of a hook it passes over" +many_targets | on x86 socket_feed bash "$GATE" --mac true || fail "other machines read every target of the Mac's hooks" +many_targets | on mac-active socket_feed bash "$GATE" --not-mac true || fail "a Mac reads every target of the deploy hook" +many_targets | on mac-dormant bash "$GATE" true || fail "a dormant Mac reads every target piped to a hook it passes over" +[[ $(printf 'a\n' | on mac-active bash "$GATE" bash -c 'echo "ran $1"; cat' _ post) == $'ran post\na' ]] || + fail "an active Mac runs Limine's hooks with their arguments and targets" +[[ $(on mac-active status bash "$GATE" bash -c 'exit 7' /dev/null) == 2 ]] || fail "the gate needs a command" +pass "the gate runs Limine's hooks everywhere but a Mac that has not activated Limine" + +# --------------------------------------------------------------------------- +# pacman: hooks from /usr/share/libalpm/hooks, overridden by name from +# /etc/pacman.d/hooks, run in name order, each with the targets of the +# transaction its triggers match (sorted, over a socket for NeedsTargets, +# else no stdin). +# Transactions are lines of " ". +fake_root() { + local pkg=$1 root=$2 name + cp -a "$pkg" "$root" + cp "$RECIPE/mkinitcpio-install.hook" "$root/usr/share/libalpm/hooks/90-mkinitcpio-install.hook" + for name in usr/share/libalpm/scripts/limine-mkinitcpio-install usr/share/libalpm/scripts/limine-mkinitcpio-remove \ + usr/bin/limine-install usr/share/libalpm/scripts/mkinitcpio usr/bin/mkinitcpio usr/bin/limine-mkinitcpio; do + rm -f "$root/$name" + printf '#!/bin/bash\n{ printf %%s %q; if (($#)); then printf " %%s" "$@"; fi; echo; if [[ -S /dev/stdin || -p /dev/stdin ]]; then sed "s/^/ /"; fi; } >>"$SIM_LOG"\n' \ + "${name##*/}" >"$root/$name" + chmod 755 "$root/$name" + done +} + +run_hook() { + local root=$1 hook=$2 when=$3 transaction=$4 + local line section='' hook_when='' exec_line='' needs=0 type='' ops='' targets='' + local -a triggers=() + while IFS= read -r line || [[ -n $line ]]; do + case $line in + '[Trigger]' | '[Action]') + [[ $section != Trigger ]] || triggers+=("$type|$ops|$targets") + section=${line//[][]/} type='' ops=' ' targets='' + ;; + NeedsTargets) needs=1 ;; + *' = '*) + case $section/${line%% = *} in + Trigger/Type) type=${line#* = } ;; + Trigger/Operation) ops+="${line#* = } " ;; + Trigger/Target) targets+="${line#* = } " ;; + Action/When) hook_when=${line#* = } ;; + Action/Exec) exec_line=${line#* = } ;; + esac + ;; + esac + done <"$hook" + [[ $hook_when == "$when" ]] || return 0 + + local op kind target trigger t_type t_ops t_targets pattern + local -a matched=() patterns=() + while read -r op kind target; do + for trigger in "${triggers[@]}"; do + IFS='|' read -r t_type t_ops t_targets <<<"$trigger" + [[ $t_type == "$kind" && $t_ops == *" $op "* ]] || continue + read -ra patterns <<<"$t_targets" + for pattern in "${patterns[@]}"; do + # shellcheck disable=SC2053 + if [[ $target == $pattern ]]; then + matched+=("$target") + break 2 + fi + done + done + done <"$transaction" + ((${#matched[@]})) || return 0 + + local word + local -a argv=() command=() + read -ra argv <<<"$exec_line" + for word in "${argv[@]}"; do + if [[ $word == /* && -e $root$word ]]; then command+=("$root$word"); else command+=("$word"); fi + done + if ((needs)); then + printf '%s\n' "${matched[@]}" | LC_ALL=C sort -u | socket_feed "${command[@]}" + else + "${command[@]}" <&- + fi +} + +run_transaction() { + local root=$1 transaction=$2 when name + local -A hooks=() + for name in "$root"/usr/share/libalpm/hooks/*.hook "$root"/etc/pacman.d/hooks/*.hook; do + [[ -e $name ]] && hooks[${name##*/}]=$name + done + for when in PreTransaction PostTransaction; do + while read -r name; do + run_hook "$root" "${hooks[$name.hook]}" "$when" "$transaction" + done < <(printf '%s\n' "${!hooks[@]}" | sed 's/\.hook$//' | LC_ALL=C sort) + done +} + +fake_root "$PKG_X86" "$TEST_ROOT/root-x86_64" +fake_root "$PKG_ARM" "$TEST_ROOT/root-aarch64" + +transaction() { + local name=$1 + shift + printf '%s\n' "$@" >"$TEST_ROOT/tx-$name" +} +transaction kernel \ + 'Install Path usr/lib/modules/7.1.0-asahi/vmlinuz' \ + 'Install Path usr/lib/modules/7.1.0-asahi/modules.builtin' \ + 'Install Path usr/lib/modules/7.1.0-asahi/pkgbase' \ + 'Remove Path usr/lib/modules/7.0.0-asahi/vmlinuz' \ + 'Remove Path usr/lib/modules/7.0.0-asahi/modules.builtin' \ + 'Remove Path usr/lib/modules/7.0.0-asahi/pkgbase' \ + 'Upgrade Package linux-asahi' +transaction firmware 'Upgrade Path usr/lib/firmware/brcm/fixture.bin' 'Upgrade Package linux-firmware' +transaction modprobe 'Upgrade Path usr/lib/modprobe.d/' 'Install Path usr/lib/modprobe.d/omarchy-mac.conf' 'Upgrade Package omarchy-mac' +transaction limine 'Upgrade Path usr/share/limine/BOOTAA64.EFI' 'Upgrade Package limine' + +replay() { + local arch=$1 m=$2 name=$3 + export SIM_LOG=$TEST_ROOT/log + : >"$SIM_LOG" + local out + out=$(on "$m" run_transaction "$TEST_ROOT/root-$arch" "$TEST_ROOT/tx-$name" 2>&1) || fail "$name on $arch/$m replays" + [[ -z $out ]] || fail "$name on $arch/$m prints nothing of the gate's" "$out" + cat "$SIM_LOG" +} + +limine_kernel=$'limine-mkinitcpio-remove pre\n usr/lib/modules/7.0.0-asahi/modules.builtin\nlimine-mkinitcpio-remove post\nlimine-mkinitcpio-install\n usr/lib/modules/7.1.0-asahi/modules.builtin' +stock_kernel=$'mkinitcpio install\n usr/lib/modules/7.1.0-asahi/vmlinuz' +limine_firmware=$'limine-mkinitcpio-install\n usr/lib/firmware/brcm/fixture.bin' +stock_firmware=$'mkinitcpio install\n usr/lib/firmware/brcm/fixture.bin' +stock_modprobe=$'mkinitcpio install\n usr/lib/modprobe.d/' + +declare -A want=( + [kernel/other]=$limine_kernel + [firmware/other]=$limine_firmware + [modprobe/other]='' + [limine/other]='limine-install' + [kernel/mac-dormant]=$stock_kernel + [firmware/mac-dormant]=$stock_firmware + [modprobe/mac-dormant]=$stock_modprobe + [limine/mac-dormant]='' + [kernel/mac-active]=$'limine-mkinitcpio-remove pre\n usr/lib/modules/7.0.0-asahi/modules.builtin\nlimine-mkinitcpio-remove post\n'"$stock_kernel"$'\nlimine-mkinitcpio-install\n usr/lib/modules/7.1.0-asahi/modules.builtin' + [firmware/mac-active]=$stock_firmware$'\n'$limine_firmware + [modprobe/mac-active]=$stock_modprobe + [limine/mac-active]='' +) +want[kernel/mac-marker-only]=${want[kernel/mac-dormant]} + +for name in kernel firmware modprobe limine; do + got=$(replay x86_64 x86 "$name") + [[ $got == "${want[$name/other]}" ]] || fail "x86_64: $name transaction runs upstream's hooks" "$got" + for m in generic-aarch64 qualcomm; do + got=$(replay aarch64 "$m" "$name") + [[ $got == "${want[$name/other]}" ]] || fail "aarch64 $m: $name transaction runs as on x86_64" "$got" + done +done +pass "x86_64, generic aarch64 and Snapdragon transactions run Limine's hooks as upstream ships them" + +for m in mac-dormant mac-marker-only mac-active; do + for name in kernel firmware modprobe limine; do + [[ -v want[$name/$m] ]] || continue + got=$(replay aarch64 "$m" "$name") + [[ $got == "${want[$name/$m]}" ]] || fail "$m: $name transaction" "$got" + done +done +pass "a dormant Mac defers to mkinitcpio's own hook; an active Mac also writes Limine's entries after it" + +# --------------------------------------------------------------------------- +# The wrapper, as sudo mkinitcpio -P and mkinitcpio's hook script call it +# (/usr/local/bin first in PATH, stdin at its end). +wrapper() { + local root=$1 m=$2 + sed "s|/usr/|$root/usr/|g" "$root/usr/local/bin/mkinitcpio" >"$TEST_ROOT/wrapper" + export SIM_LOG=$TEST_ROOT/log + : >"$SIM_LOG" + on "$m" bash "$TEST_ROOT/wrapper" -P /dev/null 2>&1 || true + cat "$SIM_LOG" +} +upstream_wrapper=$'mkinitcpio -P\nlimine-mkinitcpio' +[[ $(wrapper "$TEST_ROOT/root-x86_64" x86) == "$upstream_wrapper" ]] || fail "x86_64 keeps upstream's wrapper" +for m in generic-aarch64 qualcomm; do + [[ $(wrapper "$TEST_ROOT/root-aarch64" "$m") == "$upstream_wrapper" ]] || fail "$m keeps upstream's wrapper" +done +for m in mac-dormant mac-active; do + [[ $(wrapper "$TEST_ROOT/root-aarch64" "$m") == 'mkinitcpio -P' ]] || fail "$m runs plain mkinitcpio" +done +pass "the mkinitcpio wrapper is upstream's everywhere but a Mac, where it is plain mkinitcpio" diff --git a/tests/oma-service-removal.py b/tests/oma-service-removal.py new file mode 100644 index 0000000..5686144 --- /dev/null +++ b/tests/oma-service-removal.py @@ -0,0 +1,347 @@ +#!/usr/bin/env python3 +"""Removal regression fixtures. No real systemd manager, user home or package is touched.""" +import os +from pathlib import Path +import socket +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] + +# Characters that make systemd's shell_maybe_quote() quote a value, a copy of +# SHELL_NEED_ESCAPE, GLOB_CHARS and the rest of SHELL_NEED_QUOTES in escape.h. +SHELL_NEED_QUOTES = '"\\`$*?[]' + "'()<>|&;!" + + +def systemd_environment_value(value): + r"""Return VALUE as ``systemctl show-environment`` would print it. + + print_variable() in systemctl-set-environment.c hands every value to + shell_maybe_quote(SHELL_ESCAPE_POSIX) quotes special values and uses + cescape_char() for control bytes. + """ + if not any(c in SHELL_NEED_QUOTES or c.isspace() or ord(c) < 0x20 or c == "\x7f" + for c in value): + return value + escapes = dict(zip("\a\b\f\n\r\t\v\\'", (r"\a", r"\b", r"\f", r"\n", r"\r", r"\t", r"\v", r"\\", r"\'"))) + return "$'" + "".join(escapes.get(c, f"\\{ord(c):03o}" if ord(c) < 0x20 or c == "\x7f" else c) + for c in value) + "'" + + +class Removal(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix="oma-removal-") + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.home = self.root / "home" + self.runtime = self.root / "runtime" + self.units = self.home / ".config/systemd/user" + self.units.mkdir(parents=True) + self.runtime.mkdir() + self.bin = self.root / "bin" + self.bin.mkdir() + self.log = self.root / "calls" + self.env = dict(os.environ, PATH=f"{self.bin}:{os.environ['PATH']}", CALLS=str(self.log)) + self.executable("systemctl", '''#!/bin/bash +printf '%s\\n' "$*" >> "$CALLS" +case "$*" in + *show-environment*) [[ -z ${MANAGER_FAIL-} ]] || exit 1 + # print_variable() prints every value the way a shell would read it, so the + # fixture, not this stub, decides how the value is quoted. + echo "XDG_CONFIG_HOME=${CONFIG_HOME_RAW-${CONFIG_HOME-}}" ;; + *property=ExecStart*) echo "${EFFECTIVE-}" ;; + *property=LoadState*) echo "${LOAD_STATE-loaded}" ;; + # A unit that failed stays failed after it is stopped, and systemd refuses + # reset-failed for every other state, reporting the unit as not loaded. + *property=ActiveState*) echo "${ACTIVE_STATE-inactive}" ;; + *" reset-failed "*) [[ ${ACTIVE_STATE-inactive} == failed && -z ${RESET_FAIL-} ]] || { + printf 'Failed to reset failed state of unit: Unit is not loaded.\n' >&2; exit 1; } ;; + *" stop "*) [[ -z ${STOP_FAIL-} ]] || exit 1 ;; +esac +''') + + def executable(self, name, source): + path = self.bin / name + path.write_text(source) + path.chmod(0o755) + + def online(self): + sock = socket.socket(socket.AF_UNIX) + sock.bind(str(self.runtime / "bus")) + self.addCleanup(sock.close) + + def install(self, app, binary=None): + unit = self.units / f"{app}.service" + unit.write_text(f'[Service]\nExecStart="{binary or "/usr/bin/" + app}" --config "{self.home}/config.toml" daemon\n') + target = self.units / "graphical-session.target.wants" + target.mkdir(exist_ok=True) + link = target / unit.name + link.symlink_to(f"../{unit.name}") + return unit, link + + def run_remove(self, app): + self.log.unlink(missing_ok=True) # Every run is judged on its own calls. + return subprocess.run(["bash", str(ROOT / f"pkgbuilds/{app}-bin/package-remove"), + "--user", app, str(self.home), str(self.runtime)], + env=self.env, text=True, capture_output=True) + + def test_logged_out_users_and_data_preservation(self): + for app in ("omawake", "omaspeak"): + unit, link = self.install(app) + config = self.home / f"{app}.toml" + config.write_text("keep settings and models") + result = self.run_remove(app) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(unit.exists()) + self.assertFalse(link.is_symlink()) + self.assertEqual(config.read_text(), "keep settings and models") + self.assertFalse(self.log.exists(), "offline cleanup contacted systemd") + + def test_active_unit_is_stopped_before_removing_it(self): + self.online() + for app in ("omawake", "omaspeak"): + unit, link = self.install(app) + self.env["EFFECTIVE"] = f"{{ path=/usr/bin/{app} ; argv[]=/usr/bin/{app} daemon ; }}" + result = self.run_remove(app) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(unit.exists()) + self.assertFalse(link.is_symlink()) + calls = self.log.read_text().splitlines() + self.assertLess(calls.index(f"--user stop {app}.service"), calls.index(f"--user disable {app}.service")) + self.assertEqual(calls[-1], "--user daemon-reload") + + def test_failed_stop_prevents_unit_deletion_and_fails_hook(self): + self.online() + unit, link = self.install("omawake") + self.env.update(STOP_FAIL="1", EFFECTIVE="{ path=/usr/bin/omawake ; }") + result = self.run_remove("omawake") + self.assertNotEqual(result.returncode, 0) + self.assertTrue(unit.exists()) + self.assertTrue(link.is_symlink()) + self.assertNotIn("disable", self.log.read_text()) + + def test_reset_failed_is_requested_only_for_a_unit_that_failed(self): + self.online() + for app in ("omawake", "omaspeak"): + unit, link = self.install(app) + self.env["EFFECTIVE"] = f"{{ path=/usr/bin/{app} ; argv[]=/usr/bin/{app} daemon ; }}" + # A loaded unit that never failed is not failed, and asking systemd to + # reset it fails with "Unit is not loaded": that must not abort removal. + self.env["ACTIVE_STATE"] = "active" + result = self.run_remove(app) + self.assertEqual(result.returncode, 0, result.stderr) + calls = [call for call in self.log.read_text().splitlines() if call.startswith("--user")] + self.assertNotIn(f"--user reset-failed {app}.service", calls) + # Reading the manager's state is welcome; the only changes asked for + # are the stop, the disable and the reload that follow them. + self.assertEqual([call for call in calls + if "show-environment" not in call and "--property=" not in call], + [f"--user stop {app}.service", + f"--user disable {app}.service", "--user daemon-reload"]) + self.assertFalse(unit.exists()) + self.assertFalse(link.is_symlink()) + + # A unit that failed does keep that state once stopped, and there the + # reset belongs between stopping the service and disabling the unit. + unit, link = self.install(app) + self.env["ACTIVE_STATE"] = "failed" + result = self.run_remove(app) + self.assertEqual(result.returncode, 0, result.stderr) + calls = self.log.read_text().splitlines() + reset = f"--user reset-failed {app}.service" + self.assertIn(reset, calls) + self.assertLess(calls.index(f"--user stop {app}.service"), calls.index(reset)) + self.assertLess(calls.index(reset), calls.index(f"--user disable {app}.service")) + self.assertFalse(unit.exists()) + self.assertFalse(link.is_symlink()) + del self.env["ACTIVE_STATE"] + + def test_reset_refused_by_a_healthy_manager_still_fails_the_transaction(self): + self.online() + unit, link = self.install("omaspeak") + self.env.update(EFFECTIVE="{ path=/usr/bin/omaspeak ; }", + ACTIVE_STATE="failed", RESET_FAIL="1") + result = self.run_remove("omaspeak") + self.assertNotEqual(result.returncode, 0) + self.assertTrue(unit.exists()) + self.assertTrue(link.is_symlink()) + + def test_custom_build_and_mask_are_preserved(self): + unit, link = self.install("omawake", "/home/user/dev/omawake") + self.assertEqual(self.run_remove("omawake").returncode, 0) + self.assertTrue(unit.exists()) + self.assertTrue(link.is_symlink()) + unit.unlink() + unit.symlink_to("/dev/null") + self.assertEqual(self.run_remove("omawake").returncode, 0) + self.assertTrue(unit.is_symlink()) + self.assertFalse(self.log.exists()) + + def test_effective_override_and_missing_online_unit(self): + self.online() + unit, _ = self.install("omaspeak") + self.env["EFFECTIVE"] = "{ path=/home/user/development/omaspeak ; }" + self.assertEqual(self.run_remove("omaspeak").returncode, 0) + self.assertTrue(unit.exists()) + self.assertNotIn(" stop ", self.log.read_text()) + unit.unlink() + self.env.update(EFFECTIVE="", LOAD_STATE="not-found") + self.assertEqual(self.run_remove("omaspeak").returncode, 0) + self.assertNotIn(" stop ", self.log.read_text()) + + def test_manager_config_home_and_unavailable_manager(self): + self.online() + default = self.units + self.units = self.home / "custom-config/systemd/user" + self.units.mkdir(parents=True) + unit, link = self.install("omawake") + self.env.update(CONFIG_HOME=str(self.home / "custom-config"), MANAGER_FAIL="1") + self.assertNotEqual(self.run_remove("omawake").returncode, 0) + self.assertTrue(unit.exists()) + del self.env["MANAGER_FAIL"] + self.assertEqual(self.run_remove("omawake").returncode, 0) + self.assertFalse(unit.exists()) + self.assertFalse(link.is_symlink()) + self.assertTrue(default.exists()) + + def test_root_dispatch_drops_privileges_and_propagates_failure(self): + passwd = f"fixture:x:12345:12345::{self.home}:/bin/bash" + self.executable("getent", f"#!/bin/sh\nprintf '%s\\n' '{passwd}'\n") + self.executable("runuser", '#!/bin/sh\nprintf "%s\\n" "$*" >> "$CALLS"\nexit 1\n') + result = subprocess.run(["bash", str(ROOT / "pkgbuilds/omawake-bin/package-remove"), "omawake"], env=self.env, capture_output=True) + self.assertNotEqual(result.returncode, 0) + self.assertIn("-u fixture -- env", self.log.read_text()) + self.assertIn("--user omawake", self.log.read_text()) + + def test_offline_executable_overrides_are_preserved(self): + for app in ("omawake", "omaspeak"): + for spacing in ("ExecStart=\nExecStart={command}", + # systemd's parser throws the whitespace around an + # assignment away, so both of these spellings still + # name a development build, exactly as the first does. + "ExecStart =\nExecStart = {command}", + "\tExecStart\t=\t{command}"): + unit, link = self.install(app) + dropins = Path(str(unit) + ".d") + dropins.mkdir(exist_ok=True) + (dropins / "override.conf").write_text("[Service]\n" + spacing.format( + command=f"/home/user/build/{app} daemon") + "\n") + try: + with self.subTest(app=app, spacing=spacing): + self.assertEqual(self.run_remove(app).returncode, 0) + self.assertTrue(unit.exists(), "removed a service with an override") + self.assertTrue(link.is_symlink(), "unlinked a service with an override") + finally: + unit.unlink(missing_ok=True) + link.unlink(missing_ok=True) + self.assertFalse(self.log.exists(), "offline cleanup contacted systemd") + + def test_shell_quoted_manager_config_home_is_resolved(self): + self.online() + default_units = self.units + for app in ("omawake", "omaspeak"): + config_home = self.home / f"{app} custom's \\ config" + self.units = config_home / "systemd/user" + self.units.mkdir(parents=True) + unit, link = self.install(app) + # A unit in the directory the manager reads nothing from is no unit of + # the manager's, and the helper has no business reaching for it. + stray = default_units / f"{app}.service" + stray.write_text(f'[Service]\nExecStart="/usr/bin/{app}" daemon\n') + printed = self.env["CONFIG_HOME_RAW"] = systemd_environment_value(str(config_home)) + with self.subTest(app=app, printed=printed): + self.assertTrue(printed.startswith("$'") and printed.endswith("'"), + "a path of spaces is not what a plain value looks like") + result = self.run_remove(app) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(unit.exists()) + self.assertFalse(link.is_symlink()) + self.assertTrue(stray.is_file(), "guessed at a directory no manager reads") + del self.env["CONFIG_HOME_RAW"] + + def test_control_character_manager_config_home_is_resolved(self): + self.online() + for app in ("omawake", "omaspeak"): + for suffix in ("tab\tpath", "newline\npath\n", "\a\b\f\r\v", "\x01\x1b\x7f"): + config_home = self.home / (app + suffix) + self.units = config_home / "systemd/user" + self.units.mkdir(parents=True) + unit, link = self.install(app) + self.env["CONFIG_HOME_RAW"] = systemd_environment_value(str(config_home)) + with self.subTest(app=app, suffix=suffix): + result = self.run_remove(app) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(unit.exists()) + self.assertFalse(link.is_symlink()) + + def test_unreadable_manager_config_home_aborts_the_cleanup(self): + self.online() + for app in ("omawake", "omaspeak"): + unit, link = self.install(app) + # Truncated output must not make cleanup guess at a directory. + self.env["CONFIG_HOME_RAW"] = "$'" + str(self.home / f"broken {app} config") + with self.subTest(app=app): + result = self.run_remove(app) + self.assertNotEqual(result.returncode, 0) + self.assertTrue(unit.exists()) + self.assertTrue(link.is_symlink()) + self.assertNotIn(" stop ", self.log.read_text()) + del self.env["CONFIG_HOME_RAW"] + + def test_relative_manager_config_home_keeps_the_default_directory(self): + self.online() + for app in ("omawake", "omaspeak"): + unit, link = self.install(app) + # An XDG_CONFIG_HOME that is not absolute is no setting at all: the + # manager itself reads the home's .config directory then. + self.env["CONFIG_HOME_RAW"] = systemd_environment_value(f"relative {app} config") + with self.subTest(app=app): + result = self.run_remove(app) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(unit.exists()) + self.assertFalse(link.is_symlink()) + del self.env["CONFIG_HOME_RAW"] + + def test_packaging_installs_hooks_and_helpers(self): + import shutil + for app, version in (("omawake", "0.0.3"), ("omaspeak", "0.0.3")): + source = self.root / app / "src" + package = self.root / app / "pkg" + release = source / f"{app}-{version}-linux-x86_64" + release.mkdir(parents=True) + for path in [app, "lib/libaudiocpp.so.0.1.0", f"packaging/systemd/{app}.service", + "README.md", "INSTALL.md", "ACCELERATOR_SETUP.md", "CHANGELOG.md", + "RELEASE_NOTES.md", "DEMO.md", "RUNTIME.md", "config.example.toml", + "licenses/LICENSE", "assets/fixture", "benchmarks/fixture"]: + target = release / path + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text("fixture") + directory = ROOT / f"pkgbuilds/{app}-bin" + for name in ("package-remove", "remove-user-services.hook"): + shutil.copyfile(directory / name, source / name) + env = dict(self.env, srcdir=str(source), pkgdir=str(package), CARCH="x86_64") + result = subprocess.run(["bash", "-c", 'source "$1"; package', "package-fixture", str(directory / "PKGBUILD")], env=env, capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + helper = package / f"usr/lib/{app}/package-remove" + self.assertEqual(helper.read_bytes(), (directory / "package-remove").read_bytes()) + self.assertEqual(helper.stat().st_mode & 0o777, 0o755) + self.assertTrue((package / f"usr/share/libalpm/hooks/30-{app}-remove-user-services.hook").exists()) + + def test_hook_contract_and_package_release(self): + scripts = [] + for app in ("omawake", "omaspeak"): + directory = ROOT / f"pkgbuilds/{app}-bin" + hook = (directory / "remove-user-services.hook").read_text() + self.assertIn("Operation = Remove", hook) + self.assertNotIn("Operation = Upgrade", hook) + self.assertIn("When = PreTransaction", hook) + self.assertIn("AbortOnFail", hook) + self.assertIn(f"Exec = /usr/lib/{app}/package-remove {app}", hook) + self.assertIn("pkgrel=4", (directory / "PKGBUILD").read_text()) + scripts.append((directory / "package-remove").read_bytes()) + self.assertEqual(*scripts) + + +if __name__ == "__main__": + unittest.main() 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/pr-workflow-approval.cjs b/tests/pr-workflow-approval.cjs new file mode 100644 index 0000000..e4fc661 --- /dev/null +++ b/tests/pr-workflow-approval.cjs @@ -0,0 +1,314 @@ +const assert = require('node:assert/strict'); +const { readFileSync, mkdtempSync, rmSync } = require('node:fs'); +const { join } = require('node:path'); +const { tmpdir } = require('node:os'); +const { test } = require('node:test'); +const { execFileSync, spawnSync } = require('node:child_process'); +const approve = require('../.github/scripts/approve-pr-workflows.cjs'); + +const BUILD = '.github/workflows/build-pr.yml'; +const TESTS = '.github/workflows/test.yml'; +const time = '2026-09-19T02:47:52Z'; +const earlier = '2026-09-19T02:36:04Z'; +const pr = { + number: 390, state: 'open', updated_at: time, + head: { sha: 'reviewed-sha', ref: 'ghost', repo: { id: 42 } }, + labels: [{ name: 'build-approved' }], +}; +const clone = value => structuredClone(value); +const run = (id, path, overrides = {}) => ({ + id, path, event: 'pull_request', head_sha: pr.head.sha, + head_repository: { id: 42 }, head_branch: 'ghost', pull_requests: [], + status: 'completed', conclusion: 'action_required', created_at: time, + ...overrides, +}); + +function fixture(initial = [run(1, TESTS, { created_at: earlier }), run(2, BUILD)], options = {}) { + const state = { pr: clone(pr), runs: clone(initial), approved: [], reads: 0, tick: 0, transitions: [] }; + const repo = { owner: 'omacom', repo: 'omarchy-pkgs' }; + const github = { + rest: { + pulls: { get: async args => { + assert.deepEqual(args, { ...repo, pull_number: 390 }); + state.reads++; + options.onRead?.(state); + return { data: clone(state.pr) }; + } }, + actions: { + listWorkflowRunsForRepo() {}, + getWorkflowRun: async ({ run_id }) => { + const current = state.runs.find(run => run.id === run_id); + state.transitions.push([run_id, current.status]); + return { data: clone(current) }; + }, + approveWorkflowRun: async args => { + assert.deepEqual(args, { ...repo, run_id: args.run_id }); + options.onApprove?.(state, args.run_id); + const current = state.runs.find(run => run.id === args.run_id); + assert.equal(current.conclusion, 'action_required'); + state.approved.push(current.id); + current.status = 'queued'; + current.conclusion = null; + }, + }, + }, + paginate: async (method, args) => { + assert.equal(method, github.rest.actions.listWorkflowRunsForRepo); + assert.deepEqual(args, { ...repo, event: 'pull_request', head_sha: pr.head.sha, per_page: 100 }); + return clone(state.runs).reverse(); // GitHub returns newest first. + }, + }; + const invoke = overrides => approve({ + github, context: { repo, payload: { action: 'labeled', pull_request: clone(pr) } }, + core: { info() {} }, vouchStatus: 'unknown', attempts: 6, + sleep: async () => { + state.tick++; + for (const current of state.runs) { + if (current.status === 'queued' && state.tick >= (options.queueUntil ?? 1)) current.status = 'in_progress'; + } + options.onSleep?.(state); + }, + ...overrides, + }); + return { state, invoke, github }; +} + +test('an unvouched, labeled fork PR releases both required workflows', async () => { + const { state, invoke } = fixture(); + await invoke(); + assert.deepEqual(state.approved, [1, 2]); +}); + +test('waits for the label-triggered build instead of stopping at the old build', async () => { + const { state, invoke } = fixture([ + run(1, BUILD, { created_at: earlier }), run(2, TESTS, { created_at: earlier }), + ], { + onSleep(state) { + if (state.tick === 2) { + assert.deepEqual(state.approved, []); + state.runs.push(run(3, BUILD)); + } + }, + queueUntil: 4, + onApprove(state, id) { + if (id === 3) assert.equal(state.runs.find(run => run.id === 1).status, 'in_progress'); + }, + }); + await invoke({ attempts: 10 }); + assert.deepEqual(state.approved, [1, 2, 3]); + assert.ok(state.transitions.some(([id, status]) => id === 1 && status === 'queued')); +}); + +test('approves only the two known workflows for this fork, branch, PR and SHA', async () => { + const unrelated = [ + { path: '.github/workflows/publish.yml' }, { event: 'push' }, + { head_sha: 'other-sha' }, { head_repository: { id: 99 } }, + { head_branch: 'other-branch' }, { pull_requests: [{ number: 391 }] }, + ].map((overrides, i) => run(10 + i, BUILD, overrides)); + const { state, invoke } = fixture([run(1, TESTS), run(2, BUILD), ...unrelated]); + await invoke(); + assert.deepEqual(state.approved, [1, 2]); +}); + +test('accepts a run explicitly associated with this PR', async () => { + const { state, invoke } = fixture([ + run(1, TESTS), run(2, BUILD, { pull_requests: [{ number: 390 }] }), + ]); + await invoke(); + assert.deepEqual(state.approved, [1, 2]); +}); + +test('does not restart running or completed workflows', async () => { + const { state, invoke } = fixture([ + run(1, TESTS, { conclusion: 'success' }), + run(2, BUILD, { status: 'in_progress', conclusion: null }), + ]); + await invoke(); + assert.deepEqual(state.approved, []); +}); + +test('an obsolete build hold cannot cancel a newer build that was already released', async () => { + for (const current of [ + { status: 'queued', conclusion: null }, { status: 'in_progress', conclusion: null }, + { status: 'completed', conclusion: 'success' }, { status: 'completed', conclusion: 'failure' }, + ]) { + const { state, invoke } = fixture([ + run(1, BUILD, { created_at: earlier }), run(2, TESTS), run(3, BUILD, current), + ]); + await invoke(); + assert.deepEqual(state.approved, [2]); + } +}); + +for (const status of ['denounced', '', undefined, 'unexpected']) { + test(`vouch status ${String(status)} fails closed`, async () => { + const { state, invoke } = fixture(); + await assert.rejects(invoke({ vouchStatus: status }), /Cannot approve workflows/); + assert.deepEqual(state.approved, []); + }); +} + +for (const status of ['bot', 'collaborator', 'vouched']) { + test(`a labeled ${status} can also clear GitHub's approval gate`, async () => { + const { state, invoke } = fixture(); + await invoke({ vouchStatus: status }); + assert.deepEqual(state.approved, [1, 2]); + }); +} + +for (const [name, change] of [ + ['removed label', pr => { pr.labels = []; }], + ['changed head', pr => { pr.head.sha = 'new-sha'; }], + ['closed PR', pr => { pr.state = 'closed'; }], +]) { + test(`${name} stops approval, including changes immediately before a write`, async () => { + for (const read of [1, 2]) { + const { state, invoke } = fixture(undefined, { onRead(state) { + if (state.reads === read) change(state.pr); + } }); + await invoke(); + assert.deepEqual(state.approved, []); + } + }); +} + +test('revocation between approvals prevents releasing further workflows', async () => { + const { state, invoke } = fixture(undefined, { onSleep(state) { state.pr.labels = []; } }); + await invoke(); + assert.deepEqual(state.approved, [1]); +}); + +test('a delayed tests workflow is also awaited', async () => { + const { state, invoke } = fixture([run(2, BUILD)], { + onSleep(state) { if (state.tick === 2) state.runs.push(run(1, TESTS)); }, + }); + await invoke(); + assert.deepEqual(state.approved, [1, 2]); +}); + +test('reopening a labeled PR waits for its new tests, even if old tests passed at the same SHA', async () => { + const { state, invoke } = fixture([ + run(1, TESTS, { created_at: earlier, conclusion: 'success' }), run(2, BUILD), + ], { onSleep(state) { if (state.tick === 2) state.runs.push(run(3, TESTS)); } }); + await invoke({ context: { repo: { owner: 'omacom', repo: 'omarchy-pkgs' }, + payload: { action: 'reopened', pull_request: clone(pr) } } }); + assert.deepEqual(state.approved, [2, 3]); +}); + +test('missing current runs time out without approving stale builds', async () => { + const { state, invoke } = fixture([run(1, TESTS), run(2, BUILD, { created_at: earlier })]); + await assert.rejects(invoke(), /Timed out/); + assert.deepEqual(state.approved, []); +}); + +test('API failure is reported rather than silently treated as approval', async () => { + const { state, invoke } = fixture(undefined, { onApprove() { throw new Error('Forbidden'); } }); + await assert.rejects(invoke(), /Forbidden/); + assert.deepEqual(state.approved, []); +}); + +// Execute the actual build workflow's approval script and shell gate. This +// covers the stale event payload that originally accompanied held PR runs. +const workflow = readFileSync(join(__dirname, '../.github/workflows/build-pr.yml'), 'utf8'); +const approvalScript = workflow.match(/- id: approval[\s\S]*?script: \|\n([\s\S]*?)(?= # One matrix)/)[1] + .split('\n').map(line => line.replace(/^ /, '')).join('\n'); +const gateScript = workflow.match(/ case "\$STATUS" in[\s\S]*? esac/)[0] + '\nprintf "%s" "$trusted"'; + +test('the build reads the live label rather than its pre-label event payload', async () => { + const execute = new (Object.getPrototypeOf(async function () {}).constructor)('github', 'context', 'core', approvalScript); + for (const [current, approved] of [ + [pr, true], [{ ...pr, labels: [] }, false], + [{ ...pr, head: { ...pr.head, sha: 'new-sha' } }, false], + [{ ...pr, state: 'closed' }, false], + ]) { + const outputs = {}; + await execute({ rest: { pulls: { get: async () => ({ data: current }) } } }, + { repo: {}, payload: { pull_request: { ...pr, labels: [] } } }, + { setOutput: (key, value) => { outputs[key] = value; } }); + assert.equal(outputs.approved, approved); + } +}); + +test('the build gate permits a missing vouch only with approval, never a denouncement or lookup failure', () => { + for (const [status, approved, expected] of [ + ['unknown', 'true', 'true'], ['unknown', 'false', 'false'], + ['denounced', 'true', 'false'], ['', 'true', 'false'], ['unexpected', 'true', 'false'], + ['vouched', 'false', 'true'], ['collaborator', 'false', 'true'], + ['bot', 'false', 'true'], ['dispatch', 'false', 'true'], + ]) { + assert.equal(execFileSync('bash', ['-c', gateScript], { + env: { ...process.env, STATUS: status, APPROVED: approved }, encoding: 'utf8', + }), expected); + } +}); + +// Exercise the actual reporting job, including its GitHub check name: a +// successful/skipped check called "result" would accidentally allow merging +// a PR whose build never ran. GitHub keeps a missing required check pending. +const resultJob = workflow.slice(workflow.indexOf('\n result:\n')); +const resultName = resultJob.match(/^ name: (.+)$/m)[1]; +const resultScript = resultJob.split(' - run: |\n')[1]; +function report({ trusted = 'false', vouch = 'unknown', empty = 'false', changes = 'success', build = 'skipped' } = {}) { + const needs = { + changes: { result: changes, outputs: { trusted, vouch_status: vouch, empty } }, + build: { result: build }, + }; + // The reporting expressions use &&, || and string equality, with the + // same semantics in JavaScript and Actions for these string-only fixtures. + const render = text => text.replace(/\$\{\{(.*?)\}\}/g, (_, expression) => + new Function('needs', `return (${expression})`)(needs)); + const directory = mkdtempSync(join(tmpdir(), 'build-approval-report-')); + const summaryPath = join(directory, 'summary'); + try { + const result = spawnSync('bash', ['-e', '-c', render(resultScript)], { + env: { ...process.env, GITHUB_STEP_SUMMARY: summaryPath }, encoding: 'utf8', + }); + return { name: render(resultName), ...result, + summary: result.stdout.includes('::notice::') ? readFileSync(summaryPath, 'utf8') : '' }; + } finally { + rmSync(directory, { recursive: true, force: true }); + } +} + +test('an unvouched PR waits without publishing a passing or failing required result', () => { + const result = report(); + assert.equal(result.name, 'Awaiting build approval'); + assert.equal(result.status, 0); + assert.match(result.stdout, /::notice::Awaiting maintainer build approval/); + assert.doesNotMatch(result.stdout, /::error::/); + assert.match(result.summary, /required \*\*result\*\* check remains pending/); +}); + +test('applying build-approved transitions the waiting PR to the required build result', () => { + assert.notEqual(report().name, 'result'); + const approved = report({ trusted: 'true', build: 'success' }); + assert.equal(approved.name, 'result'); + assert.equal(approved.status, 0); + const failed = report({ trusted: 'true', build: 'failure' }); + assert.equal(failed.name, 'result'); + assert.notEqual(failed.status, 0); +}); + +test('trusted tooling-only PRs still satisfy the required result without a package build', () => { + const result = report({ trusted: 'true', vouch: 'vouched' }); + assert.equal(result.name, 'result'); + assert.equal(result.status, 0); +}); + +for (const [name, overrides] of [ + ['denounced author', { vouch: 'denounced' }], + ['failed trust lookup', { vouch: '', changes: 'failure' }], + ['missing trust result', { vouch: '' }], + ['missing gate output', { trusted: '' }], + ['failed planning', { changes: 'failure' }], + ['cancelled planning', { changes: 'cancelled' }], + ['empty PR', { empty: 'true' }], + ['cancelled build', { trusted: 'true', build: 'cancelled' }], +]) { + test(`${name} fails the required result instead of masquerading as pending approval`, () => { + const result = report(overrides); + assert.equal(result.name, 'result'); + assert.notEqual(result.status, 0); + assert.doesNotMatch(result.stdout, /::notice::Awaiting maintainer build approval/); + }); +} diff --git a/tests/publish-artifact.sh b/tests/publish-artifact.sh new file mode 100755 index 0000000..2c5269c --- /dev/null +++ b/tests/publish-artifact.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# Self-test for bin/publish-artifact against a local directory as the remote. +# Needs repo-add, gpg, rclone, bsdtar (run in the Arch builder/test container). +set -euo pipefail +ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +T=$(mktemp -d); chmod 755 "$T"; trap 'rm -rf "$T"' EXIT +REMOTE="$T/r2"; mkdir -p "$REMOTE" + +# throwaway signing key +export GNUPGHOME="$T/g"; mkdir -m700 "$GNUPGHOME" +gpg --batch --quiet --passphrase '' --quick-gen-key 'Test ' ed25519 sign 0 2>/dev/null +export GPG_PRIVATE_KEY=$(gpg --batch --armor --export-secret-keys 'Test ') GPG_PASSPHRASE='' +unset GNUPGHOME + +# minimal real packages via makepkg +mkpkg() { # mkpkg [payload] + local d="$T/src/$1-$2${4:+-$4}"; mkdir -p "$d"; cd "$d" + printf 'pkgname=%s\npkgver=1.0\npkgrel=%s\narch=(%s)\npackage(){ install -Dm644 /dev/null "$pkgdir/usr/share/%s-%s"; echo "%s" > "$pkgdir/usr/share/%s-%s"; }\n' "$1" "$2" "$3" "$1" "$2" "${4:-payload}" "$1" "$2" > PKGBUILD + # CARCH so the PKGINFO records the requested arch (--ignorearch would + # stamp the host's). + # makepkg refuses to run as root (the CI test container does); build the + # fixture as an unprivileged user in that case. + if (( EUID == 0 )); then + id -u fixture >/dev/null 2>&1 || useradd -m fixture + chmod 755 "$T/src"; chown -R fixture "$d" + runuser -u fixture -- env CARCH=$3 makepkg -f --nodeps --ignorearch >/dev/null 2>&1 + else + CARCH=$3 makepkg -f --nodeps --ignorearch >/dev/null 2>&1 + fi + ls "$d"/*.pkg.tar.zst +} +A1=$(mkpkg alpha 1 any); A2=$(mkpkg alpha 2 any); B1=$(mkpkg beta 1 x86_64); C1=$(mkpkg gamma 1 aarch64) + +pub() { "$ROOT/bin/publish-artifact" --remote "$REMOTE" --mirror edge --arch x86_64 "$@" >"$T/out" 2>&1; } +entries() { tar -tf "$REMOTE/edge/x86_64/omarchy.db.tar.zst" | grep '/$' | sort | tr '\n' ' '; } +pass() { echo "PASS: $1"; } +fail() { echo "FAIL: $1"; cat "$T/out"; exit 1; } + +pub "$A1" && [[ "$(entries)" == "alpha-1.0-1/ " ]] && [[ -f "$REMOTE/edge/x86_64/$(basename "$A1").sig" ]] \ + && pass "first publish creates db with one entry and a signature" || fail "first publish" + +sum_before=$(sha256sum "$REMOTE/edge/x86_64/$(basename "$A1")") +pub "$B1" && [[ "$(entries)" == "alpha-1.0-1/ beta-1.0-1/ " ]] && [[ "$(sha256sum "$REMOTE/edge/x86_64/$(basename "$A1")")" == "$sum_before" ]] \ + && pass "second package added incrementally; first file untouched" || fail "incremental add" + +pub "$A2" && [[ "$(entries)" == "alpha-1.0-2/ beta-1.0-1/ " ]] && [[ -f "$REMOTE/edge/x86_64/$(basename "$A1")" ]] \ + && pass "new pkgrel replaces the db entry, old file remains on remote" || fail "replace entry" + +# Same bytes again: allowed, idempotent (this is how a fast-ring artifact +# reaches rc and stable after edge, and how a re-run recovers). +pub "$A2" && grep -q 'identical bytes' "$T/out" && [[ "$(entries)" == "alpha-1.0-2/ beta-1.0-1/ " ]] \ + && pass "identical bytes under an existing name: accepted, db unchanged" || fail "identical republish" + +# Orphan repair: a file that reached the remote but whose db entry was lost +# (a concurrent publish overwrote the db) is fixed by publishing it again. +( cd "$REMOTE/edge/x86_64" && repo-remove --quiet omarchy.db.tar.zst alpha >/dev/null 2>&1 ) +[[ "$(entries)" == "beta-1.0-1/ " ]] || fail "fixture: could not drop alpha from the db" +pub "$A2" && [[ "$(entries)" == "alpha-1.0-2/ beta-1.0-1/ " ]] \ + && pass "orphaned file regains its db entry on republish" || fail "orphan repair" + +# Different bytes under an existing name: refused. Build alpha-2 again with +# a different payload (makepkg is reproducible, so the content must change). +A2b=$(mkpkg alpha 2 any different-payload) +[[ "$(md5sum < "$A2")" != "$(md5sum < "$A2b")" ]] || { echo "fixture: rebuilt package is byte-identical, cannot test"; exit 1; } +if pub "$A2b"; then fail "different bytes under same filename should refuse"; else grep -q 'DIFFERENT bytes' "$T/out" && pass "different bytes under an existing name refused" || fail "wrong refusal reason"; fi + +if pub "$C1"; then fail "aarch64 package into x86_64 should refuse"; else grep -q 'publishing to x86_64' "$T/out" && pass "wrong-arch package refused" || fail "wrong-arch reason"; fi + +cp "$B1" "$T/renamed-1.0-1-x86_64.pkg.tar.zst" +if pub "$T/renamed-1.0-1-x86_64.pkg.tar.zst"; then fail "filename/PKGINFO mismatch should refuse"; else grep -q 'does not match PKGINFO' "$T/out" && pass "filename must match PKGINFO" || fail "mismatch reason"; fi + +# db must verify: pacman can read it and each package's signature checks +gpg --batch --quiet --import <<<"$GPG_PRIVATE_KEY" 2>/dev/null || true +( cd "$REMOTE/edge/x86_64" && for f in *.pkg.tar.zst; do gpg --batch --quiet --verify "$f.sig" "$f" 2>/dev/null || { echo "FAIL: signature $f"; exit 1; }; done ) && pass "all signatures verify" 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..bb9c8dc --- /dev/null +++ b/tests/upstream-watch.py @@ -0,0 +1,400 @@ +#!/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) + + +class T3CodeHookTest(unittest.TestCase): + """Keep both desktop architectures on the same complete upstream release.""" + + def setUp(self): + work = tempfile.TemporaryDirectory() + self.addCleanup(work.cleanup) + self.root = Path(work.name) + self.recipe = self.root / "PKGBUILD" + self.recipe.write_text("pkgver=0.0.41\n") + self.feed = self.root / "latest-linux.yml" + self.feed.write_text("version: 0.0.42\npath: T3-Code-0.0.42-x86_64.AppImage\n") + self.arm_feed = self.root / "latest-linux-arm64.yml" + self.arm_feed.write_text("version: 0.0.42\npath: T3-Code-0.0.42-arm64.AppImage\n") + for arch in ("x86_64", "arm64"): + (self.root / f"T3-Code-0.0.42-{arch}.AppImage").write_text(arch) + # Serve only fixture assets, and record the requested release URLs. + curl = self.root / "curl" + curl.write_text('#!/bin/bash\nurl="${@: -1}"\nprintf "%s\\n" "$url" >> requests\ncat "${url##*/}"\n') + curl.chmod(0o755) + self.env = dict(os.environ, PATH=f"{self.root}:{os.environ['PATH']}") + + def run_hook(self): + return subprocess.run( + ['bash', str(ROOT / 'pkgbuilds/t3code-bin/.omarchy/upstream.sh')], + cwd=self.root, env=self.env, text=True, capture_output=True, + ) + + def test_hashes_both_architectures_from_one_release(self): + result = self.run_hook() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(json.loads(result.stdout), { + 'pkgver': '0.0.42', + 'sha256sums': { + arch: [w.hash_file(self.root / f'T3-Code-0.0.42-{asset_arch}.AppImage', 'sha256')] + for arch, asset_arch in [('x86_64', 'x86_64'), ('aarch64', 'arm64')] + }, + }) + self.assertIn('/download/v0.0.42/latest-linux-arm64.yml', (self.root / 'requests').read_text()) + + def test_current_version_does_not_download_assets(self): + self.recipe.write_text('pkgver=0.0.42\n') + result = self.run_hook() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(json.loads(result.stdout), {}) + self.assertEqual(len((self.root / 'requests').read_text().splitlines()), 1) + + def test_incomplete_or_mismatched_arm_release_reports_no_update(self): + for bad_feed in ('', 'version: 0.0.43\npath: T3-Code-0.0.42-arm64.AppImage\n', + 'version: 0.0.42\npath: renamed.AppImage\n'): + with self.subTest(feed=bad_feed): + self.arm_feed.write_text(bad_feed) + result = self.run_hook() + self.assertNotEqual(result.returncode, 0) + self.assertEqual(result.stdout, '') + self.arm_feed.unlink() + result = self.run_hook() + self.assertNotEqual(result.returncode, 0) + self.assertEqual(result.stdout, '') + + def test_missing_arm_asset_reports_no_update(self): + (self.root / 'T3-Code-0.0.42-arm64.AppImage').unlink() + result = self.run_hook() + self.assertNotEqual(result.returncode, 0) + self.assertEqual(result.stdout, '') + + +if __name__ == '__main__': + unittest.main(verbosity=2)